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
5package moddeps_test
6
7import (
8	"bytes"
9	"encoding/json"
10	"fmt"
11	"internal/testenv"
12	"io"
13	"io/fs"
14	"os"
15	"path/filepath"
16	"slices"
17	"sort"
18	"strings"
19	"sync"
20	"testing"
21
22	"golang.org/x/mod/module"
23)
24
25// TestAllDependencies ensures dependencies of all
26// modules in GOROOT are in a consistent state.
27//
28// In short mode, it does a limited quick check and stops there.
29// In long mode, it also makes a copy of the entire GOROOT tree
30// and requires network access to perform more thorough checks.
31// Keep this distinction in mind when adding new checks.
32//
33// See issues 36852, 41409, and 43687.
34// (Also see golang.org/issue/27348.)
35func TestAllDependencies(t *testing.T) {
36	goBin := testenv.GoToolPath(t)
37
38	// Ensure that all packages imported within GOROOT
39	// are vendored in the corresponding GOROOT module.
40	//
41	// This property allows offline development within the Go project, and ensures
42	// that all dependency changes are presented in the usual code review process.
43	//
44	// As a quick first-order check, avoid network access and the need to copy the
45	// entire GOROOT tree or explicitly invoke version control to check for changes.
46	// Just check that packages are vendored. (In non-short mode, we go on to also
47	// copy the GOROOT tree and perform more rigorous consistency checks. Jump below
48	// for more details.)
49	for _, m := range findGorootModules(t) {
50		// This short test does NOT ensure that the vendored contents match
51		// the unmodified contents of the corresponding dependency versions.
52		t.Run(m.Path+"(quick)", func(t *testing.T) {
53			t.Logf("module %s in directory %s", m.Path, m.Dir)
54
55			if m.hasVendor {
56				// Load all of the packages in the module to ensure that their
57				// dependencies are vendored. If any imported package is missing,
58				// 'go list -deps' will fail when attempting to load it.
59				cmd := testenv.Command(t, goBin, "list", "-mod=vendor", "-deps", "./...")
60				cmd.Dir = m.Dir
61				cmd.Env = append(cmd.Environ(), "GO111MODULE=on", "GOWORK=off")
62				cmd.Stderr = new(strings.Builder)
63				_, err := cmd.Output()
64				if err != nil {
65					t.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr)
66					t.Logf("(Run 'go mod vendor' in %s to ensure that dependencies have been vendored.)", m.Dir)
67				}
68				return
69			}
70
71			// There is no vendor directory, so the module must have no dependencies.
72			// Check that the list of active modules contains only the main module.
73			cmd := testenv.Command(t, goBin, "list", "-mod=readonly", "-m", "all")
74			cmd.Dir = m.Dir
75			cmd.Env = append(cmd.Environ(), "GO111MODULE=on", "GOWORK=off")
76			cmd.Stderr = new(strings.Builder)
77			out, err := cmd.Output()
78			if err != nil {
79				t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr)
80			}
81			if strings.TrimSpace(string(out)) != m.Path {
82				t.Errorf("'%s' reported active modules other than %s:\n%s", strings.Join(cmd.Args, " "), m.Path, out)
83				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)
84			}
85		})
86	}
87
88	// We now get to the slow, but more thorough part of the test.
89	// Only run it in long test mode.
90	if testing.Short() {
91		return
92	}
93
94	// Ensure that all modules within GOROOT are tidy, vendored, and bundled.
95	// Ensure that the vendored contents match the unmodified contents of the
96	// corresponding dependency versions.
97	//
98	// The non-short section of this test requires network access and the diff
99	// command.
100	//
101	// It makes a temporary copy of the entire GOROOT tree (where it can safely
102	// perform operations that may mutate the tree), executes the same module
103	// maintenance commands that we expect Go developers to run, and then
104	// diffs the potentially modified module copy with the real one in GOROOT.
105	// (We could try to rely on Git to do things differently, but that's not the
106	// path we've chosen at this time. This allows the test to run when the tree
107	// is not checked into Git.)
108
109	testenv.MustHaveExternalNetwork(t)
110	if haveDiff := func() bool {
111		diff, err := testenv.Command(t, "diff", "--recursive", "--unified", ".", ".").CombinedOutput()
112		if err != nil || len(diff) != 0 {
113			return false
114		}
115		diff, err = testenv.Command(t, "diff", "--recursive", "--unified", ".", "..").CombinedOutput()
116		if err == nil || len(diff) == 0 {
117			return false
118		}
119		return true
120	}(); !haveDiff {
121		// For now, the diff command is a mandatory dependency of this test.
122		// This test will primarily run on longtest builders, since few people
123		// would test the cmd/internal/moddeps package directly, and all.bash
124		// runs tests in short mode. It's fine to skip if diff is unavailable.
125		t.Skip("skipping because a diff command with support for --recursive and --unified flags is unavailable")
126	}
127
128	// We're going to check the standard modules for tidiness, so we need a usable
129	// GOMODCACHE. If the default directory doesn't exist, use a temporary
130	// directory instead. (That can occur, for example, when running under
131	// run.bash with GO_TEST_SHORT=0: run.bash sets GOPATH=/nonexist-gopath, and
132	// GO_TEST_SHORT=0 causes it to run this portion of the test.)
133	var modcacheEnv []string
134	{
135		out, err := testenv.Command(t, goBin, "env", "GOMODCACHE").Output()
136		if err != nil {
137			t.Fatalf("%s env GOMODCACHE: %v", goBin, err)
138		}
139		modcacheOk := false
140		if gomodcache := string(bytes.TrimSpace(out)); gomodcache != "" {
141			if _, err := os.Stat(gomodcache); err == nil {
142				modcacheOk = true
143			}
144		}
145		if !modcacheOk {
146			modcacheEnv = []string{
147				"GOMODCACHE=" + t.TempDir(),
148				"GOFLAGS=" + os.Getenv("GOFLAGS") + " -modcacherw", // Allow t.TempDir() to clean up subdirectories.
149			}
150		}
151	}
152
153	// Build the bundle binary at the golang.org/x/tools
154	// module version specified in GOROOT/src/cmd/go.mod.
155	bundleDir := t.TempDir()
156	r := runner{
157		Dir: filepath.Join(testenv.GOROOT(t), "src/cmd"),
158		Env: append(os.Environ(), modcacheEnv...),
159	}
160	r.run(t, goBin, "build", "-mod=readonly", "-o", bundleDir, "golang.org/x/tools/cmd/bundle")
161
162	var gorootCopyDir string
163	for _, m := range findGorootModules(t) {
164		// Create a test-wide GOROOT copy. It can be created once
165		// and reused between subtests whenever they don't fail.
166		//
167		// This is a relatively expensive operation, but it's a pre-requisite to
168		// be able to safely run commands like "go mod tidy", "go mod vendor", and
169		// "go generate" on the GOROOT tree content. Those commands may modify the
170		// tree, and we don't want to happen to the real tree as part of executing
171		// a test.
172		if gorootCopyDir == "" {
173			gorootCopyDir = makeGOROOTCopy(t)
174		}
175
176		t.Run(m.Path+"(thorough)", func(t *testing.T) {
177			t.Logf("module %s in directory %s", m.Path, m.Dir)
178
179			defer func() {
180				if t.Failed() {
181					// The test failed, which means it's possible the GOROOT copy
182					// may have been modified. No choice but to reset it for next
183					// module test case. (This is slow, but it happens only during
184					// test failures.)
185					gorootCopyDir = ""
186				}
187			}()
188
189			rel, err := filepath.Rel(testenv.GOROOT(t), m.Dir)
190			if err != nil {
191				t.Fatalf("filepath.Rel(%q, %q): %v", testenv.GOROOT(t), m.Dir, err)
192			}
193			r := runner{
194				Dir: filepath.Join(gorootCopyDir, rel),
195				Env: append(append(os.Environ(), modcacheEnv...),
196					// Set GOROOT.
197					"GOROOT="+gorootCopyDir,
198					// Add GOROOTcopy/bin and bundleDir to front of PATH.
199					"PATH="+filepath.Join(gorootCopyDir, "bin")+string(filepath.ListSeparator)+
200						bundleDir+string(filepath.ListSeparator)+os.Getenv("PATH"),
201					"GOWORK=off",
202				),
203			}
204			goBinCopy := filepath.Join(gorootCopyDir, "bin", "go")
205			r.run(t, goBinCopy, "mod", "tidy")   // See issue 43687.
206			r.run(t, goBinCopy, "mod", "verify") // Verify should be a no-op, but test it just in case.
207			r.run(t, goBinCopy, "mod", "vendor") // See issue 36852.
208			pkgs := packagePattern(m.Path)
209			r.run(t, goBinCopy, "generate", `-run=^//go:generate bundle `, pkgs) // See issue 41409.
210			advice := "$ cd " + m.Dir + "\n" +
211				"$ go mod tidy                               # to remove extraneous dependencies\n" +
212				"$ go mod vendor                             # to vendor dependencies\n" +
213				"$ go generate -run=bundle " + pkgs + "               # to regenerate bundled packages\n"
214			if m.Path == "std" {
215				r.run(t, goBinCopy, "generate", "syscall", "internal/syscall/...") // See issue 43440.
216				advice += "$ go generate syscall internal/syscall/...  # to regenerate syscall packages\n"
217			}
218			// TODO(golang.org/issue/43440): Check anything else influenced by dependency versions.
219
220			diff, err := testenv.Command(t, "diff", "--recursive", "--unified", r.Dir, m.Dir).CombinedOutput()
221			if err != nil || len(diff) != 0 {
222				t.Errorf(`Module %s in %s is not tidy (-want +got):
223
224%s
225To fix it, run:
226
227%s
228(If module %[1]s is definitely tidy, this could mean
229there's a problem in the go or bundle command.)`, m.Path, m.Dir, diff, advice)
230			}
231		})
232	}
233}
234
235// packagePattern returns a package pattern that matches all packages
236// in the module modulePath, and ideally as few others as possible.
237func packagePattern(modulePath string) string {
238	if modulePath == "std" {
239		return "std"
240	}
241	return modulePath + "/..."
242}
243
244// makeGOROOTCopy makes a temporary copy of the current GOROOT tree.
245// The goal is to allow the calling test t to safely mutate a GOROOT
246// copy without also modifying the original GOROOT.
247//
248// It copies the entire tree as is, with the exception of the GOROOT/.git
249// directory, which is skipped, and the GOROOT/{bin,pkg} directories,
250// which are symlinked. This is done for speed, since a GOROOT tree is
251// functional without being in a Git repository, and bin and pkg are
252// deemed safe to share for the purpose of the TestAllDependencies test.
253func makeGOROOTCopy(t *testing.T) string {
254	t.Helper()
255
256	gorootCopyDir := t.TempDir()
257	err := filepath.Walk(testenv.GOROOT(t), func(src string, info os.FileInfo, err error) error {
258		if err != nil {
259			return err
260		}
261		if info.IsDir() && src == filepath.Join(testenv.GOROOT(t), ".git") {
262			return filepath.SkipDir
263		}
264
265		rel, err := filepath.Rel(testenv.GOROOT(t), src)
266		if err != nil {
267			return fmt.Errorf("filepath.Rel(%q, %q): %v", testenv.GOROOT(t), src, err)
268		}
269		dst := filepath.Join(gorootCopyDir, rel)
270
271		if info.IsDir() && (src == filepath.Join(testenv.GOROOT(t), "bin") ||
272			src == filepath.Join(testenv.GOROOT(t), "pkg")) {
273			// If the OS supports symlinks, use them instead
274			// of copying the bin and pkg directories.
275			if err := os.Symlink(src, dst); err == nil {
276				return filepath.SkipDir
277			}
278		}
279
280		perm := info.Mode() & os.ModePerm
281		if info.Mode()&os.ModeSymlink != 0 {
282			info, err = os.Stat(src)
283			if err != nil {
284				return err
285			}
286			perm = info.Mode() & os.ModePerm
287		}
288
289		// If it's a directory, make a corresponding directory.
290		if info.IsDir() {
291			return os.MkdirAll(dst, perm|0200)
292		}
293
294		// Copy the file bytes.
295		// We can't create a symlink because the file may get modified;
296		// we need to ensure that only the temporary copy is affected.
297		s, err := os.Open(src)
298		if err != nil {
299			return err
300		}
301		defer s.Close()
302		d, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
303		if err != nil {
304			return err
305		}
306		_, err = io.Copy(d, s)
307		if err != nil {
308			d.Close()
309			return err
310		}
311		return d.Close()
312	})
313	if err != nil {
314		t.Fatal(err)
315	}
316	t.Logf("copied GOROOT from %s to %s", testenv.GOROOT(t), gorootCopyDir)
317	return gorootCopyDir
318}
319
320type runner struct {
321	Dir string
322	Env []string
323}
324
325// run runs the command and requires that it succeeds.
326func (r runner) run(t *testing.T, args ...string) {
327	t.Helper()
328	cmd := testenv.Command(t, args[0], args[1:]...)
329	cmd.Dir = r.Dir
330	cmd.Env = slices.Clip(r.Env)
331	if r.Dir != "" {
332		cmd.Env = append(cmd.Env, "PWD="+r.Dir)
333	}
334	out, err := cmd.CombinedOutput()
335	if err != nil {
336		t.Logf("> %s\n", strings.Join(args, " "))
337		t.Fatalf("command failed: %s\n%s", err, out)
338	}
339}
340
341// TestDependencyVersionsConsistent verifies that each module in GOROOT that
342// requires a given external dependency requires the same version of that
343// dependency.
344//
345// This property allows us to maintain a single release branch of each such
346// dependency, minimizing the number of backports needed to pull in critical
347// fixes. It also ensures that any bug detected and fixed in one GOROOT module
348// (such as "std") is fixed in all other modules (such as "cmd") as well.
349func TestDependencyVersionsConsistent(t *testing.T) {
350	// Collect the dependencies of all modules in GOROOT, indexed by module path.
351	type requirement struct {
352		Required    module.Version
353		Replacement module.Version
354	}
355	seen := map[string]map[requirement][]gorootModule{} // module path → requirement → set of modules with that requirement
356	for _, m := range findGorootModules(t) {
357		if !m.hasVendor {
358			// TestAllDependencies will ensure that the module has no dependencies.
359			continue
360		}
361
362		// We want this test to be able to run offline and with an empty module
363		// cache, so we verify consistency only for the module versions listed in
364		// vendor/modules.txt. That includes all direct dependencies and all modules
365		// that provide any imported packages.
366		//
367		// It's ok if there are undetected differences in modules that do not
368		// provide imported packages: we will not have to pull in any backports of
369		// fixes to those modules anyway.
370		vendor, err := os.ReadFile(filepath.Join(m.Dir, "vendor", "modules.txt"))
371		if err != nil {
372			t.Error(err)
373			continue
374		}
375
376		for _, line := range strings.Split(strings.TrimSpace(string(vendor)), "\n") {
377			parts := strings.Fields(line)
378			if len(parts) < 3 || parts[0] != "#" {
379				continue
380			}
381
382			// This line is of the form "# module version [=> replacement [version]]".
383			var r requirement
384			r.Required.Path = parts[1]
385			r.Required.Version = parts[2]
386			if len(parts) >= 5 && parts[3] == "=>" {
387				r.Replacement.Path = parts[4]
388				if module.CheckPath(r.Replacement.Path) != nil {
389					// If the replacement is a filesystem path (rather than a module path),
390					// we don't know whether the filesystem contents have changed since
391					// the module was last vendored.
392					//
393					// Fortunately, we do not currently use filesystem-local replacements
394					// in GOROOT modules.
395					t.Errorf("cannot check consistency for filesystem-local replacement in module %s (%s):\n%s", m.Path, m.Dir, line)
396				}
397
398				if len(parts) >= 6 {
399					r.Replacement.Version = parts[5]
400				}
401			}
402
403			if seen[r.Required.Path] == nil {
404				seen[r.Required.Path] = make(map[requirement][]gorootModule)
405			}
406			seen[r.Required.Path][r] = append(seen[r.Required.Path][r], m)
407		}
408	}
409
410	// Now verify that we saw only one distinct version for each module.
411	for path, versions := range seen {
412		if len(versions) > 1 {
413			t.Errorf("Modules within GOROOT require different versions of %s.", path)
414			for r, mods := range versions {
415				desc := new(strings.Builder)
416				desc.WriteString(r.Required.Version)
417				if r.Replacement.Path != "" {
418					fmt.Fprintf(desc, " => %s", r.Replacement.Path)
419					if r.Replacement.Version != "" {
420						fmt.Fprintf(desc, " %s", r.Replacement.Version)
421					}
422				}
423
424				for _, m := range mods {
425					t.Logf("%s\trequires %v", m.Path, desc)
426				}
427			}
428		}
429	}
430}
431
432type gorootModule struct {
433	Path      string
434	Dir       string
435	hasVendor bool
436}
437
438// findGorootModules returns the list of modules found in the GOROOT source tree.
439func findGorootModules(t *testing.T) []gorootModule {
440	t.Helper()
441	goBin := testenv.GoToolPath(t)
442
443	goroot.once.Do(func() {
444		// If the root itself is a symlink to a directory,
445		// we want to follow it (see https://go.dev/issue/64375).
446		// Add a trailing separator to force that to happen.
447		root := testenv.GOROOT(t)
448		if !os.IsPathSeparator(root[len(root)-1]) {
449			root += string(filepath.Separator)
450		}
451		goroot.err = filepath.WalkDir(root, func(path string, info fs.DirEntry, err error) error {
452			if err != nil {
453				return err
454			}
455			if info.IsDir() && path != root && (info.Name() == "vendor" || info.Name() == "testdata") {
456				return filepath.SkipDir
457			}
458			if info.IsDir() && path == filepath.Join(testenv.GOROOT(t), "pkg") {
459				// GOROOT/pkg contains generated artifacts, not source code.
460				//
461				// In https://golang.org/issue/37929 it was observed to somehow contain
462				// a module cache, so it is important to skip. (That helps with the
463				// running time of this test anyway.)
464				return filepath.SkipDir
465			}
466			if info.IsDir() && path != root && (strings.HasPrefix(info.Name(), "_") || strings.HasPrefix(info.Name(), ".")) {
467				// _ and . prefixed directories can be used for internal modules
468				// without a vendor directory that don't contribute to the build
469				// but might be used for example as code generators.
470				return filepath.SkipDir
471			}
472			if info.IsDir() || info.Name() != "go.mod" {
473				return nil
474			}
475			dir := filepath.Dir(path)
476
477			// Use 'go list' to describe the module contained in this directory (but
478			// not its dependencies).
479			cmd := testenv.Command(t, goBin, "list", "-json", "-m")
480			cmd.Dir = dir
481			cmd.Env = append(cmd.Environ(), "GO111MODULE=on", "GOWORK=off")
482			cmd.Stderr = new(strings.Builder)
483			out, err := cmd.Output()
484			if err != nil {
485				return fmt.Errorf("'go list -json -m' in %s: %w\n%s", dir, err, cmd.Stderr)
486			}
487
488			var m gorootModule
489			if err := json.Unmarshal(out, &m); err != nil {
490				return fmt.Errorf("decoding 'go list -json -m' in %s: %w", dir, err)
491			}
492			if m.Path == "" || m.Dir == "" {
493				return fmt.Errorf("'go list -json -m' in %s failed to populate Path and/or Dir", dir)
494			}
495			if _, err := os.Stat(filepath.Join(dir, "vendor")); err == nil {
496				m.hasVendor = true
497			}
498			goroot.modules = append(goroot.modules, m)
499			return nil
500		})
501		if goroot.err != nil {
502			return
503		}
504
505		// knownGOROOTModules is a hard-coded list of modules that are known to exist in GOROOT.
506		// If findGorootModules doesn't find a module, it won't be covered by tests at all,
507		// so make sure at least these modules are found. See issue 46254. If this list
508		// becomes a nuisance to update, can be replaced with len(goroot.modules) check.
509		knownGOROOTModules := [...]string{
510			"std",
511			"cmd",
512			// The "misc" module sometimes exists, but cmd/distpack intentionally removes it.
513		}
514		var seen = make(map[string]bool) // Key is module path.
515		for _, m := range goroot.modules {
516			seen[m.Path] = true
517		}
518		for _, m := range knownGOROOTModules {
519			if !seen[m] {
520				goroot.err = fmt.Errorf("findGorootModules didn't find the well-known module %q", m)
521				break
522			}
523		}
524		sort.Slice(goroot.modules, func(i, j int) bool {
525			return goroot.modules[i].Dir < goroot.modules[j].Dir
526		})
527	})
528	if goroot.err != nil {
529		t.Fatal(goroot.err)
530	}
531	return goroot.modules
532}
533
534// goroot caches the list of modules found in the GOROOT source tree.
535var goroot struct {
536	once    sync.Once
537	modules []gorootModule
538	err     error
539}
540