# This file demonstrates the effect of lazy loading on the reproducibility of # tests (and tests of test dependencies) outside the main module. # # It is similar to the cases in mod_all.txt and mod_lazy_test_horizon.txt, but # focuses on the effect of "go test" on specific packages instead of the "all" # pattern. # The package import graph used in this test looks like: # # lazy ---- a # | # a_test ---- b # | # b_test ---- c # # And the non-lazy module dependency graph looks like: # # lazy ---- a.1 ---- b.1 ---- c.1 cp go.mod go.mod.old go mod tidy cmp go.mod go.mod.old # In Go 1.15 mode, 'go list -m all' includes modules needed by the # transitive closure of tests of dependencies of tests of dependencies of …. go list -m all stdout '^example.com/b v0.1.0 ' stdout '^example.com/c v0.1.0 ' cmp go.mod go.mod.old # 'go test' (or equivalent) of any such dependency, no matter how remote, does # not update the go.mod file. go list -test -deps example.com/a stdout example.com/b ! stdout example.com/c [!short] go test -c -o $devnull example.com/a [!short] cmp go.mod go.mod.old go list -test -deps example.com/b stdout example.com/c [!short] go test -c -o $devnull example.com/b [!short] cmp go.mod go.mod.old go mod edit -go=1.17 a/go.mod go mod edit -go=1.17 b1/go.mod go mod edit -go=1.17 b2/go.mod go mod edit -go=1.17 c1/go.mod go mod edit -go=1.17 c2/go.mod go mod edit -go=1.17 # After changing to 'go 1.17` uniformly, 'go list -m all' should prune out # example.com/c, because it is not imported by any package (or test of a package) # transitively imported by the main module. # # example.com/a is imported, # and example.com/b is needed in order to run 'go test example.com/a', # but example.com/c is not needed because we don't expect the user to need to run # 'go test example.com/b'. # If we skip directly to adding a new import of c, the dependency is too far # away for a deepening scan to find, which is fine because the package whose # test imported it wasn't even it "all". It should resolve from the latest # version of its module. # However, if we reach c by running successive tests starting from the main # module, we should end up with exactly the version required by b, with an update # to the go.mod file as soon as we test a test dependency that is not itself in # "all". cp go.mod go.mod.117 go mod tidy cmp go.mod go.mod.117 go list -m all stdout '^example.com/b v0.1.0 ' ! stdout '^example.com/c ' # 'go test' of a package (transitively) imported by the main module # should work without changes to the go.mod file. go list -test -deps example.com/a stdout example.com/b ! stdout example.com/c [!short] go test -c -o $devnull example.com/a # However, 'go test' of a package that is itself a dependency should require an # update to the go.mod file. ! go list -test -deps example.com/b # TODO(#36460): The hint here is wrong. We should suggest # 'go get -t example.com/b@v0.1.0' instead of 'go mod tidy'. stderr '^go: updates to go\.mod needed; to update it:\n\tgo mod tidy$' [!short] ! go test -c -o $devnull example.com/b [!short] stderr '^go: updates to go\.mod needed; to update it:\n\tgo mod tidy$' go get -t example.com/b@v0.1.0 go list -test -deps example.com/b stdout example.com/c [!short] go test -c -o $devnull example.com/b # The update should bring the version required by b, not the latest version of c. go list -m example.com/c stdout '^example.com/c v0.1.0 ' cmp go.mod go.mod.b # We should reach the same state if we arrive at it via `go test -mod=mod`. cp go.mod.117 go.mod [short] go list -mod=mod -test -deps example.com/a [!short] go test -mod=mod -c -o $devnull example.com/a [short] go list -mod=mod -test -deps example.com/b [!short] go test -mod=mod -c -o $devnull example.com/b cmp go.mod go.mod.b -- go.mod -- module example.com/lazy go 1.15 require example.com/a v0.1.0 replace ( example.com/a v0.1.0 => ./a example.com/b v0.1.0 => ./b1 example.com/b v0.2.0 => ./b2 example.com/c v0.1.0 => ./c1 example.com/c v0.2.0 => ./c2 ) -- go.mod.b -- module example.com/lazy go 1.17 require example.com/a v0.1.0 require example.com/b v0.1.0 // indirect replace ( example.com/a v0.1.0 => ./a example.com/b v0.1.0 => ./b1 example.com/b v0.2.0 => ./b2 example.com/c v0.1.0 => ./c1 example.com/c v0.2.0 => ./c2 ) -- lazy.go -- package lazy import ( _ "example.com/a" ) -- a/go.mod -- module example.com/a go 1.15 require example.com/b v0.1.0 -- a/a.go -- package a -- a/a_test.go -- package a import ( "testing" _ "example.com/b" ) func TestUsingB(t *testing.T) { // … } -- b1/go.mod -- module example.com/b go 1.15 require example.com/c v0.1.0 -- b1/b.go -- package b -- b1/b_test.go -- package b import _ "example.com/c" -- b2/go.mod -- module example.com/b go 1.15 require example.com/c v0.1.0 -- b2/b.go -- package b This file should not be used, so this syntax error should be ignored. -- b2/b_test.go -- package b This file should not be used, so this syntax error should be ignored. -- c1/go.mod -- module example.com/c go 1.15 -- c1/c.go -- package c -- c2/go.mod -- module example.com/c go 1.15 -- c2/c.go -- package c This file should not be used, so this syntax error should be ignored.