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.
22 exec "golang.org/x/sys/execabs"
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"
35 // Mode is a bitmask that defines for which execution modes a test should run.
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.
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.
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
60 PrintGoroutinesOnFailure bool
65 ts *servertest.TCPServer
67 // closers is a queue of clean-up functions to run at the end of the entire
72 type runConfig struct {
73 editor fake.EditorConfig
74 sandbox fake.SandboxConfig
82 func (r *Runner) defaultConfig() *runConfig {
84 modes: r.DefaultModes,
89 // A RunOption augments the behavior of the test runner.
90 type RunOption interface {
94 type optionSetter func(*runConfig)
96 func (f optionSetter) set(opts *runConfig) {
100 // Timeout configures a custom timeout for this test run.
101 func Timeout(d time.Duration) RunOption {
102 return optionSetter(func(opts *runConfig) {
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
114 // Modes configures the execution modes that the test should run in.
115 func Modes(modes Mode) RunOption {
116 return optionSetter(func(opts *runConfig) {
121 func SendPID() RunOption {
122 return optionSetter(func(opts *runConfig) {
123 opts.editor.SendPID = true
127 // EditorConfig is a RunOption option that configured the regtest editor.
128 type EditorConfig fake.EditorConfig
130 func (c EditorConfig) set(opts *runConfig) {
131 opts.editor = fake.EditorConfig(c)
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{}
143 return optionSetter(func(opts *runConfig) {
144 opts.editor.WorkspaceFolders = relFolders
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
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
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) {
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
176 func InExistingDir(dir string) RunOption {
177 return optionSetter(func(opts *runConfig) {
178 opts.sandbox.Workdir = dir
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
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
200 // LimitWorkspaceScope sets the LimitWorkspaceScope configuration.
201 func LimitWorkspaceScope() RunOption {
202 return optionSetter(func(opts *runConfig) {
203 opts.editor.LimitWorkspaceScope = true
207 type TestFunc func(t *testing.T, env *Env)
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) {
219 getServer func(context.Context, *testing.T) jsonrpc2.StreamServer
221 {"singleton", Singleton, singletonServer},
222 {"forwarded", Forwarded, r.forwardedServer},
223 {"separate_process", SeparateProcess, r.separateProcessServer},
224 {"experimental_workspace_module", Experimental, experimentalWorkspaceModule},
227 for _, tc := range tests {
229 config := r.defaultConfig()
230 for _, opt := range opts {
233 if config.modes&tc.mode == 0 {
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)
243 t.Run(tc.name, func(t *testing.T) {
244 ctx, cancel := context.WithTimeout(context.Background(), config.timeout)
246 ctx = debug.WithInstance(ctx, "", "off")
247 if config.debugAddr != "" {
248 di := debug.GetInstance(ctx)
249 di.DebugAddress = config.debugAddr
251 di.MonitorMemory(ctx)
254 rootDir := filepath.Join(r.TempDir, filepath.FromSlash(t.Name()))
255 if err := os.MkdirAll(rootDir, 0755); err != nil {
258 config.sandbox.Files = files
259 config.sandbox.RootDir = rootDir
260 sandbox, err := fake.NewSandbox(&config.sandbox)
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.
271 ss := tc.getServer(ctx, t)
272 framer := jsonrpc2.NewRawStream
273 ls := &loggingFramer{}
274 if !config.skipLogs {
275 framer = ls.framer(jsonrpc2.NewRawStream)
277 ts := servertest.NewPipeServer(ctx, ss, framer)
278 env := NewEnv(ctx, t, sandbox, ts, config.editor, !config.skipHooks)
280 if t.Failed() && r.PrintGoroutinesOnFailure {
281 pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
283 if t.Failed() || testing.Verbose() {
284 ls.printBuffers(t.Name(), os.Stderr)
288 // Always await the initial workspace load.
289 env.Await(InitialWorkspaceLoad)
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": "",
305 "netbsd-arm-bsiegert": "",
306 "solaris-amd64-oraclerel": "",
307 "windows-arm-zx2c4": "",
310 func checkBuilder(t *testing.T) {
312 builder := os.Getenv("GO_BUILDER_NAME")
313 if reason, ok := longBuilders[builder]; ok && testing.Short() {
315 t.Skipf("Skipping %s with -short due to %s", builder, reason)
317 t.Skipf("Skipping %s with -short", builder)
322 type loggingFramer struct {
327 // safeBuffer is a threadsafe buffer for logs.
328 type safeBuffer struct {
333 func (b *safeBuffer) Write(p []byte) (int, error) {
336 return b.buf.Write(p)
339 func (s *loggingFramer) framer(f jsonrpc2.Framer) jsonrpc2.Framer {
340 return func(nc net.Conn) jsonrpc2.Stream {
344 s.buf = &safeBuffer{buf: bytes.Buffer{}}
350 return protocol.LoggingStream(stream, s.buf)
356 func (s *loggingFramer) printBuffers(testname string, w io.Writer) {
363 fmt.Fprintf(os.Stderr, "#### Start Gopls Test Logs for %q\n", testname)
365 io.Copy(w, &s.buf.buf)
367 fmt.Fprintf(os.Stderr, "#### End Gopls Test Logs for %q\n", testname)
370 func singletonServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
371 return lsprpc.NewStreamServer(cache.New(ctx, hooks.Options), false)
374 func experimentalWorkspaceModule(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
375 options := func(o *source.Options) {
377 o.ExperimentalWorkspaceModule = true
379 return lsprpc.NewStreamServer(cache.New(ctx, options), false)
382 func (r *Runner) forwardedServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
383 ts := r.getTestServer()
384 return lsprpc.NewForwarder("tcp", ts.Addr)
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 {
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)
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)
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
411 const runTestAsGoplsEnvvar = "_GOPLS_TEST_BINARY_RUN_AS_GOPLS"
413 func (r *Runner) getRemoteSocket(t *testing.T) string {
417 const daemonFile = "gopls-test-daemon"
418 if r.socketDir != "" {
419 return filepath.Join(r.socketDir, daemonFile)
422 if r.GoplsPath == "" {
423 t.Fatal("cannot run tests with a separate process unless a path to a gopls binary is configured")
426 r.socketDir, err = ioutil.TempDir(r.TempDir, "gopls-regtest-socket")
428 t.Fatalf("creating tempdir: %v", err)
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
437 if err := cmd.Run(); err != nil {
438 panic(fmt.Sprintf("error running external gopls: %v\nstderr:\n%s", err, stderr.String()))
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) {
449 r.closers = append(r.closers, closer)
452 // Close cleans up resource that have been allocated to this workspace.
453 func (r *Runner) Close() error {
459 if err := r.ts.Close(); err != nil {
460 errmsgs = append(errmsgs, err.Error())
463 if r.socketDir != "" {
464 if err := os.RemoveAll(r.socketDir); err != nil {
465 errmsgs = append(errmsgs, err.Error())
469 for _, closer := range r.closers {
470 if err := closer.Close(); err != nil {
471 errmsgs = append(errmsgs, err.Error())
474 if err := os.RemoveAll(r.TempDir); err != nil {
475 errmsgs = append(errmsgs, err.Error())
478 if len(errmsgs) > 0 {
479 return fmt.Errorf("errors closing the test runner:\n\t%s", strings.Join(errmsgs, "\n\t"))