1
0
mirror of https://github.com/golang/go synced 2024-11-19 00:54:42 -07:00
go/internal/lsp/fake/sandbox.go
Rebecca Stambler 88e38c1d8d internal/lsp: make sure diagnostics only refer to existing files
We were previously sending diagnostics for nonexistent files, and then
adding them to the snapshot in the process. Remove this behavior, and
add a regression test. Case insensitive filesystems were too confusing
to write a test for, but fortunately, Filippo reported another instance
of this bug, so I used that for the regression test.

Fixes golang/go#38602

Change-Id: I4ef6b51944f3338e838875a5aafffd066e8392f4
Reviewed-on: https://go-review.googlesource.com/c/tools/+/230315
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-05-07 02:01:22 +00:00

157 lines
4.4 KiB
Go

// 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 fake
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/txtar"
)
// Sandbox holds a collection of temporary resources to use for working with Go
// code in tests.
type Sandbox struct {
name string
gopath string
basedir string
env []string
Proxy *Proxy
Workdir *Workdir
}
// NewSandbox creates a collection of named temporary resources, with a
// working directory populated by the txtar-encoded content in srctxt, and a
// file-based module proxy populated with the txtar-encoded content in
// proxytxt.
func NewSandbox(name, srctxt, proxytxt string, inGopath bool, env ...string) (_ *Sandbox, err error) {
sb := &Sandbox{
name: name,
env: env,
}
defer func() {
// Clean up if we fail at any point in this constructor.
if err != nil {
sb.Close()
}
}()
basedir, err := ioutil.TempDir("", fmt.Sprintf("goplstest-sandbox-%s-", name))
if err != nil {
return nil, fmt.Errorf("creating temporary workdir: %v", err)
}
sb.basedir = basedir
proxydir := filepath.Join(sb.basedir, "proxy")
sb.gopath = filepath.Join(sb.basedir, "gopath")
// Set the working directory as $GOPATH/src if inGopath is true.
workdir := filepath.Join(sb.gopath, "src")
dirs := []string{sb.gopath, proxydir}
if !inGopath {
workdir = filepath.Join(sb.basedir, "work")
dirs = append(dirs, workdir)
}
for _, subdir := range dirs {
if err := os.Mkdir(subdir, 0755); err != nil {
return nil, err
}
}
sb.Proxy, err = NewProxy(proxydir, proxytxt)
sb.Workdir, err = NewWorkdir(workdir, srctxt)
return sb, nil
}
func unpackTxt(txt string) map[string][]byte {
dataMap := make(map[string][]byte)
archive := txtar.Parse([]byte(txt))
for _, f := range archive.Files {
dataMap[f.Name] = f.Data
}
return dataMap
}
// splitModuleVersionPath extracts module information from files stored in the
// directory structure modulePath@version/suffix.
// For example:
// splitModuleVersionPath("mod.com@v1.2.3/package") = ("mod.com", "v1.2.3", "package")
func splitModuleVersionPath(path string) (modulePath, version, suffix string) {
parts := strings.Split(path, "/")
var modulePathParts []string
for i, p := range parts {
if strings.Contains(p, "@") {
mv := strings.SplitN(p, "@", 2)
modulePathParts = append(modulePathParts, mv[0])
return strings.Join(modulePathParts, "/"), mv[1], strings.Join(parts[i+1:], "/")
}
modulePathParts = append(modulePathParts, p)
}
// Default behavior: this is just a module path.
return path, "", ""
}
// GOPATH returns the value of the Sandbox GOPATH.
func (sb *Sandbox) GOPATH() string {
return sb.gopath
}
// GoEnv returns the default environment variables that can be used for
// invoking Go commands in the sandbox.
func (sb *Sandbox) GoEnv() []string {
return append([]string{
"GOPATH=" + sb.GOPATH(),
"GOPROXY=" + sb.Proxy.GOPROXY(),
"GO111MODULE=",
"GOSUMDB=off",
}, sb.env...)
}
// RunGoCommand executes a go command in the sandbox.
func (sb *Sandbox) RunGoCommand(ctx context.Context, verb string, args ...string) error {
inv := gocommand.Invocation{
Verb: verb,
Args: args,
WorkingDir: sb.Workdir.workdir,
Env: sb.GoEnv(),
}
gocmdRunner := &gocommand.Runner{}
_, stderr, _, err := gocmdRunner.RunRaw(ctx, inv)
if err != nil {
return err
}
// Hardcoded "file watcher": If the command executed was "go mod init",
// send a file creation event for a go.mod in the working directory.
if strings.HasPrefix(stderr.String(), "go: creating new go.mod") {
modpath := filepath.Join(sb.Workdir.workdir, "go.mod")
sb.Workdir.sendEvents(ctx, []FileEvent{{
Path: modpath,
ProtocolEvent: protocol.FileEvent{
URI: toURI(modpath),
Type: protocol.Created,
},
}})
}
return nil
}
// Close removes all state associated with the sandbox.
func (sb *Sandbox) Close() error {
var goCleanErr error
if sb.gopath != "" {
if err := sb.RunGoCommand(context.Background(), "clean", "-modcache"); err != nil {
goCleanErr = fmt.Errorf("cleaning modcache: %v", err)
}
}
err := os.RemoveAll(sb.basedir)
if err != nil || goCleanErr != nil {
return fmt.Errorf("error(s) cleaning sandbox: cleaning modcache: %v; removing files: %v", goCleanErr, err)
}
return nil
}