func (r *Runner) Close() error {
	r.mu.Lock()
	defer r.mu.Unlock()
	var errmsgs []string
	if r.ts != nil {
		if err := r.ts.Close(); err != nil {
			errmsgs = append(errmsgs, err.Error())
		}
	}
	if r.socketDir != "" {
		if err := os.RemoveAll(r.socketDir); err != nil {
			errmsgs = append(errmsgs, err.Error())
		}
	}
	if !r.SkipCleanup {
		for _, closer := range r.closers {
			if err := closer.Close(); err != nil {
				errmsgs = append(errmsgs, err.Error())
			}
		}
		if err := os.RemoveAll(r.TempDir); err != nil {
			errmsgs = append(errmsgs, err.Error())
		}
	}
	if len(errmsgs) > 0 {
		return fmt.Errorf("errors closing the test runner:\n\t%s", strings.Join(errmsgs, "\n\t"))
	}
	return nil
}
Close cleans up resource that have been allocated to this workspace.
package regtest
import (
	"bytes"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"runtime/pprof"
	"strings"
	"sync"
	"testing"
	"time"
	exec "golang.org/x/sys/execabs"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/jsonrpc2"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/jsonrpc2/servertest"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/cache"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/debug"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/fake"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/lsprpc"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/protocol"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/source"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/xcontext"
)
type Mode int
const (
	Singleton Mode = 1 << iota
	Forwarded
	SeparateProcess
	Experimental
)
type Runner struct {
	DefaultModes             Mode
	Timeout                  time.Duration
	GoplsPath                string
	PrintGoroutinesOnFailure bool
	TempDir                  string
	SkipCleanup              bool
	OptionsHook              func(*source.Options)
	mu        sync.Mutex
	ts        *servertest.TCPServer
	socketDir string
	closers []io.Closer
}
type runConfig struct {
	editor      fake.EditorConfig
	sandbox     fake.SandboxConfig
	modes       Mode
	timeout     time.Duration
	debugAddr   string
	skipLogs    bool
	skipHooks   bool
	optionsHook func(*source.Options)
}
func (r *Runner) defaultConfig() *runConfig {
	return &runConfig{
		modes:       r.DefaultModes,
		timeout:     r.Timeout,
		optionsHook: r.OptionsHook,
	}
}
type RunOption interface {
	set(*runConfig)
}
type optionSetter func(*runConfig)
func (f optionSetter) set(opts *runConfig) {
	f(opts)
}
func Timeout(d time.Duration) RunOption {
	return optionSetter(func(opts *runConfig) {
		opts.timeout = d
	})
}
func ProxyFiles(txt string) RunOption {
	return optionSetter(func(opts *runConfig) {
		opts.sandbox.ProxyFiles = fake.UnpackTxt(txt)
	})
}
func Modes(modes Mode) RunOption {
	return optionSetter(func(opts *runConfig) {
		opts.modes = modes
	})
}
func Options(hook func(*source.Options)) RunOption {
	return optionSetter(func(opts *runConfig) {
		old := opts.optionsHook
		opts.optionsHook = func(o *source.Options) {
			if old != nil {
				old(o)
			}
			hook(o)
		}
	})
}
func SendPID() RunOption {
	return optionSetter(func(opts *runConfig) {
		opts.editor.SendPID = true
	})
}
type EditorConfig fake.EditorConfig
func (c EditorConfig) set(opts *runConfig) {
	opts.editor = fake.EditorConfig(c)
}
func WorkspaceFolders(relFolders ...string) RunOption {
	if len(relFolders) == 0 {
		relFolders = []string{}
	}
	return optionSetter(func(opts *runConfig) {
		opts.editor.WorkspaceFolders = relFolders
	})
}
func InGOPATH() RunOption {
	return optionSetter(func(opts *runConfig) {
		opts.sandbox.InGoPath = true
	})
}
func DebugAddress(addr string) RunOption {
	return optionSetter(func(opts *runConfig) {
		opts.debugAddr = addr
	})
}
func SkipLogs() RunOption {
	return optionSetter(func(opts *runConfig) {
		opts.skipLogs = true
	})
}
func InExistingDir(dir string) RunOption {
	return optionSetter(func(opts *runConfig) {
		opts.sandbox.Workdir = dir
	})
}
func SkipHooks(skip bool) RunOption {
	return optionSetter(func(opts *runConfig) {
		opts.skipHooks = skip
	})
}
func GOPROXY(goproxy string) RunOption {
	return optionSetter(func(opts *runConfig) {
		opts.sandbox.GOPROXY = goproxy
	})
}
func LimitWorkspaceScope() RunOption {
	return optionSetter(func(opts *runConfig) {
		opts.editor.LimitWorkspaceScope = true
	})
}
type TestFunc func(t *testing.T, env *Env)
func (r *Runner) Run(t *testing.T, files string, test TestFunc, opts ...RunOption) {
	t.Helper()
	checkBuilder(t)
	tests := []struct {
		name      string
		mode      Mode
		getServer func(context.Context, *testing.T, func(*source.Options)) jsonrpc2.StreamServer
	}{
		{"singleton", Singleton, singletonServer},
		{"forwarded", Forwarded, r.forwardedServer},
		{"separate_process", SeparateProcess, r.separateProcessServer},
		{"experimental", Experimental, experimentalServer},
	}
	for _, tc := range tests {
		tc := tc
		config := r.defaultConfig()
		for _, opt := range opts {
			opt.set(config)
		}
		if config.modes&tc.mode == 0 {
			continue
		}
		if config.debugAddr != "" && tc.mode != Singleton {
			t.Fatalf("debugging regtest servers only works in Singleton mode, "+
				"got debug addr %q and mode %v", config.debugAddr, tc.mode)
		}
		t.Run(tc.name, func(t *testing.T) {
			ctx, cancel := context.WithTimeout(context.Background(), config.timeout)
			defer cancel()
			ctx = debug.WithInstance(ctx, "", "off")
			if config.debugAddr != "" {
				di := debug.GetInstance(ctx)
				di.Serve(ctx, config.debugAddr)
				di.MonitorMemory(ctx)
			}
			rootDir := filepath.Join(r.TempDir, filepath.FromSlash(t.Name()))
			if err := os.MkdirAll(rootDir, 0755); err != nil {
				t.Fatal(err)
			}
			files := fake.UnpackTxt(files)
			if config.editor.WindowsLineEndings {
				for name, data := range files {
					files[name] = bytes.ReplaceAll(data, []byte("\n"), []byte("\r\n"))
				}
			}
			config.sandbox.Files = files
			config.sandbox.RootDir = rootDir
			sandbox, err := fake.NewSandbox(&config.sandbox)
			if err != nil {
				t.Fatal(err)
			}
			r.AddCloser(sandbox)
			ss := tc.getServer(ctx, t, config.optionsHook)
			framer := jsonrpc2.NewRawStream
			ls := &loggingFramer{}
			if !config.skipLogs {
				framer = ls.framer(jsonrpc2.NewRawStream)
			}
			ts := servertest.NewPipeServer(ctx, ss, framer)
			env := NewEnv(ctx, t, sandbox, ts, config.editor, !config.skipHooks)
			defer func() {
				if t.Failed() && r.PrintGoroutinesOnFailure {
					pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
				}
				if t.Failed() || testing.Verbose() {
					ls.printBuffers(t.Name(), os.Stderr)
				}
				closeCtx, cancel := context.WithTimeout(xcontext.Detach(ctx), 5*time.Second)
				defer cancel()
				if err := env.Editor.Close(closeCtx); err != nil {
					t.Errorf("closing editor: %v", err)
				}
			}()
			env.Await(InitialWorkspaceLoad)
			test(t, env)
		})
	}
}
var longBuilders = map[string]string{
	"openbsd-amd64-64":        "golang.org/issues/42789",
	"openbsd-386-64":          "golang.org/issues/42789",
	"openbsd-386-68":          "golang.org/issues/42789",
	"openbsd-amd64-68":        "golang.org/issues/42789",
	"darwin-amd64-10_12":      "",
	"freebsd-amd64-race":      "",
	"illumos-amd64":           "",
	"netbsd-arm-bsiegert":     "",
	"solaris-amd64-oraclerel": "",
	"windows-arm-zx2c4":       "",
}
func checkBuilder(t *testing.T) {
	t.Helper()
	builder := os.Getenv("GO_BUILDER_NAME")
	if reason, ok := longBuilders[builder]; ok && testing.Short() {
		if reason != "" {
			t.Skipf("Skipping %s with -short due to %s", builder, reason)
		} else {
			t.Skipf("Skipping %s with -short", builder)
		}
	}
}
type loggingFramer struct {
	mu  sync.Mutex
	buf *safeBuffer
}
type safeBuffer struct {
	mu  sync.Mutex
	buf bytes.Buffer
}
func (b *safeBuffer) Write(p []byte) (int, error) {
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.buf.Write(p)
}
func (s *loggingFramer) framer(f jsonrpc2.Framer) jsonrpc2.Framer {
	return func(nc net.Conn) jsonrpc2.Stream {
		s.mu.Lock()
		framed := false
		if s.buf == nil {
			s.buf = &safeBuffer{buf: bytes.Buffer{}}
			framed = true
		}
		s.mu.Unlock()
		stream := f(nc)
		if framed {
			return protocol.LoggingStream(stream, s.buf)
		}
		return stream
	}
}
func (s *loggingFramer) printBuffers(testname string, w io.Writer) {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.buf == nil {
		return
	}
	fmt.Fprintf(os.Stderr, "#### Start Gopls Test Logs for %q\n", testname)
	s.buf.mu.Lock()
	io.Copy(w, &s.buf.buf)
	s.buf.mu.Unlock()
	fmt.Fprintf(os.Stderr, "#### End Gopls Test Logs for %q\n", testname)
}
func singletonServer(ctx context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
	return lsprpc.NewStreamServer(cache.New(optsHook), false)
}
func experimentalServer(_ context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
	options := func(o *source.Options) {
		optsHook(o)
		o.EnableAllExperiments()
		o.ExperimentalWorkspaceModule = true
	}
	return lsprpc.NewStreamServer(cache.New(options), false)
}
func (r *Runner) forwardedServer(ctx context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
	ts := r.getTestServer(optsHook)
	return newForwarder("tcp", ts.Addr)
}
func (r *Runner) getTestServer(optsHook func(*source.Options)) *servertest.TCPServer {
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.ts == nil {
		ctx := context.Background()
		ctx = debug.WithInstance(ctx, "", "off")
		ss := lsprpc.NewStreamServer(cache.New(optsHook), false)
		r.ts = servertest.NewTCPServer(ctx, ss, nil)
	}
	return r.ts
}
func (r *Runner) separateProcessServer(ctx context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
	socket := r.getRemoteSocket(t)
	return newForwarder("unix", socket)
}
func newForwarder(network, address string) *lsprpc.Forwarder {
	server, err := lsprpc.NewForwarder(network+";"+address, nil)
	if err != nil {
		panic(fmt.Sprintf("internal error: unable to create forwarder: %v", err))
	}
	return server
}
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(r.TempDir, "gopls-regtest-socket")
	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
}
func (r *Runner) AddCloser(closer io.Closer) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.closers = append(r.closers, closer)
}