1 // Copyright 2019 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package cmdtest contains the test suite for the command line behavior of gopls.
20 "golang.org/x/tools/internal/jsonrpc2/servertest"
21 "golang.org/x/tools/internal/lsp/cache"
22 "golang.org/x/tools/internal/lsp/cmd"
23 "golang.org/x/tools/internal/lsp/debug"
24 "golang.org/x/tools/internal/lsp/lsprpc"
25 "golang.org/x/tools/internal/lsp/protocol"
26 "golang.org/x/tools/internal/lsp/source"
27 "golang.org/x/tools/internal/lsp/tests"
28 "golang.org/x/tools/internal/span"
29 "golang.org/x/tools/internal/tool"
35 options func(*source.Options)
36 normalizers []normalizer
40 type normalizer struct {
47 func TestCommandLine(t *testing.T, testdata string, options func(*source.Options)) {
48 // On Android, the testdata directory is not copied to the runner.
49 if stat, err := os.Stat(testdata); err != nil || !stat.IsDir() {
50 t.Skip("testdata directory not present")
52 tests.RunTests(t, testdata, false, func(t *testing.T, datum *tests.Data) {
53 ctx := tests.Context(t)
54 ts := NewTestServer(ctx, options)
55 tests.Run(t, NewRunner(datum, ctx, ts.Addr, options), datum)
56 cmd.CloseTestConnections(ctx)
60 func NewTestServer(ctx context.Context, options func(*source.Options)) *servertest.TCPServer {
61 ctx = debug.WithInstance(ctx, "", "")
62 cache := cache.New(ctx, options)
63 ss := lsprpc.NewStreamServer(cache, false)
64 return servertest.NewTCPServer(ctx, ss, nil)
67 func NewRunner(data *tests.Data, ctx context.Context, remote string, options func(*source.Options)) *runner {
72 normalizers: make([]normalizer, 0, len(data.Exported.Modules)),
75 // build the path normalizing patterns
76 for _, m := range data.Exported.Modules {
77 for fragment := range m.Files {
79 path: data.Exported.File(m.Name, fragment),
82 if n.slashed = filepath.ToSlash(n.path); n.slashed == n.path {
85 quoted := strconv.Quote(n.path)
86 if n.escaped = quoted[1 : len(quoted)-1]; n.escaped == n.path {
89 r.normalizers = append(r.normalizers, n)
95 func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) {
96 //TODO: add command line completions tests when it works
99 func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
100 //TODO: add command line completions tests when it works
103 func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) {
104 //TODO: add command line completions tests when it works
107 func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
108 //TODO: add command line completions tests when it works
111 func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
112 //TODO: add command line completions tests when it works
115 func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
116 //TODO: add command line completions tests when it works
119 func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
120 //TODO: add command line completions tests when it works
123 func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
124 //TODO: add command line completions tests when it works
127 func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span) {
128 //TODO: function extraction not supported on command line
131 func (r *runner) runGoplsCmd(t testing.TB, args ...string) (string, string) {
132 rStdout, wStdout, err := os.Pipe()
136 oldStdout := os.Stdout
137 rStderr, wStderr, err := os.Pipe()
141 oldStderr := os.Stderr
142 stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
143 var wg sync.WaitGroup
146 io.Copy(stdout, rStdout)
150 io.Copy(stderr, rStderr)
153 os.Stdout, os.Stderr = wStdout, wStderr
154 app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env, r.options)
156 err = tool.Run(tests.Context(t),
158 append([]string{fmt.Sprintf("-remote=internal@%s", remote)}, args...))
160 fmt.Fprint(os.Stderr, err)
165 os.Stdout, os.Stderr = oldStdout, oldStderr
168 return stdout.String(), stderr.String()
171 // NormalizeGoplsCmd runs the gopls command and normalizes its output.
172 func (r *runner) NormalizeGoplsCmd(t testing.TB, args ...string) (string, string) {
173 stdout, stderr := r.runGoplsCmd(t, args...)
174 return r.Normalize(stdout), r.Normalize(stderr)
177 // NormalizePrefix normalizes a single path at the front of the input string.
178 func (r *runner) NormalizePrefix(s string) string {
179 for _, n := range r.normalizers {
180 if t := strings.TrimPrefix(s, n.path); t != s {
181 return n.fragment + t
183 if t := strings.TrimPrefix(s, n.slashed); t != s {
184 return n.fragment + t
186 if t := strings.TrimPrefix(s, n.escaped); t != s {
187 return n.fragment + t
193 // Normalize replaces all paths present in s with just the fragment portion
194 // this is used to make golden files not depend on the temporary paths of the files
195 func (r *runner) Normalize(s string) string {
201 match := make([]entry, 0, len(r.normalizers))
202 // collect the initial state of all the matchers
203 for _, n := range r.normalizers {
204 index := strings.Index(s, n.path)
206 match = append(match, entry{n.path, index, n.fragment})
209 index := strings.Index(s, n.slashed)
211 match = append(match, entry{n.slashed, index, n.fragment})
215 index := strings.Index(s, n.escaped)
217 match = append(match, entry{n.escaped, index, n.fragment})
221 // result should be the same or shorter than the input
222 buf := bytes.NewBuffer(make([]byte, 0, len(s)))
225 // find the nearest path match to the start of the buffer
228 for i, c := range match {
229 if c.index >= 0 && nearest > c.index {
234 // if there are no matches, we copy the rest of the string and are done
236 buf.WriteString(s[last:])
241 // copy up to the start of the match
242 buf.WriteString(s[last:n.index])
243 // skip over the filename
244 last = n.index + len(n.path)
245 // add in the fragment instead
246 buf.WriteString(n.fragment)
247 // see what the next match for this path is
248 n.index = strings.Index(s[last:], n.path)