+++ /dev/null
-// 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/testenv"
- "golang.org/x/tools/txtar"
- errors "golang.org/x/xerrors"
-)
-
-// Sandbox holds a collection of temporary resources to use for working with Go
-// code in tests.
-type Sandbox struct {
- gopath string
- basedir string
- goproxy string
- Workdir *Workdir
-}
-
-// SandboxConfig controls the behavior of a test sandbox. The zero value
-// defines a reasonable default.
-type SandboxConfig struct {
- // RootDir sets the base directory to use when creating temporary
- // directories. If not specified, defaults to a new temporary directory.
- RootDir string
- // Files holds a txtar-encoded archive of files to populate the initial state
- // of the working directory.
- //
- // For convenience, the special substring "$SANDBOX_WORKDIR" is replaced with
- // the sandbox's resolved working directory before writing files.
- Files string
- // InGoPath specifies that the working directory should be within the
- // temporary GOPATH.
- InGoPath bool
- // Workdir configures the working directory of the Sandbox, for running in a
- // pre-existing directory. If unset, a new working directory will be created
- // under RootDir.
- //
- // This option is incompatible with InGoPath or Files.
- Workdir string
-
- // ProxyFiles holds a txtar-encoded archive of files to populate a file-based
- // Go proxy.
- ProxyFiles string
- // GOPROXY is the explicit GOPROXY value that should be used for the sandbox.
- //
- // This option is incompatible with ProxyFiles.
- GOPROXY string
-}
-
-// 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.
-//
-// If rootDir is non-empty, it will be used as the root of temporary
-// directories created for the sandbox. Otherwise, a new temporary directory
-// will be used as root.
-func NewSandbox(config *SandboxConfig) (_ *Sandbox, err error) {
- if config == nil {
- config = new(SandboxConfig)
- }
-
- if config.Workdir != "" && (config.Files != "" || config.InGoPath) {
- return nil, fmt.Errorf("invalid SandboxConfig: Workdir cannot be used in conjunction with Files or InGoPath. Got %+v", config)
- }
-
- if config.GOPROXY != "" && config.ProxyFiles != "" {
- return nil, fmt.Errorf("invalid SandboxConfig: GOPROXY cannot be set in conjunction with ProxyFiles. Got %+v", config)
- }
-
- sb := &Sandbox{}
- defer func() {
- // Clean up if we fail at any point in this constructor.
- if err != nil {
- sb.Close()
- }
- }()
-
- baseDir, err := ioutil.TempDir(config.RootDir, "gopls-sandbox-")
- if err != nil {
- return nil, fmt.Errorf("creating temporary workdir: %v", err)
- }
- sb.basedir = baseDir
- sb.gopath = filepath.Join(sb.basedir, "gopath")
- if err := os.Mkdir(sb.gopath, 0755); err != nil {
- return nil, err
- }
- if config.GOPROXY != "" {
- sb.goproxy = config.GOPROXY
- } else {
- proxydir := filepath.Join(sb.basedir, "proxy")
- if err := os.Mkdir(proxydir, 0755); err != nil {
- return nil, err
- }
- sb.goproxy, err = WriteProxy(proxydir, config.ProxyFiles)
- if err != nil {
- return nil, err
- }
- }
- if config.Workdir != "" {
- sb.Workdir = NewWorkdir(config.Workdir)
- } else {
- workdir := config.Workdir
- // If we don't have a pre-existing work dir, we want to create either
- // $GOPATH/src or <RootDir/work>.
- if config.InGoPath {
- // Set the working directory as $GOPATH/src.
- workdir = filepath.Join(sb.gopath, "src")
- } else if workdir == "" {
- workdir = filepath.Join(sb.basedir, "work")
- }
- if err := os.Mkdir(workdir, 0755); err != nil {
- return nil, err
- }
- sb.Workdir = NewWorkdir(workdir)
- if err := sb.Workdir.writeInitialFiles(config.Files); err != nil {
- return nil, err
- }
- }
-
- return sb, nil
-}
-
-// Tempdir creates a new temp directory with the given txtar-encoded files. It
-// is the responsibility of the caller to call os.RemoveAll on the returned
-// file path when it is no longer needed.
-func Tempdir(txt string) (string, error) {
- dir, err := ioutil.TempDir("", "gopls-tempdir-")
- if err != nil {
- return "", err
- }
- if err := writeTxtar(txt, RelativeTo(dir)); err != nil {
- return "", err
- }
- return dir, 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() map[string]string {
- vars := map[string]string{
- "GOPATH": sb.GOPATH(),
- "GOPROXY": sb.goproxy,
- "GO111MODULE": "",
- "GOSUMDB": "off",
- "GOPACKAGESDRIVER": "off",
- }
- if testenv.Go1Point() >= 5 {
- vars["GOMODCACHE"] = ""
- }
- return vars
-}
-
-// RunGoCommand executes a go command in the sandbox.
-func (sb *Sandbox) RunGoCommand(ctx context.Context, dir, verb string, args []string) error {
- var vars []string
- for k, v := range sb.GoEnv() {
- vars = append(vars, fmt.Sprintf("%s=%s", k, v))
- }
- inv := gocommand.Invocation{
- Verb: verb,
- Args: args,
- Env: vars,
- }
- // Use the provided directory for the working directory, if available.
- // sb.Workdir may be nil if we exited the constructor with errors (we call
- // Close to clean up any partial state from the constructor, which calls
- // RunGoCommand).
- if dir != "" {
- inv.WorkingDir = sb.Workdir.AbsPath(dir)
- } else if sb.Workdir != nil {
- inv.WorkingDir = string(sb.Workdir.RelativeTo)
- }
- gocmdRunner := &gocommand.Runner{}
- _, _, _, err := gocmdRunner.RunRaw(ctx, inv)
- if err != nil {
- return err
- }
- // Since running a go command may result in changes to workspace files,
- // check if we need to send any any "watched" file events.
- if sb.Workdir != nil {
- if err := sb.Workdir.CheckForFileChanges(ctx); err != nil {
- return errors.Errorf("checking for file changes: %w", err)
- }
- }
- return nil
-}
-
-// Close removes all state associated with the sandbox.
-func (sb *Sandbox) Close() error {
- var goCleanErr error
- if sb.gopath != "" {
- goCleanErr = sb.RunGoCommand(context.Background(), "", "clean", []string{"-modcache"})
- }
- 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
-}