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.
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"
34 // Mode is a bitmask that defines for which execution modes a test should run.
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
43 // Forwarded forwards connections to a shared in-process gopls instance.
45 // SeparateProcess forwards connection to a shared separate gopls process.
47 // Experimental enables all of the experimental configurations that are
48 // being developed. Currently, it enables the workspace module.
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
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
67 PrintGoroutinesOnFailure bool
72 ts *servertest.TCPServer
74 // closers is a queue of clean-up functions to run at the end of the entire
79 type runConfig struct {
80 editor fake.EditorConfig
81 sandbox fake.SandboxConfig
89 func (r *Runner) defaultConfig() *runConfig {
91 modes: r.DefaultModes,
96 // A RunOption augments the behavior of the test runner.
97 type RunOption interface {
101 type optionSetter func(*runConfig)
103 func (f optionSetter) set(opts *runConfig) {
107 // WithTimeout configures a custom timeout for this test run.
108 func WithTimeout(d time.Duration) RunOption {
109 return optionSetter(func(opts *runConfig) {
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
121 // WithModes configures the execution modes that the test should run in.
122 func WithModes(modes Mode) RunOption {
123 return optionSetter(func(opts *runConfig) {
128 // EditorConfig is a RunOption option that configured the regtest editor.
129 type EditorConfig fake.EditorConfig
131 func (c EditorConfig) set(opts *runConfig) {
132 opts.editor = fake.EditorConfig(c)
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
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
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
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
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) {
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
182 func InExistingDir(dir string) RunOption {
183 return optionSetter(func(opts *runConfig) {
184 opts.sandbox.Workdir = dir
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
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
206 // WithLimitWorkspaceScope sets the LimitWorkspaceScope configuration.
207 func WithLimitWorkspaceScope() RunOption {
208 return optionSetter(func(opts *runConfig) {
209 opts.editor.LimitWorkspaceScope = true
213 type TestFunc func(t *testing.T, env *Env)
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) {
224 getServer func(context.Context, *testing.T) jsonrpc2.StreamServer
226 {"singleton", Singleton, singletonServer},
227 {"forwarded", Forwarded, r.forwardedServer},
228 {"separate_process", SeparateProcess, r.separateProcessServer},
229 {"experimental_workspace_module", Experimental, experimentalWorkspaceModule},
232 for _, tc := range tests {
234 config := r.defaultConfig()
235 for _, opt := range opts {
238 if config.modes&tc.mode == 0 {
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)
248 t.Run(tc.name, func(t *testing.T) {
249 ctx, cancel := context.WithTimeout(context.Background(), config.timeout)
251 ctx = debug.WithInstance(ctx, "", "off")
252 if config.debugAddr != "" {
253 di := debug.GetInstance(ctx)
254 di.DebugAddress = config.debugAddr
256 di.MonitorMemory(ctx)
259 tempDir := filepath.Join(r.TempDir, filepath.FromSlash(t.Name()))
260 if err := os.MkdirAll(tempDir, 0755); err != nil {
263 config.sandbox.Files = files
264 config.sandbox.RootDir = tempDir
265 sandbox, err := fake.NewSandbox(&config.sandbox)
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.
276 ss := tc.getServer(ctx, t)
277 framer := jsonrpc2.NewRawStream
278 ls := &loggingFramer{}
279 if !config.skipLogs {
280 framer = ls.framer(jsonrpc2.NewRawStream)
282 ts := servertest.NewPipeServer(ctx, ss, framer)
283 env := NewEnv(ctx, t, sandbox, ts, config.editor, !config.skipHooks)
285 if t.Failed() && r.PrintGoroutinesOnFailure {
286 pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
288 if t.Failed() || testing.Verbose() {
289 ls.printBuffers(t.Name(), os.Stderr)
293 // Always await the initial workspace load.
294 env.Await(InitialWorkspaceLoad)
300 type loggingFramer struct {
302 buffers []*safeBuffer
305 // safeBuffer is a threadsafe buffer for logs.
306 type safeBuffer struct {
311 func (b *safeBuffer) Write(p []byte) (int, error) {
314 return b.buf.Write(p)
317 func (s *loggingFramer) framer(f jsonrpc2.Framer) jsonrpc2.Framer {
318 return func(nc net.Conn) jsonrpc2.Stream {
320 buf := &safeBuffer{buf: bytes.Buffer{}}
321 s.buffers = append(s.buffers, buf)
324 return protocol.LoggingStream(stream, buf)
328 func (s *loggingFramer) printBuffers(testname string, w io.Writer) {
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)
337 fmt.Fprintf(os.Stderr, "#### End Gopls Test Logs %d of %d for %q\n", i+1, len(s.buffers), testname)
341 func singletonServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
342 return lsprpc.NewStreamServer(cache.New(ctx, hooks.Options), false)
345 func experimentalWorkspaceModule(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
346 options := func(o *source.Options) {
348 o.ExperimentalWorkspaceModule = true
350 return lsprpc.NewStreamServer(cache.New(ctx, options), false)
353 func (r *Runner) forwardedServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
354 ts := r.getTestServer()
355 return lsprpc.NewForwarder("tcp", ts.Addr)
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 {
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)
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)
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
382 const runTestAsGoplsEnvvar = "_GOPLS_TEST_BINARY_RUN_AS_GOPLS"
384 func (r *Runner) getRemoteSocket(t *testing.T) string {
388 const daemonFile = "gopls-test-daemon"
389 if r.socketDir != "" {
390 return filepath.Join(r.socketDir, daemonFile)
393 if r.GoplsPath == "" {
394 t.Fatal("cannot run tests with a separate process unless a path to a gopls binary is configured")
397 r.socketDir, err = ioutil.TempDir(r.TempDir, "gopls-regtest-socket")
399 t.Fatalf("creating tempdir: %v", err)
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
408 if err := cmd.Run(); err != nil {
409 panic(fmt.Sprintf("error running external gopls: %v\nstderr:\n%s", err, stderr.String()))
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) {
420 r.closers = append(r.closers, closer)
423 // Close cleans up resource that have been allocated to this workspace.
424 func (r *Runner) Close() error {
430 if err := r.ts.Close(); err != nil {
431 errmsgs = append(errmsgs, err.Error())
434 if r.socketDir != "" {
435 if err := os.RemoveAll(r.socketDir); err != nil {
436 errmsgs = append(errmsgs, err.Error())
440 for _, closer := range r.closers {
441 if err := closer.Close(); err != nil {
442 errmsgs = append(errmsgs, err.Error())
445 if err := os.RemoveAll(r.TempDir); err != nil {
446 errmsgs = append(errmsgs, err.Error())
449 if len(errmsgs) > 0 {
450 return fmt.Errorf("errors closing the test runner:\n\t%s", strings.Join(errmsgs, "\n\t"))