.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools / gopls@v0.6.9 / internal / regtest / runner.go
1 // Copyright 2020 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.
4
5 package regtest
6
7 import (
8         "bytes"
9         "context"
10         "fmt"
11         "io"
12         "io/ioutil"
13         "net"
14         "os"
15         "path/filepath"
16         "runtime/pprof"
17         "strings"
18         "sync"
19         "testing"
20         "time"
21
22         exec "golang.org/x/sys/execabs"
23
24         "golang.org/x/tools/gopls/internal/hooks"
25         "golang.org/x/tools/internal/jsonrpc2"
26         "golang.org/x/tools/internal/jsonrpc2/servertest"
27         "golang.org/x/tools/internal/lsp/cache"
28         "golang.org/x/tools/internal/lsp/debug"
29         "golang.org/x/tools/internal/lsp/fake"
30         "golang.org/x/tools/internal/lsp/lsprpc"
31         "golang.org/x/tools/internal/lsp/protocol"
32         "golang.org/x/tools/internal/lsp/source"
33 )
34
35 // Mode is a bitmask that defines for which execution modes a test should run.
36 type Mode int
37
38 const (
39         // Singleton mode uses a separate in-process gopls instance for each test,
40         // and communicates over pipes to mimic the gopls sidecar execution mode,
41         // which communicates over stdin/stderr.
42         Singleton Mode = 1 << iota
43         // Forwarded forwards connections to a shared in-process gopls instance.
44         Forwarded
45         // SeparateProcess forwards connection to a shared separate gopls process.
46         SeparateProcess
47         // Experimental enables all of the experimental configurations that are
48         // being developed. Currently, it enables the workspace module.
49         Experimental
50 )
51
52 // A Runner runs tests in gopls execution environments, as specified by its
53 // modes. For modes that share state (for example, a shared cache or common
54 // remote), any tests that execute on the same Runner will share the same
55 // state.
56 type Runner struct {
57         DefaultModes             Mode
58         Timeout                  time.Duration
59         GoplsPath                string
60         PrintGoroutinesOnFailure bool
61         TempDir                  string
62         SkipCleanup              bool
63
64         mu        sync.Mutex
65         ts        *servertest.TCPServer
66         socketDir string
67         // closers is a queue of clean-up functions to run at the end of the entire
68         // test suite.
69         closers []io.Closer
70 }
71
72 type runConfig struct {
73         editor    fake.EditorConfig
74         sandbox   fake.SandboxConfig
75         modes     Mode
76         timeout   time.Duration
77         debugAddr string
78         skipLogs  bool
79         skipHooks bool
80 }
81
82 func (r *Runner) defaultConfig() *runConfig {
83         return &runConfig{
84                 modes:   r.DefaultModes,
85                 timeout: r.Timeout,
86         }
87 }
88
89 // A RunOption augments the behavior of the test runner.
90 type RunOption interface {
91         set(*runConfig)
92 }
93
94 type optionSetter func(*runConfig)
95
96 func (f optionSetter) set(opts *runConfig) {
97         f(opts)
98 }
99
100 // Timeout configures a custom timeout for this test run.
101 func Timeout(d time.Duration) RunOption {
102         return optionSetter(func(opts *runConfig) {
103                 opts.timeout = d
104         })
105 }
106
107 // ProxyFiles configures a file proxy using the given txtar-encoded string.
108 func ProxyFiles(txt string) RunOption {
109         return optionSetter(func(opts *runConfig) {
110                 opts.sandbox.ProxyFiles = txt
111         })
112 }
113
114 // Modes configures the execution modes that the test should run in.
115 func Modes(modes Mode) RunOption {
116         return optionSetter(func(opts *runConfig) {
117                 opts.modes = modes
118         })
119 }
120
121 func SendPID() RunOption {
122         return optionSetter(func(opts *runConfig) {
123                 opts.editor.SendPID = true
124         })
125 }
126
127 // EditorConfig is a RunOption option that configured the regtest editor.
128 type EditorConfig fake.EditorConfig
129
130 func (c EditorConfig) set(opts *runConfig) {
131         opts.editor = fake.EditorConfig(c)
132 }
133
134 // WorkspaceFolders configures the workdir-relative workspace folders to send
135 // to the LSP server. By default the editor sends a single workspace folder
136 // corresponding to the workdir root. To explicitly configure no workspace
137 // folders, use WorkspaceFolders with no arguments.
138 func WorkspaceFolders(relFolders ...string) RunOption {
139         if len(relFolders) == 0 {
140                 // Use an empty non-nil slice to signal explicitly no folders.
141                 relFolders = []string{}
142         }
143         return optionSetter(func(opts *runConfig) {
144                 opts.editor.WorkspaceFolders = relFolders
145         })
146 }
147
148 // InGOPATH configures the workspace working directory to be GOPATH, rather
149 // than a separate working directory for use with modules.
150 func InGOPATH() RunOption {
151         return optionSetter(func(opts *runConfig) {
152                 opts.sandbox.InGoPath = true
153         })
154 }
155
156 // DebugAddress configures a debug server bound to addr. This option is
157 // currently only supported when executing in Singleton mode. It is intended to
158 // be used for long-running stress tests.
159 func DebugAddress(addr string) RunOption {
160         return optionSetter(func(opts *runConfig) {
161                 opts.debugAddr = addr
162         })
163 }
164
165 // SkipLogs skips the buffering of logs during test execution. It is intended
166 // for long-running stress tests.
167 func SkipLogs() RunOption {
168         return optionSetter(func(opts *runConfig) {
169                 opts.skipLogs = true
170         })
171 }
172
173 // InExistingDir runs the test in a pre-existing directory. If set, no initial
174 // files may be passed to the runner. It is intended for long-running stress
175 // tests.
176 func InExistingDir(dir string) RunOption {
177         return optionSetter(func(opts *runConfig) {
178                 opts.sandbox.Workdir = dir
179         })
180 }
181
182 // SkipHooks allows for disabling the test runner's client hooks that are used
183 // for instrumenting expectations (tracking diagnostics, logs, work done,
184 // etc.). It is intended for performance-sensitive stress tests or benchmarks.
185 func SkipHooks(skip bool) RunOption {
186         return optionSetter(func(opts *runConfig) {
187                 opts.skipHooks = skip
188         })
189 }
190
191 // GOPROXY configures the test environment to have an explicit proxy value.
192 // This is intended for stress tests -- to ensure their isolation, regtests
193 // should instead use WithProxyFiles.
194 func GOPROXY(goproxy string) RunOption {
195         return optionSetter(func(opts *runConfig) {
196                 opts.sandbox.GOPROXY = goproxy
197         })
198 }
199
200 // LimitWorkspaceScope sets the LimitWorkspaceScope configuration.
201 func LimitWorkspaceScope() RunOption {
202         return optionSetter(func(opts *runConfig) {
203                 opts.editor.LimitWorkspaceScope = true
204         })
205 }
206
207 type TestFunc func(t *testing.T, env *Env)
208
209 // Run executes the test function in the default configured gopls execution
210 // modes. For each a test run, a new workspace is created containing the
211 // un-txtared files specified by filedata.
212 func (r *Runner) Run(t *testing.T, files string, test TestFunc, opts ...RunOption) {
213         t.Helper()
214         checkBuilder(t)
215
216         tests := []struct {
217                 name      string
218                 mode      Mode
219                 getServer func(context.Context, *testing.T) jsonrpc2.StreamServer
220         }{
221                 {"singleton", Singleton, singletonServer},
222                 {"forwarded", Forwarded, r.forwardedServer},
223                 {"separate_process", SeparateProcess, r.separateProcessServer},
224                 {"experimental_workspace_module", Experimental, experimentalWorkspaceModule},
225         }
226
227         for _, tc := range tests {
228                 tc := tc
229                 config := r.defaultConfig()
230                 for _, opt := range opts {
231                         opt.set(config)
232                 }
233                 if config.modes&tc.mode == 0 {
234                         continue
235                 }
236                 if config.debugAddr != "" && tc.mode != Singleton {
237                         // Debugging is useful for running stress tests, but since the daemon has
238                         // likely already been started, it would be too late to debug.
239                         t.Fatalf("debugging regtest servers only works in Singleton mode, "+
240                                 "got debug addr %q and mode %v", config.debugAddr, tc.mode)
241                 }
242
243                 t.Run(tc.name, func(t *testing.T) {
244                         ctx, cancel := context.WithTimeout(context.Background(), config.timeout)
245                         defer cancel()
246                         ctx = debug.WithInstance(ctx, "", "off")
247                         if config.debugAddr != "" {
248                                 di := debug.GetInstance(ctx)
249                                 di.DebugAddress = config.debugAddr
250                                 di.Serve(ctx)
251                                 di.MonitorMemory(ctx)
252                         }
253
254                         rootDir := filepath.Join(r.TempDir, filepath.FromSlash(t.Name()))
255                         if err := os.MkdirAll(rootDir, 0755); err != nil {
256                                 t.Fatal(err)
257                         }
258                         config.sandbox.Files = files
259                         config.sandbox.RootDir = rootDir
260                         sandbox, err := fake.NewSandbox(&config.sandbox)
261                         if err != nil {
262                                 t.Fatal(err)
263                         }
264                         // Deferring the closure of ws until the end of the entire test suite
265                         // has, in testing, given the LSP server time to properly shutdown and
266                         // release any file locks held in workspace, which is a problem on
267                         // Windows. This may still be flaky however, and in the future we need a
268                         // better solution to ensure that all Go processes started by gopls have
269                         // exited before we clean up.
270                         r.AddCloser(sandbox)
271                         ss := tc.getServer(ctx, t)
272                         framer := jsonrpc2.NewRawStream
273                         ls := &loggingFramer{}
274                         if !config.skipLogs {
275                                 framer = ls.framer(jsonrpc2.NewRawStream)
276                         }
277                         ts := servertest.NewPipeServer(ctx, ss, framer)
278                         env := NewEnv(ctx, t, sandbox, ts, config.editor, !config.skipHooks)
279                         defer func() {
280                                 if t.Failed() && r.PrintGoroutinesOnFailure {
281                                         pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
282                                 }
283                                 if t.Failed() || testing.Verbose() {
284                                         ls.printBuffers(t.Name(), os.Stderr)
285                                 }
286                                 env.CloseEditor()
287                         }()
288                         // Always await the initial workspace load.
289                         env.Await(InitialWorkspaceLoad)
290                         test(t, env)
291                 })
292         }
293 }
294
295 // longBuilders maps builders that are skipped when -short is set to a
296 // (possibly empty) justification.
297 var longBuilders = map[string]string{
298         "openbsd-amd64-64":        "golang.org/issues/42789",
299         "openbsd-386-64":          "golang.org/issues/42789",
300         "openbsd-386-68":          "golang.org/issues/42789",
301         "openbsd-amd64-68":        "golang.org/issues/42789",
302         "darwin-amd64-10_12":      "",
303         "freebsd-amd64-race":      "",
304         "illumos-amd64":           "",
305         "netbsd-arm-bsiegert":     "",
306         "solaris-amd64-oraclerel": "",
307         "windows-arm-zx2c4":       "",
308 }
309
310 func checkBuilder(t *testing.T) {
311         t.Helper()
312         builder := os.Getenv("GO_BUILDER_NAME")
313         if reason, ok := longBuilders[builder]; ok && testing.Short() {
314                 if reason != "" {
315                         t.Skipf("Skipping %s with -short due to %s", builder, reason)
316                 } else {
317                         t.Skipf("Skipping %s with -short", builder)
318                 }
319         }
320 }
321
322 type loggingFramer struct {
323         mu  sync.Mutex
324         buf *safeBuffer
325 }
326
327 // safeBuffer is a threadsafe buffer for logs.
328 type safeBuffer struct {
329         mu  sync.Mutex
330         buf bytes.Buffer
331 }
332
333 func (b *safeBuffer) Write(p []byte) (int, error) {
334         b.mu.Lock()
335         defer b.mu.Unlock()
336         return b.buf.Write(p)
337 }
338
339 func (s *loggingFramer) framer(f jsonrpc2.Framer) jsonrpc2.Framer {
340         return func(nc net.Conn) jsonrpc2.Stream {
341                 s.mu.Lock()
342                 framed := false
343                 if s.buf == nil {
344                         s.buf = &safeBuffer{buf: bytes.Buffer{}}
345                         framed = true
346                 }
347                 s.mu.Unlock()
348                 stream := f(nc)
349                 if framed {
350                         return protocol.LoggingStream(stream, s.buf)
351                 }
352                 return stream
353         }
354 }
355
356 func (s *loggingFramer) printBuffers(testname string, w io.Writer) {
357         s.mu.Lock()
358         defer s.mu.Unlock()
359
360         if s.buf == nil {
361                 return
362         }
363         fmt.Fprintf(os.Stderr, "#### Start Gopls Test Logs for %q\n", testname)
364         s.buf.mu.Lock()
365         io.Copy(w, &s.buf.buf)
366         s.buf.mu.Unlock()
367         fmt.Fprintf(os.Stderr, "#### End Gopls Test Logs for %q\n", testname)
368 }
369
370 func singletonServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
371         return lsprpc.NewStreamServer(cache.New(ctx, hooks.Options), false)
372 }
373
374 func experimentalWorkspaceModule(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
375         options := func(o *source.Options) {
376                 hooks.Options(o)
377                 o.ExperimentalWorkspaceModule = true
378         }
379         return lsprpc.NewStreamServer(cache.New(ctx, options), false)
380 }
381
382 func (r *Runner) forwardedServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
383         ts := r.getTestServer()
384         return lsprpc.NewForwarder("tcp", ts.Addr)
385 }
386
387 // getTestServer gets the shared test server instance to connect to, or creates
388 // one if it doesn't exist.
389 func (r *Runner) getTestServer() *servertest.TCPServer {
390         r.mu.Lock()
391         defer r.mu.Unlock()
392         if r.ts == nil {
393                 ctx := context.Background()
394                 ctx = debug.WithInstance(ctx, "", "off")
395                 ss := lsprpc.NewStreamServer(cache.New(ctx, hooks.Options), false)
396                 r.ts = servertest.NewTCPServer(ctx, ss, nil)
397         }
398         return r.ts
399 }
400
401 func (r *Runner) separateProcessServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
402         // TODO(rfindley): can we use the autostart behavior here, instead of
403         // pre-starting the remote?
404         socket := r.getRemoteSocket(t)
405         return lsprpc.NewForwarder("unix", socket)
406 }
407
408 // runTestAsGoplsEnvvar triggers TestMain to run gopls instead of running
409 // tests. It's a trick to allow tests to find a binary to use to start a gopls
410 // subprocess.
411 const runTestAsGoplsEnvvar = "_GOPLS_TEST_BINARY_RUN_AS_GOPLS"
412
413 func (r *Runner) getRemoteSocket(t *testing.T) string {
414         t.Helper()
415         r.mu.Lock()
416         defer r.mu.Unlock()
417         const daemonFile = "gopls-test-daemon"
418         if r.socketDir != "" {
419                 return filepath.Join(r.socketDir, daemonFile)
420         }
421
422         if r.GoplsPath == "" {
423                 t.Fatal("cannot run tests with a separate process unless a path to a gopls binary is configured")
424         }
425         var err error
426         r.socketDir, err = ioutil.TempDir(r.TempDir, "gopls-regtest-socket")
427         if err != nil {
428                 t.Fatalf("creating tempdir: %v", err)
429         }
430         socket := filepath.Join(r.socketDir, daemonFile)
431         args := []string{"serve", "-listen", "unix;" + socket, "-listen.timeout", "10s"}
432         cmd := exec.Command(r.GoplsPath, args...)
433         cmd.Env = append(os.Environ(), runTestAsGoplsEnvvar+"=true")
434         var stderr bytes.Buffer
435         cmd.Stderr = &stderr
436         go func() {
437                 if err := cmd.Run(); err != nil {
438                         panic(fmt.Sprintf("error running external gopls: %v\nstderr:\n%s", err, stderr.String()))
439                 }
440         }()
441         return socket
442 }
443
444 // AddCloser schedules a closer to be closed at the end of the test run. This
445 // is useful for Windows in particular, as
446 func (r *Runner) AddCloser(closer io.Closer) {
447         r.mu.Lock()
448         defer r.mu.Unlock()
449         r.closers = append(r.closers, closer)
450 }
451
452 // Close cleans up resource that have been allocated to this workspace.
453 func (r *Runner) Close() error {
454         r.mu.Lock()
455         defer r.mu.Unlock()
456
457         var errmsgs []string
458         if r.ts != nil {
459                 if err := r.ts.Close(); err != nil {
460                         errmsgs = append(errmsgs, err.Error())
461                 }
462         }
463         if r.socketDir != "" {
464                 if err := os.RemoveAll(r.socketDir); err != nil {
465                         errmsgs = append(errmsgs, err.Error())
466                 }
467         }
468         if !r.SkipCleanup {
469                 for _, closer := range r.closers {
470                         if err := closer.Close(); err != nil {
471                                 errmsgs = append(errmsgs, err.Error())
472                         }
473                 }
474                 if err := os.RemoveAll(r.TempDir); err != nil {
475                         errmsgs = append(errmsgs, err.Error())
476                 }
477         }
478         if len(errmsgs) > 0 {
479                 return fmt.Errorf("errors closing the test runner:\n\t%s", strings.Join(errmsgs, "\n\t"))
480         }
481         return nil
482 }