1
2
3
4
5
6
7 package gotooltest
8
9 import (
10 "bytes"
11 "encoding/json"
12 "fmt"
13 "os"
14 "os/exec"
15 "path/filepath"
16 "regexp"
17 "runtime"
18 "strings"
19 "sync"
20
21 "github.com/rogpeppe/go-internal/testscript"
22 )
23
24 var (
25 goVersionRegex = regexp.MustCompile(`^go([1-9][0-9]*)\.([1-9][0-9]*)$`)
26
27 goEnv struct {
28 GOROOT string
29 GOCACHE string
30 GOPROXY string
31 goversion string
32 releaseTags []string
33 once sync.Once
34 err error
35 }
36 )
37
38
39
40
41
42
43 func initGoEnv() (err error) {
44 td, err := os.MkdirTemp("", "gotooltest-initGoEnv")
45 if err != nil {
46 return fmt.Errorf("failed to create temporary directory for go command tests: %w", err)
47 }
48 defer func() {
49 if rerr := os.RemoveAll(td); rerr != nil && err == nil {
50 err = fmt.Errorf("failed to remove temporary directory for go command tests: %w", rerr)
51 }
52 }()
53
54
55
56 if err := os.WriteFile(filepath.Join(td, "go.mod"), []byte("module gotooltest"), 0600); err != nil {
57 return fmt.Errorf("failed to write temporary go.mod file: %w", err)
58 }
59
60 run := func(args ...string) (*bytes.Buffer, *bytes.Buffer, error) {
61 var stdout, stderr bytes.Buffer
62 cmd := exec.Command(args[0], args[1:]...)
63 cmd.Dir = td
64 cmd.Stdout = &stdout
65 cmd.Stderr = &stderr
66 return &stdout, &stderr, cmd.Run()
67 }
68
69 lout, stderr, err := run("go", "list", "-f={{context.ReleaseTags}}", "runtime")
70 if err != nil {
71 return fmt.Errorf("failed to determine release tags from go command: %v\n%v", err, stderr.String())
72 }
73 tagStr := strings.TrimSpace(lout.String())
74 tagStr = strings.Trim(tagStr, "[]")
75 goEnv.releaseTags = strings.Split(tagStr, " ")
76
77 eout, stderr, err := run("go", "env", "-json",
78 "GOROOT",
79 "GOCACHE",
80 "GOPROXY",
81 )
82 if err != nil {
83 return fmt.Errorf("failed to determine environment from go command: %v\n%v", err, stderr)
84 }
85 if err := json.Unmarshal(eout.Bytes(), &goEnv); err != nil {
86 return fmt.Errorf("failed to unmarshal GOROOT and GOCACHE tags from go command out: %v\n%v", err, eout)
87 }
88
89 version := goEnv.releaseTags[len(goEnv.releaseTags)-1]
90 if !goVersionRegex.MatchString(version) {
91 return fmt.Errorf("invalid go version %q", version)
92 }
93 goEnv.goversion = version[2:]
94
95 return nil
96 }
97
98
99
100
101
102
103
104
105 func Setup(p *testscript.Params) error {
106 goEnv.once.Do(func() {
107 goEnv.err = initGoEnv()
108 })
109 if goEnv.err != nil {
110 return goEnv.err
111 }
112
113 origSetup := p.Setup
114 p.Setup = func(e *testscript.Env) error {
115 e.Vars = goEnviron(e.Vars)
116 if origSetup != nil {
117 return origSetup(e)
118 }
119 return nil
120 }
121 if p.Cmds == nil {
122 p.Cmds = make(map[string]func(ts *testscript.TestScript, neg bool, args []string))
123 }
124 p.Cmds["go"] = cmdGo
125 return nil
126 }
127
128 func goEnviron(env0 []string) []string {
129 env := environ(env0)
130 workdir := env.get("WORK")
131 return append(env, []string{
132 "GOPATH=" + filepath.Join(workdir, ".gopath"),
133 "CCACHE_DISABLE=1",
134 "GOARCH=" + runtime.GOARCH,
135 "GOOS=" + runtime.GOOS,
136 "GOROOT=" + goEnv.GOROOT,
137 "GOCACHE=" + goEnv.GOCACHE,
138 "GOPROXY=" + goEnv.GOPROXY,
139 "goversion=" + goEnv.goversion,
140 }...)
141 }
142
143 func cmdGo(ts *testscript.TestScript, neg bool, args []string) {
144 if len(args) < 1 {
145 ts.Fatalf("usage: go subcommand ...")
146 }
147 err := ts.Exec("go", args...)
148 if err != nil {
149 ts.Logf("[%v]\n", err)
150 if !neg {
151 ts.Fatalf("unexpected go command failure")
152 }
153 } else {
154 if neg {
155 ts.Fatalf("unexpected go command success")
156 }
157 }
158 }
159
160 type environ []string
161
162 func (e0 *environ) get(name string) string {
163 e := *e0
164 for i := len(e) - 1; i >= 0; i-- {
165 v := e[i]
166 if len(v) <= len(name) {
167 continue
168 }
169 if strings.HasPrefix(v, name) && v[len(name)] == '=' {
170 return v[len(name)+1:]
171 }
172 }
173 return ""
174 }
175
View as plain text