Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / internal / lsp / fake / sandbox.go
1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package fake
6
7 import (
8         "context"
9         "fmt"
10         "io/ioutil"
11         "os"
12         "path/filepath"
13         "strings"
14
15         "golang.org/x/tools/internal/gocommand"
16         "golang.org/x/tools/internal/testenv"
17         "golang.org/x/tools/txtar"
18         errors "golang.org/x/xerrors"
19 )
20
21 // Sandbox holds a collection of temporary resources to use for working with Go
22 // code in tests.
23 type Sandbox struct {
24         gopath  string
25         basedir string
26         goproxy string
27         Workdir *Workdir
28 }
29
30 // SandboxConfig controls the behavior of a test sandbox. The zero value
31 // defines a reasonable default.
32 type SandboxConfig struct {
33         // RootDir sets the base directory to use when creating temporary
34         // directories. If not specified, defaults to a new temporary directory.
35         RootDir string
36         // Files holds a txtar-encoded archive of files to populate the initial state
37         // of the working directory.
38         //
39         // For convenience, the special substring "$SANDBOX_WORKDIR" is replaced with
40         // the sandbox's resolved working directory before writing files.
41         Files string
42         // InGoPath specifies that the working directory should be within the
43         // temporary GOPATH.
44         InGoPath bool
45         // Workdir configures the working directory of the Sandbox, for running in a
46         // pre-existing directory. If unset, a new working directory will be created
47         // under RootDir.
48         //
49         // This option is incompatible with InGoPath or Files.
50         Workdir string
51
52         // ProxyFiles holds a txtar-encoded archive of files to populate a file-based
53         // Go proxy.
54         ProxyFiles string
55         // GOPROXY is the explicit GOPROXY value that should be used for the sandbox.
56         //
57         // This option is incompatible with ProxyFiles.
58         GOPROXY string
59 }
60
61 // NewSandbox creates a collection of named temporary resources, with a
62 // working directory populated by the txtar-encoded content in srctxt, and a
63 // file-based module proxy populated with the txtar-encoded content in
64 // proxytxt.
65 //
66 // If rootDir is non-empty, it will be used as the root of temporary
67 // directories created for the sandbox. Otherwise, a new temporary directory
68 // will be used as root.
69 func NewSandbox(config *SandboxConfig) (_ *Sandbox, err error) {
70         if config == nil {
71                 config = new(SandboxConfig)
72         }
73
74         if config.Workdir != "" && (config.Files != "" || config.InGoPath) {
75                 return nil, fmt.Errorf("invalid SandboxConfig: Workdir cannot be used in conjunction with Files or InGoPath. Got %+v", config)
76         }
77
78         if config.GOPROXY != "" && config.ProxyFiles != "" {
79                 return nil, fmt.Errorf("invalid SandboxConfig: GOPROXY cannot be set in conjunction with ProxyFiles. Got %+v", config)
80         }
81
82         sb := &Sandbox{}
83         defer func() {
84                 // Clean up if we fail at any point in this constructor.
85                 if err != nil {
86                         sb.Close()
87                 }
88         }()
89
90         baseDir, err := ioutil.TempDir(config.RootDir, "gopls-sandbox-")
91         if err != nil {
92                 return nil, fmt.Errorf("creating temporary workdir: %v", err)
93         }
94         sb.basedir = baseDir
95         sb.gopath = filepath.Join(sb.basedir, "gopath")
96         if err := os.Mkdir(sb.gopath, 0755); err != nil {
97                 return nil, err
98         }
99         if config.GOPROXY != "" {
100                 sb.goproxy = config.GOPROXY
101         } else {
102                 proxydir := filepath.Join(sb.basedir, "proxy")
103                 if err := os.Mkdir(proxydir, 0755); err != nil {
104                         return nil, err
105                 }
106                 sb.goproxy, err = WriteProxy(proxydir, config.ProxyFiles)
107                 if err != nil {
108                         return nil, err
109                 }
110         }
111         if config.Workdir != "" {
112                 sb.Workdir = NewWorkdir(config.Workdir)
113         } else {
114                 workdir := config.Workdir
115                 // If we don't have a pre-existing work dir, we want to create either
116                 // $GOPATH/src or <RootDir/work>.
117                 if config.InGoPath {
118                         // Set the working directory as $GOPATH/src.
119                         workdir = filepath.Join(sb.gopath, "src")
120                 } else if workdir == "" {
121                         workdir = filepath.Join(sb.basedir, "work")
122                 }
123                 if err := os.Mkdir(workdir, 0755); err != nil {
124                         return nil, err
125                 }
126                 sb.Workdir = NewWorkdir(workdir)
127                 if err := sb.Workdir.writeInitialFiles(config.Files); err != nil {
128                         return nil, err
129                 }
130         }
131
132         return sb, nil
133 }
134
135 // Tempdir creates a new temp directory with the given txtar-encoded files. It
136 // is the responsibility of the caller to call os.RemoveAll on the returned
137 // file path when it is no longer needed.
138 func Tempdir(txt string) (string, error) {
139         dir, err := ioutil.TempDir("", "gopls-tempdir-")
140         if err != nil {
141                 return "", err
142         }
143         if err := writeTxtar(txt, RelativeTo(dir)); err != nil {
144                 return "", err
145         }
146         return dir, nil
147 }
148
149 func unpackTxt(txt string) map[string][]byte {
150         dataMap := make(map[string][]byte)
151         archive := txtar.Parse([]byte(txt))
152         for _, f := range archive.Files {
153                 dataMap[f.Name] = f.Data
154         }
155         return dataMap
156 }
157
158 // splitModuleVersionPath extracts module information from files stored in the
159 // directory structure modulePath@version/suffix.
160 // For example:
161 //  splitModuleVersionPath("mod.com@v1.2.3/package") = ("mod.com", "v1.2.3", "package")
162 func splitModuleVersionPath(path string) (modulePath, version, suffix string) {
163         parts := strings.Split(path, "/")
164         var modulePathParts []string
165         for i, p := range parts {
166                 if strings.Contains(p, "@") {
167                         mv := strings.SplitN(p, "@", 2)
168                         modulePathParts = append(modulePathParts, mv[0])
169                         return strings.Join(modulePathParts, "/"), mv[1], strings.Join(parts[i+1:], "/")
170                 }
171                 modulePathParts = append(modulePathParts, p)
172         }
173         // Default behavior: this is just a module path.
174         return path, "", ""
175 }
176
177 // GOPATH returns the value of the Sandbox GOPATH.
178 func (sb *Sandbox) GOPATH() string {
179         return sb.gopath
180 }
181
182 // GoEnv returns the default environment variables that can be used for
183 // invoking Go commands in the sandbox.
184 func (sb *Sandbox) GoEnv() map[string]string {
185         vars := map[string]string{
186                 "GOPATH":           sb.GOPATH(),
187                 "GOPROXY":          sb.goproxy,
188                 "GO111MODULE":      "",
189                 "GOSUMDB":          "off",
190                 "GOPACKAGESDRIVER": "off",
191         }
192         if testenv.Go1Point() >= 5 {
193                 vars["GOMODCACHE"] = ""
194         }
195         return vars
196 }
197
198 // RunGoCommand executes a go command in the sandbox.
199 func (sb *Sandbox) RunGoCommand(ctx context.Context, dir, verb string, args []string) error {
200         var vars []string
201         for k, v := range sb.GoEnv() {
202                 vars = append(vars, fmt.Sprintf("%s=%s", k, v))
203         }
204         inv := gocommand.Invocation{
205                 Verb: verb,
206                 Args: args,
207                 Env:  vars,
208         }
209         // Use the provided directory for the working directory, if available.
210         // sb.Workdir may be nil if we exited the constructor with errors (we call
211         // Close to clean up any partial state from the constructor, which calls
212         // RunGoCommand).
213         if dir != "" {
214                 inv.WorkingDir = sb.Workdir.AbsPath(dir)
215         } else if sb.Workdir != nil {
216                 inv.WorkingDir = string(sb.Workdir.RelativeTo)
217         }
218         gocmdRunner := &gocommand.Runner{}
219         _, _, _, err := gocmdRunner.RunRaw(ctx, inv)
220         if err != nil {
221                 return err
222         }
223         // Since running a go command may result in changes to workspace files,
224         // check if we need to send any any "watched" file events.
225         if sb.Workdir != nil {
226                 if err := sb.Workdir.CheckForFileChanges(ctx); err != nil {
227                         return errors.Errorf("checking for file changes: %w", err)
228                 }
229         }
230         return nil
231 }
232
233 // Close removes all state associated with the sandbox.
234 func (sb *Sandbox) Close() error {
235         var goCleanErr error
236         if sb.gopath != "" {
237                 goCleanErr = sb.RunGoCommand(context.Background(), "", "clean", []string{"-modcache"})
238         }
239         err := os.RemoveAll(sb.basedir)
240         if err != nil || goCleanErr != nil {
241                 return fmt.Errorf("error(s) cleaning sandbox: cleaning modcache: %v; removing files: %v", goCleanErr, err)
242         }
243         return nil
244 }