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