mirror of
https://github.com/golang/go
synced 2024-11-18 14:14:46 -07:00
84d0e3d1cc
A bunch of options are added to enable long-running performance-oriented tests in existing directories. They will be used in a later CL to implement a simple stress test, as an example of what is possible. Change-Id: I531b201b415362ea135978238b3d64b903226359 Reviewed-on: https://go-review.googlesource.com/c/tools/+/244440 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com>
226 lines
6.6 KiB
Go
226 lines
6.6 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/testenv"
|
|
"golang.org/x/tools/txtar"
|
|
)
|
|
|
|
// 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.
|
|
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
|
|
}
|
|
|
|
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, 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,
|
|
}
|
|
// 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 sb.Workdir != nil {
|
|
inv.WorkingDir = sb.Workdir.workdir
|
|
}
|
|
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 fmt.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 != "" {
|
|
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
|
|
}
|