Skip to content

Commit 1ffc3a1

Browse files
committed
gopls/internal/test/marker: add defloc, to bind positions by definition
Address a long-standing TODO in the marker tests by adding a new value marker @defloc, which binds a name to the result of a definition request. Also - more documentation improvements - add support for formatting positions outside of the test archive - refactor location formatting, to better handle external locations Change-Id: I33e0d0e1a5d6d58cd83c62b8ad9b50e147077e2e Reviewed-on: https://go-review.googlesource.com/c/tools/+/630555 Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 442d6be commit 1ffc3a1

File tree

4 files changed

+127
-72
lines changed

4 files changed

+127
-72
lines changed

gopls/internal/test/marker/doc.go

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,41 @@ treatment by the test runner:
102102
103103
# Marker types
104104
105-
Markers are of two kinds. A few are "value markers" (e.g. @item), which are
106-
processed in a first pass and each computes a value that may be referred to
107-
by name later. Most are "action markers", which are processed in a second
108-
pass and take some action such as testing an LSP operation; they may refer
109-
to values computed by value markers.
105+
Markers are of two kinds: "value markers" and "action markers". Value markers
106+
are processed in a first pass, and define named values that may be referred to
107+
as arguments to action markers. For example, the @loc marker defines a named
108+
location that may be used wherever a location is expected. Value markers cannot
109+
refer to names defined by other value markers. Action markers are processed in
110+
a second pass and perform some action such as testing an LSP operation.
110111
111-
The following markers are supported within marker tests:
112+
Below, we list supported markers using function signatures, augmented with the
113+
named argument support name=value, as described above. The types referred to in
114+
the signatures below are described in the Argument conversion section.
115+
116+
Here is the list of supported value markers:
117+
118+
- loc(name, location): specifies the name for a location in the source. These
119+
locations may be referenced by other markers. Naturally, the location
120+
argument may be specified only as a string or regular expression in the
121+
first pass.
122+
123+
- defloc(name, location): performs a textDocument/defintiion request at the
124+
src location, and binds the result to the given name. This may be used to
125+
refer to positions in the standard library.
126+
127+
- hiloc(name, location, kind): defines a documentHighlight value of the
128+
given location and kind. Use its label in a @highlightall marker to
129+
indicate the expected result of a highlight query.
130+
131+
- item(name, details, kind): defines a completionItem with the provided
132+
fields. This information is not positional, and therefore @item markers
133+
may occur anywhere in the source. Use in conjunction with @complete,
134+
@snippet, or @rank.
135+
136+
TODO(rfindley): rethink whether floating @item annotations are the best
137+
way to specify completion results.
138+
139+
Here is the list of supported action markers:
112140
113141
- acceptcompletion(location, label, golden): specifies that accepting the
114142
completion candidate produced at the given location with provided label
@@ -177,10 +205,6 @@ The following markers are supported within marker tests:
177205
textDocument/highlight request at the given src location, which should
178206
highlight the provided dst locations and kinds.
179207
180-
- hiloc(label, location, kind): defines a documentHighlight value of the
181-
given location and kind. Use its label in a @highlightall marker to
182-
indicate the expected result of a highlight query.
183-
184208
- hover(src, dst location, sm stringMatcher): performs a textDocument/hover
185209
at the src location, and checks that the result is the dst location, with
186210
matching hover content.
@@ -198,17 +222,6 @@ The following markers are supported within marker tests:
198222
(These locations are the declarations of the functions enclosing
199223
the calls, not the calls themselves.)
200224
201-
- item(label, details, kind): defines a completionItem with the provided
202-
fields. This information is not positional, and therefore @item markers
203-
may occur anywhere in the source. Used in conjunction with @complete,
204-
@snippet, or @rank.
205-
206-
TODO(rfindley): rethink whether floating @item annotations are the best
207-
way to specify completion results.
208-
209-
- loc(name, location): specifies the name for a location in the source. These
210-
locations may be referenced by other markers.
211-
212225
- outgoingcalls(src location, want ...location): makes a
213226
callHierarchy/outgoingCalls query at the src location, and checks that
214227
the set of call.To locations matches want.
@@ -382,9 +395,6 @@ Note that -update does not cause missing @diag or @loc markers to be added.
382395
# TODO
383396
384397
- Rename the files .txtar.
385-
- Provide some means by which locations in the standard library
386-
(or builtin.go) can be named, so that, for example, we can we
387-
can assert that MyError implements the built-in error type.
388398
- Eliminate all *err markers, preferring named arguments.
389399
*/
390400
package marker

gopls/internal/test/marker/marker_test.go

Lines changed: 86 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ func Test(t *testing.T) {
112112
test := test
113113
t.Run(test.name, func(t *testing.T) {
114114
t.Parallel()
115+
115116
if test.skipReason != "" {
116117
t.Skip(test.skipReason)
117118
}
@@ -154,6 +155,7 @@ func Test(t *testing.T) {
154155
}
155156
testenv.NeedsTool(t, "cgo")
156157
}
158+
157159
config := fake.EditorConfig{
158160
Settings: test.settings,
159161
CapabilitiesJSON: test.capabilities,
@@ -177,6 +179,7 @@ func Test(t *testing.T) {
177179
diags: make(map[protocol.Location][]protocol.Diagnostic),
178180
extraNotes: make(map[protocol.DocumentURI]map[string][]*expect.Note),
179181
}
182+
180183
// TODO(rfindley): make it easier to clean up the integration test environment.
181184
defer run.env.Editor.Shutdown(context.Background()) // ignore error
182185
defer run.env.Sandbox.Close() // ignore error
@@ -346,7 +349,16 @@ func (mark marker) mapper() *protocol.Mapper {
346349
return mapper
347350
}
348351

349-
// errorf reports an error with a prefix indicating the position of the marker note.
352+
// error reports an error with a prefix indicating the position of the marker
353+
// note.
354+
func (mark marker) error(args ...any) {
355+
mark.T().Helper()
356+
msg := fmt.Sprint(args...)
357+
mark.T().Errorf("%s: %s", mark.run.fmtPos(mark.note.Pos), msg)
358+
}
359+
360+
// errorf reports a formatted error with a prefix indicating the position of
361+
// the marker note.
350362
//
351363
// It formats the error message using mark.sprintf.
352364
func (mark marker) errorf(format string, args ...any) {
@@ -402,7 +414,7 @@ func valueMarkerFunc(fn any) func(marker) {
402414
args := append([]any{mark}, mark.note.Args[1:]...)
403415
argValues, err := convertArgs(mark, ftype, args)
404416
if err != nil {
405-
mark.errorf("converting args: %v", err)
417+
mark.error(err)
406418
return
407419
}
408420
results := reflect.ValueOf(fn).Call(argValues)
@@ -445,7 +457,7 @@ func actionMarkerFunc(fn any, allowedNames ...string) func(marker) {
445457
args := append([]any{mark}, mark.note.Args...)
446458
argValues, err := convertArgs(mark, ftype, args)
447459
if err != nil {
448-
mark.errorf("converting args: %v", err)
460+
mark.error(err)
449461
return
450462
}
451463
reflect.ValueOf(fn).Call(argValues)
@@ -540,9 +552,10 @@ func is[T any](arg any) bool {
540552

541553
// Supported value marker functions. See [valueMarkerFunc] for more details.
542554
var valueMarkerFuncs = map[string]func(marker){
543-
"loc": valueMarkerFunc(locMarker),
544-
"item": valueMarkerFunc(completionItemMarker),
545-
"hiloc": valueMarkerFunc(highlightLocationMarker),
555+
"loc": valueMarkerFunc(locMarker),
556+
"item": valueMarkerFunc(completionItemMarker),
557+
"hiloc": valueMarkerFunc(highlightLocationMarker),
558+
"defloc": valueMarkerFunc(defLocMarker),
546559
}
547560

548561
// Supported action marker functions. See [actionMarkerFunc] for more details.
@@ -1029,22 +1042,10 @@ func (run *markerTestRun) fmtPos(pos token.Pos) string {
10291042
// archive-relative paths for files and including the line number in the full
10301043
// archive file.
10311044
func (run *markerTestRun) fmtLoc(loc protocol.Location) string {
1032-
formatted := run.fmtLocDetails(loc, true)
1033-
if formatted == "" {
1045+
if loc == (protocol.Location{}) {
10341046
run.env.T.Errorf("unable to find %s in test archive", loc)
10351047
return "<invalid location>"
10361048
}
1037-
return formatted
1038-
}
1039-
1040-
// See fmtLoc. If includeTxtPos is not set, the position in the full archive
1041-
// file is omitted.
1042-
//
1043-
// If the location cannot be found within the archive, fmtLocDetails returns "".
1044-
func (run *markerTestRun) fmtLocDetails(loc protocol.Location, includeTxtPos bool) string {
1045-
if loc == (protocol.Location{}) {
1046-
return ""
1047-
}
10481049
lines := bytes.Count(run.test.archive.Comment, []byte("\n"))
10491050
var name string
10501051
for _, f := range run.test.archive.Files {
@@ -1057,39 +1058,74 @@ func (run *markerTestRun) fmtLocDetails(loc protocol.Location, includeTxtPos boo
10571058
lines += bytes.Count(f.Data, []byte("\n"))
10581059
}
10591060
if name == "" {
1060-
return ""
1061-
}
1061+
// Fall back to formatting the "lsp" location.
1062+
// These will be in UTF-16, but we probably don't need to clarify that,
1063+
// since it will be implied by the file:// URI format.
1064+
return summarizeLoc(string(loc.URI),
1065+
int(loc.Range.Start.Line), int(loc.Range.Start.Character),
1066+
int(loc.Range.End.Line), int(loc.Range.End.Character))
1067+
}
1068+
name, startLine, startCol, endLine, endCol := run.mapLocation(loc)
1069+
innerSpan := summarizeLoc(name, startLine, startCol, endLine, endCol)
1070+
outerSpan := summarizeLoc(run.test.name, lines+startLine, startCol, lines+endLine, endCol)
1071+
return fmt.Sprintf("%s (%s)", innerSpan, outerSpan)
1072+
}
1073+
1074+
// mapLocation returns the relative path and utf8 span of the corresponding
1075+
// location, which must be a valid location in an archive file.
1076+
func (run *markerTestRun) mapLocation(loc protocol.Location) (name string, startLine, startCol, endLine, endCol int) {
1077+
// Note: Editor.Mapper fails if loc.URI is not open, but we always open all
1078+
// archive files, so this is probably OK.
1079+
//
1080+
// In the future, we may want to have the editor read contents from disk if
1081+
// the URI is not open.
1082+
name = run.env.Sandbox.Workdir.URIToPath(loc.URI)
10621083
m, err := run.env.Editor.Mapper(name)
10631084
if err != nil {
10641085
run.env.T.Errorf("internal error: %v", err)
1065-
return "<invalid location>"
1086+
return
10661087
}
10671088
start, end, err := m.RangeOffsets(loc.Range)
10681089
if err != nil {
10691090
run.env.T.Errorf("error formatting location %s: %v", loc, err)
1091+
return
1092+
}
1093+
startLine, startCol = m.OffsetLineCol8(start)
1094+
endLine, endCol = m.OffsetLineCol8(end)
1095+
return name, startLine, startCol, endLine, endCol
1096+
}
1097+
1098+
// fmtLocForGolden is like fmtLoc, but chooses more succinct and stable
1099+
// formatting, such as would be used for formatting locations in Golden
1100+
// content.
1101+
func (run *markerTestRun) fmtLocForGolden(loc protocol.Location) string {
1102+
if loc == (protocol.Location{}) {
10701103
return "<invalid location>"
10711104
}
1072-
var (
1073-
startLine, startCol8 = m.OffsetLineCol8(start)
1074-
endLine, endCol8 = m.OffsetLineCol8(end)
1075-
)
1076-
innerSpan := fmt.Sprintf("%d:%d", startLine, startCol8) // relative to the embedded file
1077-
outerSpan := fmt.Sprintf("%d:%d", lines+startLine, startCol8) // relative to the archive file
1078-
if start != end {
1079-
if endLine == startLine {
1080-
innerSpan += fmt.Sprintf("-%d", endCol8)
1081-
outerSpan += fmt.Sprintf("-%d", endCol8)
1082-
} else {
1083-
innerSpan += fmt.Sprintf("-%d:%d", endLine, endCol8)
1084-
outerSpan += fmt.Sprintf("-%d:%d", lines+endLine, endCol8)
1085-
}
1105+
name := run.env.Sandbox.Workdir.URIToPath(loc.URI)
1106+
// Note: we check IsAbs on filepaths rather than the slash-ified name for
1107+
// accurate handling of windows drive letters.
1108+
if filepath.IsAbs(filepath.FromSlash(name)) {
1109+
// Don't format any position information in this case, since it will be
1110+
// volatile.
1111+
return "<external>"
10861112
}
1113+
return summarizeLoc(run.mapLocation(loc))
1114+
}
10871115

1088-
if includeTxtPos {
1089-
return fmt.Sprintf("%s:%s (%s:%s)", name, innerSpan, run.test.name, outerSpan)
1090-
} else {
1091-
return fmt.Sprintf("%s:%s", name, innerSpan)
1116+
// summarizeLoc formats a summary of the given location, in the form
1117+
//
1118+
// <name>:<startLine>:<startCol>[-[<endLine>:]endCol]
1119+
func summarizeLoc(name string, startLine, startCol, endLine, endCol int) string {
1120+
span := fmt.Sprintf("%s:%d:%d", name, startLine, startCol)
1121+
if startLine != endLine || startCol != endCol {
1122+
span += "-"
1123+
if endLine != startLine {
1124+
span += fmt.Sprintf("%d:", endLine)
1125+
}
1126+
span += fmt.Sprintf("%d", endCol)
10921127
}
1128+
return span
10931129
}
10941130

10951131
// ---- converters ----
@@ -1144,7 +1180,7 @@ func convert(mark marker, arg any, paramType reflect.Type) (any, error) {
11441180
if converter, ok := customConverters[paramType]; ok {
11451181
arg2, err := converter(mark, arg)
11461182
if err != nil {
1147-
return nil, fmt.Errorf("converting for input type %T to %v: %v", arg, paramType, err)
1183+
return nil, err
11481184
}
11491185
arg = arg2
11501186
}
@@ -1763,10 +1799,15 @@ func hoverErrMarker(mark marker, src protocol.Location, em stringMatcher) {
17631799
em.checkErr(mark, err)
17641800
}
17651801

1766-
// locMarker implements the @loc marker. It is executed before other
1767-
// markers, so that locations are available.
1802+
// locMarker implements the @loc marker.
17681803
func locMarker(mark marker, loc protocol.Location) protocol.Location { return loc }
17691804

1805+
// defLocMarker implements the @defloc marker, which binds a location to the
1806+
// (first) result of a jump-to-definition request.
1807+
func defLocMarker(mark marker, loc protocol.Location) protocol.Location {
1808+
return mark.run.env.GoToDefinition(loc)
1809+
}
1810+
17701811
// diagMarker implements the @diag marker. It eliminates diagnostics from
17711812
// the observed set in mark.test.
17721813
func diagMarker(mark marker, loc protocol.Location, re *regexp.Regexp) {
@@ -2101,7 +2142,7 @@ func documentLinkMarker(mark marker, g *Golden) {
21012142
continue
21022143
}
21032144
loc := protocol.Location{URI: mark.uri(), Range: l.Range}
2104-
fmt.Fprintln(&b, mark.run.fmtLocDetails(loc, false), *l.Target)
2145+
fmt.Fprintln(&b, mark.run.fmtLocForGolden(loc), *l.Target)
21052146
}
21062147

21072148
compareGolden(mark, b.Bytes(), g)
@@ -2554,9 +2595,7 @@ func workspaceSymbolMarker(mark marker, query string, golden *Golden) {
25542595
for _, s := range gotSymbols {
25552596
// Omit the txtar position of the symbol location; otherwise edits to the
25562597
// txtar archive lead to unexpected failures.
2557-
loc := mark.run.fmtLocDetails(s.Location, false)
2558-
// TODO(rfindley): can we do better here, by detecting if the location is
2559-
// relative to GOROOT?
2598+
loc := mark.run.fmtLocForGolden(s.Location)
25602599
if loc == "" {
25612600
loc = "<unknown>"
25622601
}

gopls/internal/test/marker/testdata/implementation/basic.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ type embedsImpP struct { //@loc(embedsImpP, "embedsImpP")
4343
ImpP //@implementation("ImpP", Laugher, OtherLaugher)
4444
}
4545

46+
var _ error //@defloc(StdError, "error")
47+
48+
type MyError struct {} //@implementation("MyError", StdError)
49+
50+
func (MyError) Error() string { return "bah" }
51+
4652
-- other/other.go --
4753
package other
4854

gopls/internal/test/marker/testdata/workspacesymbol/allscope.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ func Println(s string) {
2727
}
2828
-- @println --
2929
fmt/fmt.go:5:6-13 mod.test/symbols/fmt.Println Function
30-
<unknown> fmt.Println Function
30+
<external> fmt.Println Function

0 commit comments

Comments
 (0)