// Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package regtest import ( "bytes" "context" "fmt" "io" "io/ioutil" "os" "os/exec" "path/filepath" "runtime/pprof" "strings" "sync" "testing" "time" "golang.org/x/tools/internal/jsonrpc2" "golang.org/x/tools/internal/jsonrpc2/servertest" "golang.org/x/tools/internal/lsp/cache" "golang.org/x/tools/internal/lsp/debug" "golang.org/x/tools/internal/lsp/fake" "golang.org/x/tools/internal/lsp/lsprpc" "golang.org/x/tools/internal/lsp/protocol" ) // Mode is a bitmask that defines for which execution modes a test should run. type Mode int const ( // Singleton mode uses a separate in-process gopls instance for each test, // and communicates over pipes to mimic the gopls sidecar execution mode, // which communicates over stdin/stderr. Singleton Mode = 1 << iota // Forwarded forwards connections to a shared in-process gopls instance. Forwarded // SeparateProcess forwards connection to a shared separate gopls process. SeparateProcess // NormalModes are the global default execution modes, when unmodified by // test flags or by individual test options. NormalModes = Singleton | Forwarded ) // A Runner runs tests in gopls execution environments, as specified by its // modes. For modes that share state (for example, a shared cache or common // remote), any tests that execute on the same Runner will share the same // state. type Runner struct { DefaultModes Mode Timeout time.Duration GoplsPath string AlwaysPrintLogs bool PrintGoroutinesOnFailure bool mu sync.Mutex ts *servertest.TCPServer socketDir string // closers is a queue of clean-up functions to run at the end of the entire // test suite. closers []io.Closer } // getTestServer gets the test server instance to connect to, or creates one if // it doesn't exist. func (r *Runner) getTestServer() *servertest.TCPServer { r.mu.Lock() defer r.mu.Unlock() if r.ts == nil { ctx := context.Background() ctx = debug.WithInstance(ctx, "", "") ss := lsprpc.NewStreamServer(cache.New(ctx, nil)) r.ts = servertest.NewTCPServer(context.Background(), ss) } return r.ts } // runTestAsGoplsEnvvar triggers TestMain to run gopls instead of running // tests. It's a trick to allow tests to find a binary to use to start a gopls // subprocess. const runTestAsGoplsEnvvar = "_GOPLS_TEST_BINARY_RUN_AS_GOPLS" func (r *Runner) getRemoteSocket(t *testing.T) string { t.Helper() r.mu.Lock() defer r.mu.Unlock() const daemonFile = "gopls-test-daemon" if r.socketDir != "" { return filepath.Join(r.socketDir, daemonFile) } if r.GoplsPath == "" { t.Fatal("cannot run tests with a separate process unless a path to a gopls binary is configured") } var err error r.socketDir, err = ioutil.TempDir("", "gopls-regtests") if err != nil { t.Fatalf("creating tempdir: %v", err) } socket := filepath.Join(r.socketDir, daemonFile) args := []string{"serve", "-listen", "unix;" + socket, "-listen.timeout", "10s"} cmd := exec.Command(r.GoplsPath, args...) cmd.Env = append(os.Environ(), runTestAsGoplsEnvvar+"=true") var stderr bytes.Buffer cmd.Stderr = &stderr go func() { if err := cmd.Run(); err != nil { panic(fmt.Sprintf("error running external gopls: %v\nstderr:\n%s", err, stderr.String())) } }() return socket } // AddCloser schedules a closer to be closed at the end of the test run. This // is useful for Windows in particular, as func (r *Runner) AddCloser(closer io.Closer) { r.mu.Lock() defer r.mu.Unlock() r.closers = append(r.closers, closer) } type runConfig struct { modes Mode proxyTxt string timeout time.Duration env []string } func (r *Runner) defaultConfig() *runConfig { return &runConfig{ modes: r.DefaultModes, timeout: r.Timeout, } } // A RunOption augments the behavior of the test runner. type RunOption interface { set(*runConfig) } type optionSetter func(*runConfig) func (f optionSetter) set(opts *runConfig) { f(opts) } // WithTimeout configures a custom timeout for this test run. func WithTimeout(d time.Duration) RunOption { return optionSetter(func(opts *runConfig) { opts.timeout = d }) } // WithProxy configures a file proxy using the given txtar-encoded string. func WithProxy(txt string) RunOption { return optionSetter(func(opts *runConfig) { opts.proxyTxt = txt }) } // WithModes configures the execution modes that the test should run in. func WithModes(modes Mode) RunOption { return optionSetter(func(opts *runConfig) { opts.modes = modes }) } // WithEnv overlays environment variables encoded by "= 0 { return fmt.Errorf("errors closing the test runner:\n\t%s", strings.Join(errmsgs, "\n\t")) } return nil }