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