1
2
3
4
5 package moddeps_test
6
7 import (
8 "bytes"
9 "encoding/json"
10 "fmt"
11 "internal/testenv"
12 "io"
13 "io/fs"
14 "io/ioutil"
15 "os"
16 "os/exec"
17 "path/filepath"
18 "runtime"
19 "strings"
20 "sync"
21 "testing"
22
23 "golang.org/x/mod/module"
24 )
25
26
27
28
29
30
31
32
33
34
35
36 func TestAllDependencies(t *testing.T) {
37 goBin := testenv.GoToolPath(t)
38
39
40
41
42
43
44
45
46
47
48
49
50 for _, m := range findGorootModules(t) {
51
52
53 t.Run(m.Path+"(quick)", func(t *testing.T) {
54 if m.hasVendor {
55
56
57
58 cmd := exec.Command(goBin, "list", "-mod=vendor", "-deps", "./...")
59 cmd.Env = append(os.Environ(), "GO111MODULE=on")
60 cmd.Dir = m.Dir
61 cmd.Stderr = new(strings.Builder)
62 _, err := cmd.Output()
63 if err != nil {
64 t.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr)
65 t.Logf("(Run 'go mod vendor' in %s to ensure that dependencies have been vendored.)", m.Dir)
66 }
67 return
68 }
69
70
71
72 cmd := exec.Command(goBin, "list", "-mod=readonly", "-m", "all")
73 cmd.Env = append(os.Environ(), "GO111MODULE=on")
74 cmd.Dir = m.Dir
75 cmd.Stderr = new(strings.Builder)
76 out, err := cmd.Output()
77 if err != nil {
78 t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr)
79 }
80 if strings.TrimSpace(string(out)) != m.Path {
81 t.Errorf("'%s' reported active modules other than %s:\n%s", strings.Join(cmd.Args, " "), m.Path, out)
82 t.Logf("(Run 'go mod tidy' in %s to ensure that no extraneous dependencies were added, or 'go mod vendor' to copy in imported packages.)", m.Dir)
83 }
84 })
85 }
86
87
88
89 if testing.Short() {
90 return
91 }
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108 testenv.MustHaveExternalNetwork(t)
109 if haveDiff := func() bool {
110 diff, err := exec.Command("diff", "--recursive", "--unified", ".", ".").CombinedOutput()
111 if err != nil || len(diff) != 0 {
112 return false
113 }
114 diff, err = exec.Command("diff", "--recursive", "--unified", ".", "..").CombinedOutput()
115 if err == nil || len(diff) == 0 {
116 return false
117 }
118 return true
119 }(); !haveDiff {
120
121
122
123
124 t.Skip("skipping because a diff command with support for --recursive and --unified flags is unavailable")
125 }
126
127
128
129
130
131
132 var modcacheEnv []string
133 {
134 out, err := exec.Command(goBin, "env", "GOMODCACHE").Output()
135 if err != nil {
136 t.Fatalf("%s env GOMODCACHE: %v", goBin, err)
137 }
138 modcacheOk := false
139 if gomodcache := string(bytes.TrimSpace(out)); gomodcache != "" {
140 if _, err := os.Stat(gomodcache); err == nil {
141 modcacheOk = true
142 }
143 }
144 if !modcacheOk {
145 modcacheEnv = []string{
146 "GOMODCACHE=" + t.TempDir(),
147 "GOFLAGS=" + os.Getenv("GOFLAGS") + " -modcacherw",
148 }
149 }
150 }
151
152
153
154 bundleDir := t.TempDir()
155 r := runner{
156 Dir: filepath.Join(runtime.GOROOT(), "src/cmd"),
157 Env: append(os.Environ(), modcacheEnv...),
158 }
159 r.run(t, goBin, "build", "-mod=readonly", "-o", bundleDir, "golang.org/x/tools/cmd/bundle")
160
161 var gorootCopyDir string
162 for _, m := range findGorootModules(t) {
163
164
165
166
167
168
169
170
171 if gorootCopyDir == "" {
172 gorootCopyDir = makeGOROOTCopy(t)
173 }
174
175 t.Run(m.Path+"(thorough)", func(t *testing.T) {
176 defer func() {
177 if t.Failed() {
178
179
180
181
182 gorootCopyDir = ""
183 }
184 }()
185
186 rel, err := filepath.Rel(runtime.GOROOT(), m.Dir)
187 if err != nil {
188 t.Fatalf("filepath.Rel(%q, %q): %v", runtime.GOROOT(), m.Dir, err)
189 }
190 r := runner{
191 Dir: filepath.Join(gorootCopyDir, rel),
192 Env: append(append(os.Environ(), modcacheEnv...),
193
194 "GOROOT="+gorootCopyDir,
195
196 "PWD="+filepath.Join(gorootCopyDir, rel),
197 "GOROOT_FINAL=",
198
199 "PATH="+filepath.Join(gorootCopyDir, "bin")+string(filepath.ListSeparator)+
200 bundleDir+string(filepath.ListSeparator)+os.Getenv("PATH"),
201 ),
202 }
203 goBinCopy := filepath.Join(gorootCopyDir, "bin", "go")
204 r.run(t, goBinCopy, "mod", "tidy")
205 r.run(t, goBinCopy, "mod", "verify")
206 r.run(t, goBinCopy, "mod", "vendor")
207 pkgs := packagePattern(m.Path)
208 r.run(t, goBinCopy, "generate", `-run=^//go:generate bundle `, pkgs)
209 advice := "$ cd " + m.Dir + "\n" +
210 "$ go mod tidy # to remove extraneous dependencies\n" +
211 "$ go mod vendor # to vendor dependencies\n" +
212 "$ go generate -run=bundle " + pkgs + " # to regenerate bundled packages\n"
213 if m.Path == "std" {
214 r.run(t, goBinCopy, "generate", "syscall", "internal/syscall/...")
215 advice += "$ go generate syscall internal/syscall/... # to regenerate syscall packages\n"
216 }
217
218
219 diff, err := exec.Command("diff", "--recursive", "--unified", r.Dir, m.Dir).CombinedOutput()
220 if err != nil || len(diff) != 0 {
221 t.Errorf(`Module %s in %s is not tidy (-want +got):
222
223 %s
224 To fix it, run:
225
226 %s
227 (If module %[1]s is definitely tidy, this could mean
228 there's a problem in the go or bundle command.)`, m.Path, m.Dir, diff, advice)
229 }
230 })
231 }
232 }
233
234
235
236 func packagePattern(modulePath string) string {
237 if modulePath == "std" {
238 return "std"
239 }
240 return modulePath + "/..."
241 }
242
243
244
245
246
247
248
249
250
251
252 func makeGOROOTCopy(t *testing.T) string {
253 t.Helper()
254 gorootCopyDir := t.TempDir()
255 err := filepath.Walk(runtime.GOROOT(), func(src string, info os.FileInfo, err error) error {
256 if err != nil {
257 return err
258 }
259 if info.IsDir() && src == filepath.Join(runtime.GOROOT(), ".git") {
260 return filepath.SkipDir
261 }
262
263 rel, err := filepath.Rel(runtime.GOROOT(), src)
264 if err != nil {
265 return fmt.Errorf("filepath.Rel(%q, %q): %v", runtime.GOROOT(), src, err)
266 }
267 dst := filepath.Join(gorootCopyDir, rel)
268
269 if info.IsDir() && (src == filepath.Join(runtime.GOROOT(), "bin") ||
270 src == filepath.Join(runtime.GOROOT(), "pkg")) {
271
272
273 if err := os.Symlink(src, dst); err == nil {
274 return filepath.SkipDir
275 }
276 }
277
278 perm := info.Mode() & os.ModePerm
279 if info.Mode()&os.ModeSymlink != 0 {
280 info, err = os.Stat(src)
281 if err != nil {
282 return err
283 }
284 perm = info.Mode() & os.ModePerm
285 }
286
287
288 if info.IsDir() {
289 return os.MkdirAll(dst, perm|0200)
290 }
291
292
293
294
295 s, err := os.Open(src)
296 if err != nil {
297 return err
298 }
299 defer s.Close()
300 d, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
301 if err != nil {
302 return err
303 }
304 _, err = io.Copy(d, s)
305 if err != nil {
306 d.Close()
307 return err
308 }
309 return d.Close()
310 })
311 if err != nil {
312 t.Fatal(err)
313 }
314 return gorootCopyDir
315 }
316
317 type runner struct {
318 Dir string
319 Env []string
320 }
321
322
323 func (r runner) run(t *testing.T, args ...string) {
324 t.Helper()
325 cmd := exec.Command(args[0], args[1:]...)
326 cmd.Dir = r.Dir
327 cmd.Env = r.Env
328 out, err := cmd.CombinedOutput()
329 if err != nil {
330 t.Logf("> %s\n", strings.Join(args, " "))
331 t.Fatalf("command failed: %s\n%s", err, out)
332 }
333 }
334
335
336
337
338
339
340
341
342
343 func TestDependencyVersionsConsistent(t *testing.T) {
344
345 type requirement struct {
346 Required module.Version
347 Replacement module.Version
348 }
349 seen := map[string]map[requirement][]gorootModule{}
350 for _, m := range findGorootModules(t) {
351 if !m.hasVendor {
352
353 continue
354 }
355
356
357
358
359
360
361
362
363
364 vendor, err := ioutil.ReadFile(filepath.Join(m.Dir, "vendor", "modules.txt"))
365 if err != nil {
366 t.Error(err)
367 continue
368 }
369
370 for _, line := range strings.Split(strings.TrimSpace(string(vendor)), "\n") {
371 parts := strings.Fields(line)
372 if len(parts) < 3 || parts[0] != "#" {
373 continue
374 }
375
376
377 var r requirement
378 r.Required.Path = parts[1]
379 r.Required.Version = parts[2]
380 if len(parts) >= 5 && parts[3] == "=>" {
381 r.Replacement.Path = parts[4]
382 if module.CheckPath(r.Replacement.Path) != nil {
383
384
385
386
387
388
389 t.Errorf("cannot check consistency for filesystem-local replacement in module %s (%s):\n%s", m.Path, m.Dir, line)
390 }
391
392 if len(parts) >= 6 {
393 r.Replacement.Version = parts[5]
394 }
395 }
396
397 if seen[r.Required.Path] == nil {
398 seen[r.Required.Path] = make(map[requirement][]gorootModule)
399 }
400 seen[r.Required.Path][r] = append(seen[r.Required.Path][r], m)
401 }
402 }
403
404
405 for path, versions := range seen {
406 if len(versions) > 1 {
407 t.Errorf("Modules within GOROOT require different versions of %s.", path)
408 for r, mods := range versions {
409 desc := new(strings.Builder)
410 desc.WriteString(r.Required.Version)
411 if r.Replacement.Path != "" {
412 fmt.Fprintf(desc, " => %s", r.Replacement.Path)
413 if r.Replacement.Version != "" {
414 fmt.Fprintf(desc, " %s", r.Replacement.Version)
415 }
416 }
417
418 for _, m := range mods {
419 t.Logf("%s\trequires %v", m.Path, desc)
420 }
421 }
422 }
423 }
424 }
425
426 type gorootModule struct {
427 Path string
428 Dir string
429 hasVendor bool
430 }
431
432
433 func findGorootModules(t *testing.T) []gorootModule {
434 t.Helper()
435 goBin := testenv.GoToolPath(t)
436
437 goroot.once.Do(func() {
438 goroot.err = filepath.WalkDir(runtime.GOROOT(), func(path string, info fs.DirEntry, err error) error {
439 if err != nil {
440 return err
441 }
442 if info.IsDir() && (info.Name() == "vendor" || info.Name() == "testdata") {
443 return filepath.SkipDir
444 }
445 if info.IsDir() && path == filepath.Join(runtime.GOROOT(), "pkg") {
446
447
448
449
450
451 return filepath.SkipDir
452 }
453 if info.IsDir() && (strings.HasPrefix(info.Name(), "_") || strings.HasPrefix(info.Name(), ".")) {
454
455
456
457 return filepath.SkipDir
458 }
459 if info.IsDir() || info.Name() != "go.mod" {
460 return nil
461 }
462 dir := filepath.Dir(path)
463
464
465
466 cmd := exec.Command(goBin, "list", "-json", "-m")
467 cmd.Env = append(os.Environ(), "GO111MODULE=on")
468 cmd.Dir = dir
469 cmd.Stderr = new(strings.Builder)
470 out, err := cmd.Output()
471 if err != nil {
472 return fmt.Errorf("'go list -json -m' in %s: %w\n%s", dir, err, cmd.Stderr)
473 }
474
475 var m gorootModule
476 if err := json.Unmarshal(out, &m); err != nil {
477 return fmt.Errorf("decoding 'go list -json -m' in %s: %w", dir, err)
478 }
479 if m.Path == "" || m.Dir == "" {
480 return fmt.Errorf("'go list -json -m' in %s failed to populate Path and/or Dir", dir)
481 }
482 if _, err := os.Stat(filepath.Join(dir, "vendor")); err == nil {
483 m.hasVendor = true
484 }
485 goroot.modules = append(goroot.modules, m)
486 return nil
487 })
488 if goroot.err != nil {
489 return
490 }
491
492
493
494
495
496 knownGOROOTModules := [...]string{
497 "std",
498 "cmd",
499 "misc",
500 "test/bench/go1",
501 }
502 var seen = make(map[string]bool)
503 for _, m := range goroot.modules {
504 seen[m.Path] = true
505 }
506 for _, m := range knownGOROOTModules {
507 if !seen[m] {
508 goroot.err = fmt.Errorf("findGorootModules didn't find the well-known module %q", m)
509 break
510 }
511 }
512 })
513 if goroot.err != nil {
514 t.Fatal(goroot.err)
515 }
516 return goroot.modules
517 }
518
519
520 var goroot struct {
521 once sync.Once
522 modules []gorootModule
523 err error
524 }
525
View as plain text