internal/lsp: make the guru emulation tests slightly looser with a different matching system

We now match only the things we realy need to, to allow the description to vary more.

Change-Id: Ib3591c41ed5a5c725f2a3efb180ba17f808de51a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/170341
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/cmd/definition_test.go b/internal/lsp/cmd/definition_test.go
index 0c10737..06951a5 100644
--- a/internal/lsp/cmd/definition_test.go
+++ b/internal/lsp/cmd/definition_test.go
@@ -29,10 +29,10 @@
 )
 
 type definition struct {
-	src   span.Span
-	flags string
-	def   span.Span
-	match string
+	src     span.Span
+	flags   string
+	def     span.Span
+	pattern pattern
 }
 
 type definitions map[span.Span]definition
@@ -66,24 +66,26 @@
 
 func (l definitions) godef(src, def span.Span) {
 	l[src] = definition{
-		src: src,
-		def: def,
+		src:     src,
+		def:     def,
+		pattern: newPattern("", def),
 	}
 }
 
 func (l definitions) typdef(src, def span.Span) {
 	l[src] = definition{
-		src: src,
-		def: def,
+		src:     src,
+		def:     def,
+		pattern: newPattern("", def),
 	}
 }
 
 func (l definitions) definition(src span.Span, flags string, def span.Span, match string) {
 	l[src] = definition{
-		src:   src,
-		flags: flags,
-		def:   def,
-		match: match,
+		src:     src,
+		flags:   flags,
+		def:     def,
+		pattern: newPattern(match, def),
 	}
 }
 
@@ -104,76 +106,41 @@
 		got := captureStdOut(t, func() {
 			tool.Main(context.Background(), app, args)
 		})
-		if d.match == "" {
-			expect := fmt.Sprint(d.def)
-			if !strings.HasPrefix(got, expect) {
-				t.Errorf("definition %v\nexpected:\n%s\ngot:\n%s", args, expect, got)
-			}
-		} else {
-			expect := os.Expand(d.match, func(name string) string {
-				switch name {
-				case "file":
-					fname, _ := d.def.URI().Filename()
-					return fname
-				case "efile":
-					fname, _ := d.def.URI().Filename()
-					qfile := strconv.Quote(fname)
-					return qfile[1 : len(qfile)-1]
-				case "euri":
-					quri := strconv.Quote(string(d.def.URI()))
-					return quri[1 : len(quri)-1]
-				case "line":
-					return fmt.Sprint(d.def.Start().Line())
-				case "col":
-					return fmt.Sprint(d.def.Start().Column())
-				case "offset":
-					return fmt.Sprint(d.def.Start().Offset())
-				case "eline":
-					return fmt.Sprint(d.def.End().Line())
-				case "ecol":
-					return fmt.Sprint(d.def.End().Column())
-				case "eoffset":
-					return fmt.Sprint(d.def.End().Offset())
-				default:
-					return name
-				}
-			})
-			if expect != got {
-				t.Errorf("definition %v\nexpected:\n%s\ngot:\n%s", args, expect, got)
-			}
-			if *verifyGuru {
-				moduleMode := e.File(e.Modules[0].Name, "go.mod") != ""
-				var guruArgs []string
-				runGuru := false
-				if !moduleMode {
-					for _, arg := range args {
-						switch {
-						case arg == "query":
-							// just ignore this one
-						case arg == "-json":
-							guruArgs = append(guruArgs, arg)
-						case arg == "-emulate=guru":
-							// if we don't see this one we should not run guru
-							runGuru = true
-						case strings.HasPrefix(arg, "-"):
-							// unknown flag, ignore it
-							break
-						default:
-							guruArgs = append(guruArgs, arg)
-						}
+		if !d.pattern.matches(got) {
+			t.Errorf("definition %v\nexpected:\n%s\ngot:\n%s", args, d.pattern, got)
+		}
+		if *verifyGuru {
+			moduleMode := e.File(e.Modules[0].Name, "go.mod") != ""
+			var guruArgs []string
+			runGuru := false
+			if !moduleMode {
+				for _, arg := range args {
+					switch {
+					case arg == "query":
+						// just ignore this one
+					case arg == "-json":
+						guruArgs = append(guruArgs, arg)
+					case arg == "-emulate=guru":
+						// if we don't see this one we should not run guru
+						runGuru = true
+					case strings.HasPrefix(arg, "-"):
+						// unknown flag, ignore it
+						break
+					default:
+						guruArgs = append(guruArgs, arg)
 					}
 				}
-				if runGuru {
-					cmd := exec.Command("guru", guruArgs...)
-					cmd.Env = e.Config.Env
-					out, err := cmd.CombinedOutput()
-					if err != nil {
-						t.Errorf("Could not run guru %v: %v\n%s", guruArgs, err, out)
-					} else {
-						guru := strings.TrimSpace(string(out))
-						if !strings.HasPrefix(expect, guru) {
-							t.Errorf("definition %v\nexpected:\n%s\nguru gave:\n%s", args, expect, guru)
-						}
+			}
+			if runGuru {
+				cmd := exec.Command("guru", guruArgs...)
+				cmd.Env = e.Config.Env
+				out, err := cmd.CombinedOutput()
+				if err != nil {
+					t.Errorf("Could not run guru %v: %v\n%s", guruArgs, err, out)
+				} else {
+					guru := strings.TrimSpace(string(out))
+					if !d.pattern.matches(guru) {
+						t.Errorf("definition %v\nexpected:\n%s\nguru gave:\n%s", args, d.pattern, guru)
 					}
 				}
 			}
@@ -187,3 +154,74 @@
 	}
 	//TODO: add command line type definition tests when it works
 }
+
+type pattern struct {
+	raw      string
+	expanded []string
+	matchAll bool
+}
+
+func newPattern(s string, def span.Span) pattern {
+	p := pattern{raw: s}
+	if s == "" {
+		p.expanded = []string{fmt.Sprintf("%v: ", def)}
+		return p
+	}
+	p.matchAll = strings.HasSuffix(s, "$$")
+	for _, fragment := range strings.Split(s, "$$") {
+		p.expanded = append(p.expanded, os.Expand(fragment, func(name string) string {
+			switch name {
+			case "file":
+				fname, _ := def.URI().Filename()
+				return fname
+			case "efile":
+				fname, _ := def.URI().Filename()
+				qfile := strconv.Quote(fname)
+				return qfile[1 : len(qfile)-1]
+			case "euri":
+				quri := strconv.Quote(string(def.URI()))
+				return quri[1 : len(quri)-1]
+			case "line":
+				return fmt.Sprint(def.Start().Line())
+			case "col":
+				return fmt.Sprint(def.Start().Column())
+			case "offset":
+				return fmt.Sprint(def.Start().Offset())
+			case "eline":
+				return fmt.Sprint(def.End().Line())
+			case "ecol":
+				return fmt.Sprint(def.End().Column())
+			case "eoffset":
+				return fmt.Sprint(def.End().Offset())
+			default:
+				return name
+			}
+		}))
+	}
+	return p
+}
+
+func (p pattern) String() string {
+	return strings.Join(p.expanded, "$$")
+}
+
+func (p pattern) matches(s string) bool {
+	if len(p.expanded) == 0 {
+		return false
+	}
+	if !strings.HasPrefix(s, p.expanded[0]) {
+		return false
+	}
+	remains := s[len(p.expanded[0]):]
+	for _, fragment := range p.expanded[1:] {
+		i := strings.Index(remains, fragment)
+		if i < 0 {
+			return false
+		}
+		remains = remains[i+len(fragment):]
+	}
+	if !p.matchAll {
+		return true
+	}
+	return len(remains) == 0
+}
diff --git a/internal/lsp/testdata/godef/a/d.go b/internal/lsp/testdata/godef/a/d.go
index d4516ae..0b1498e 100644
--- a/internal/lsp/testdata/godef/a/d.go
+++ b/internal/lsp/testdata/godef/a/d.go
@@ -1,5 +1,7 @@
 package a
 
+import "fmt"
+
 type Thing struct { //@Thing
 	Member string //@Member
 }
@@ -33,7 +35,7 @@
 definition(aVar, "-emulate=guru", Other, "$file:$line:$col: defined here as var Other")
 
 definition(aFunc, "", Things, "$file:$line:$col-$ecol: defined here as func Things(val []string) []Thing")
-definition(aFunc, "-emulate=guru", Things, "$file:$line:$col: defined here as func Things(val []string) []Thing")
+definition(aFunc, "-emulate=guru", Things, "$file:$line:$col: defined here as func Things")
 
 definition(aMethod, "", Method, "$file:$line:$col-$ecol: defined here as func (Thing).Method(i int) string")
 definition(aMethod, "-emulate=guru", Method, "$file:$line:$col: defined here as func (Thing).Method(i int) string")
@@ -63,6 +65,6 @@
 }`)
 definition(aStructType, "-json -emulate=guru", Thing, `{
 	"objpos": "$efile:$line:$col",
-	"desc": "type Thing"
+	"desc": "type Thing$$"
 }`)
 */
diff --git a/internal/lsp/testdata/godef/b/e.go b/internal/lsp/testdata/godef/b/e.go
index 7e0c417..6b2337a 100644
--- a/internal/lsp/testdata/godef/b/e.go
+++ b/internal/lsp/testdata/godef/b/e.go
@@ -15,14 +15,14 @@
 
 /*@
 definition(bStructType, "", Thing, "$file:$line:$col-$ecol: defined here as type a.Thing struct{Member string}")
-definition(bStructType, "-emulate=guru", Thing, "$file:$line:$col: defined here as type golang.org/x/tools/internal/lsp/godef/a.Thing")
+definition(bStructType, "-emulate=guru", Thing, "$file:$line:$col: defined here as type $$a.Thing")
 
 definition(bMember, "", Member, "$file:$line:$col-$ecol: defined here as field Member string")
 definition(bMember, "-emulate=guru", Member, "$file:$line:$col: defined here as field Member string")
 
 definition(bVar, "", Other, "$file:$line:$col-$ecol: defined here as var a.Other a.Thing")
-definition(bVar, "-emulate=guru", Other, "$file:$line:$col: defined here as var golang.org/x/tools/internal/lsp/godef/a.Other")
+definition(bVar, "-emulate=guru", Other, "$file:$line:$col: defined here as var $$a.Other")
 
 definition(bFunc, "", Things, "$file:$line:$col-$ecol: defined here as func a.Things(val []string) []a.Thing")
-definition(bFunc, "-emulate=guru", Things, "$file:$line:$col: defined here as func golang.org/x/tools/internal/lsp/godef/a.Things(val []string) []golang.org/x/tools/internal/lsp/godef/a.Thing")
+definition(bFunc, "-emulate=guru", Things, "$file:$line:$col: defined here as func $$a.Things")
 */