mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
cmd/go: add tests for convergence in 'go mod tidy'
For #36460 Change-Id: I40194bb1ebab77459e5c9d43bdac4d9c1b826ac2 Reviewed-on: https://go-review.googlesource.com/c/go/+/312449 Trust: Bryan C. Mills <bcmills@google.com> Run-TryBot: Bryan C. Mills <bcmills@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
434e12f772
commit
3cc3a16029
2 changed files with 446 additions and 0 deletions
174
src/cmd/go/testdata/script/mod_tidy_convergence.txt
vendored
Normal file
174
src/cmd/go/testdata/script/mod_tidy_convergence.txt
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
# This test demonstrates a simple case in which 'go mod tidy' may resolve a
|
||||||
|
# missing package, only to remove that package when resolving its dependencies.
|
||||||
|
#
|
||||||
|
# If we naively iterate 'go mod tidy' until the dependency graph converges, this
|
||||||
|
# scenario may fail to converge.
|
||||||
|
|
||||||
|
# The import graph used in this test looks like:
|
||||||
|
#
|
||||||
|
# m --- x
|
||||||
|
# |
|
||||||
|
# x_test --- y
|
||||||
|
#
|
||||||
|
# The module dependency graph of m is initially empty.
|
||||||
|
# Modules x and y look like:
|
||||||
|
#
|
||||||
|
# x.1 (provides package x that imports y, but does not depend on module y)
|
||||||
|
#
|
||||||
|
# x.2-pre (no dependencies, but does not provide package x)
|
||||||
|
#
|
||||||
|
# y.1 (no dependencies, but provides package y)
|
||||||
|
#
|
||||||
|
# y.2 --- x.2-pre (provides package y)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# When we resolve the missing import of y in x_test, we add y@latest — which is
|
||||||
|
# y.2, not y.1 — as a new dependency. That upgrades to x to x.2-pre, which
|
||||||
|
# removes package x (and also the need for module y). We can then safely remove
|
||||||
|
# the dependency on module y, because nothing imports package y any more!
|
||||||
|
#
|
||||||
|
# We might be tempted to remove the dependency on module x for the same reason:
|
||||||
|
# it no longer provides any imported package. However, that would cause 'go mod
|
||||||
|
# tidy -e' to become unstable: with x.2-pre out of the way, we could once again
|
||||||
|
# resolve the missing import of package x by re-adding x.1.
|
||||||
|
|
||||||
|
cp go.mod go.mod.orig
|
||||||
|
|
||||||
|
# 'go mod tidy' without -e should fail without modifying go.mod,
|
||||||
|
# because it cannot resolve x and y simultaneously.
|
||||||
|
! go mod tidy
|
||||||
|
|
||||||
|
cmp go.mod go.mod.orig
|
||||||
|
|
||||||
|
stderr '^go: found example\.net/y in example\.net/y v0.2.0$'
|
||||||
|
stderr '^go: finding module for package example\.net/x$'
|
||||||
|
|
||||||
|
# TODO: This error message should be clearer — it doesn't indicate why v0.2.0-pre is required.
|
||||||
|
stderr '^example\.net/m imports\n\texample\.net/x: package example\.net/x provided by example\.net/x at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
|
||||||
|
|
||||||
|
|
||||||
|
# 'go mod tidy -e' should follow upgrades to try to resolve the modules that it
|
||||||
|
# can, and then stop. When we resolve example.net/y, we upgrade to example.net/x
|
||||||
|
# to v0.2.0-pre. At that version, package x no longer exists and no longer
|
||||||
|
# imports package y, so the import of x should be left unsatisfied and the
|
||||||
|
# existing dependency on example.net/x removed.
|
||||||
|
#
|
||||||
|
# TODO(bcmills): It would be ever better if we could keep the original
|
||||||
|
# dependency on example.net/x v0.1.0, but I don't see a way to do that without
|
||||||
|
# making the algorithm way too complicated. (We would have to detect that the
|
||||||
|
# new dependency on example.net/y interferes with the package that caused us to
|
||||||
|
# to add that dependency in the first place, and back out that part of the change
|
||||||
|
# without also backing out any other needed changes.)
|
||||||
|
|
||||||
|
go mod tidy -e
|
||||||
|
cmp go.mod go.mod.tidye
|
||||||
|
stderr '^go: found example\.net/y in example\.net/y v0.2.0$'
|
||||||
|
|
||||||
|
# TODO: This error message should be clearer — it doesn't indicate why v0.2.0-pre is required.
|
||||||
|
stderr '^example\.net/m imports\n\texample\.net/x: package example\.net/x provided by example\.net/x at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
|
||||||
|
|
||||||
|
|
||||||
|
# Since we attempt to resolve the dependencies of package x whenever we add x itself,
|
||||||
|
# this end state is stable.
|
||||||
|
|
||||||
|
go mod tidy -e
|
||||||
|
cmp go.mod go.mod.tidye
|
||||||
|
|
||||||
|
|
||||||
|
# An explicit 'go get' with the correct versions should allow 'go mod tidy' to
|
||||||
|
# succeed and remain stable. y.1 does not upgrade x, and can therefore be used
|
||||||
|
# with it.
|
||||||
|
|
||||||
|
go get -d example.net/x@v0.1.0 example.net/y@v0.1.0
|
||||||
|
go mod tidy
|
||||||
|
cmp go.mod go.mod.postget
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO(#36460): Repeat this test with a lazy main module.
|
||||||
|
|
||||||
|
|
||||||
|
-- go.mod --
|
||||||
|
module example.net/m
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
replace (
|
||||||
|
example.net/x v0.1.0 => ./x1
|
||||||
|
example.net/x v0.2.0-pre => ./x2-pre
|
||||||
|
example.net/y v0.1.0 => ./y1
|
||||||
|
example.net/y v0.2.0 => ./y2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
example.net/x v0.1.0
|
||||||
|
)
|
||||||
|
-- go.mod.tidye --
|
||||||
|
module example.net/m
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
replace (
|
||||||
|
example.net/x v0.1.0 => ./x1
|
||||||
|
example.net/x v0.2.0-pre => ./x2-pre
|
||||||
|
example.net/y v0.1.0 => ./y1
|
||||||
|
example.net/y v0.2.0 => ./y2
|
||||||
|
)
|
||||||
|
-- go.mod.postget --
|
||||||
|
module example.net/m
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
replace (
|
||||||
|
example.net/x v0.1.0 => ./x1
|
||||||
|
example.net/x v0.2.0-pre => ./x2-pre
|
||||||
|
example.net/y v0.1.0 => ./y1
|
||||||
|
example.net/y v0.2.0 => ./y2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
example.net/x v0.1.0
|
||||||
|
example.net/y v0.1.0 // indirect
|
||||||
|
)
|
||||||
|
-- m.go --
|
||||||
|
package m
|
||||||
|
|
||||||
|
import _ "example.net/x"
|
||||||
|
|
||||||
|
-- x1/go.mod --
|
||||||
|
module example.net/x
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
-- x1/x.go --
|
||||||
|
package x
|
||||||
|
-- x1/x_test.go --
|
||||||
|
package x
|
||||||
|
|
||||||
|
import _ "example.net/y"
|
||||||
|
|
||||||
|
-- x2-pre/go.mod --
|
||||||
|
module example.net/x
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
-- x2-pre/README.txt --
|
||||||
|
There is no package x here. Use example.com/x/subpkg instead.
|
||||||
|
-- x2-pre/subpkg/subpkg.go --
|
||||||
|
package subpkg // import "example.net/x/subpkg"
|
||||||
|
|
||||||
|
-- y1/go.mod --
|
||||||
|
module example.net/y
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
-- y1/y.go --
|
||||||
|
package y
|
||||||
|
|
||||||
|
-- y2/go.mod --
|
||||||
|
module example.net/y
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require example.net/x v0.2.0-pre
|
||||||
|
-- y2/y.go --
|
||||||
|
package y
|
||||||
|
|
||||||
|
import _ "example.net/x/subpkg"
|
||||||
272
src/cmd/go/testdata/script/mod_tidy_convergence_loop.txt
vendored
Normal file
272
src/cmd/go/testdata/script/mod_tidy_convergence_loop.txt
vendored
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
# This test demonstrates a simple case in which 'go mod tidy' may resolve a
|
||||||
|
# missing package, only to remove that package when resolving its dependencies.
|
||||||
|
#
|
||||||
|
# If we naively iterate 'go mod tidy' until the dependency graph converges, this
|
||||||
|
# scenario may fail to converge.
|
||||||
|
|
||||||
|
# The import graph used in this test looks like:
|
||||||
|
#
|
||||||
|
# m --- w
|
||||||
|
# |
|
||||||
|
# + --- x
|
||||||
|
# |
|
||||||
|
# + --- y
|
||||||
|
# |
|
||||||
|
# + --- z
|
||||||
|
#
|
||||||
|
# The module dependency graph of m initially contains w.1 (and, by extension,
|
||||||
|
# y.2-pre and z.2-pre). This is an arbitrary point in the cycle of possible
|
||||||
|
# configurations.
|
||||||
|
#
|
||||||
|
# w.1 requires y.2-pre and z.2-pre
|
||||||
|
# x.1 requires z.2-pre and w.2-pre
|
||||||
|
# y.1 requires w.2-pre and x.2-pre
|
||||||
|
# z.1 requires x.2-pre and y.2-pre
|
||||||
|
#
|
||||||
|
# At each point, exactly one missing package can be resolved by adding a
|
||||||
|
# dependency on the .1 release of the module that provides that package.
|
||||||
|
# However, adding that dependency causes the module providing another package to
|
||||||
|
# roll over from its .1 release to its .2-pre release, which removes the
|
||||||
|
# package. Once the package is removed, 'go mod tidy -e' no longer sees the
|
||||||
|
# module as relevant to the main module, and will happily remove the existing
|
||||||
|
# dependency on it.
|
||||||
|
#
|
||||||
|
# The cycle is of length 4 so that at every step only one package can be
|
||||||
|
# resolved. This is important because it prevents the iteration from ever
|
||||||
|
# reaching a state in which every package is simultaneously over-upgraded — such
|
||||||
|
# a state is stable and does not exhibit failure to converge.
|
||||||
|
|
||||||
|
cp go.mod go.mod.orig
|
||||||
|
|
||||||
|
# 'go mod tidy' without -e should fail without modifying go.mod,
|
||||||
|
# because it cannot resolve x, y, and z simultaneously.
|
||||||
|
! go mod tidy
|
||||||
|
|
||||||
|
cmp go.mod go.mod.orig
|
||||||
|
|
||||||
|
stderr '^go: finding module for package example\.net/w$'
|
||||||
|
stderr '^go: finding module for package example\.net/x$'
|
||||||
|
stderr -count=2 '^go: finding module for package example\.net/y$'
|
||||||
|
stderr -count=2 '^go: finding module for package example\.net/z$'
|
||||||
|
stderr '^go: found example\.net/x in example\.net/x v0.1.0$'
|
||||||
|
|
||||||
|
# TODO: These error messages should be clearer — it doesn't indicate why v0.2.0-pre is required.
|
||||||
|
stderr '^example\.net/m imports\n\texample\.net/w: package example\.net/w provided by example\.net/w at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
|
||||||
|
stderr '^example\.net/m imports\n\texample\.net/y: package example\.net/y provided by example\.net/y at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
|
||||||
|
stderr '^example\.net/m imports\n\texample\.net/z: package example\.net/z provided by example\.net/z at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
|
||||||
|
|
||||||
|
|
||||||
|
# 'go mod tidy -e' should preserve all of the upgrades to modules that could
|
||||||
|
# provide the missing packages but don't. That would at least explain why they
|
||||||
|
# are missing, and why no individual module can be upgraded in order to satisfy
|
||||||
|
# a missing import.
|
||||||
|
#
|
||||||
|
# TODO(bcmills): Today, it doesn't preserve those upgrades, and instead advances
|
||||||
|
# the state by one through the cycle of semi-tidy states.
|
||||||
|
|
||||||
|
go mod tidy -e
|
||||||
|
|
||||||
|
cmp go.mod go.mod.tidye1
|
||||||
|
|
||||||
|
stderr '^go: finding module for package example\.net/w$'
|
||||||
|
stderr '^go: finding module for package example\.net/x$'
|
||||||
|
stderr -count=2 '^go: finding module for package example\.net/y$'
|
||||||
|
stderr -count=2 '^go: finding module for package example\.net/z$'
|
||||||
|
stderr '^go: found example\.net/x in example\.net/x v0.1.0$'
|
||||||
|
|
||||||
|
stderr '^example\.net/m imports\n\texample\.net/w: package example\.net/w provided by example\.net/w at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
|
||||||
|
stderr '^example\.net/m imports\n\texample\.net/y: package example\.net/y provided by example\.net/y at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
|
||||||
|
stderr '^example\.net/m imports\n\texample\.net/z: package example\.net/z provided by example\.net/z at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
|
||||||
|
|
||||||
|
|
||||||
|
go mod tidy -e
|
||||||
|
cmp go.mod go.mod.tidye2
|
||||||
|
|
||||||
|
go mod tidy -e
|
||||||
|
cmp go.mod go.mod.tidye3
|
||||||
|
|
||||||
|
go mod tidy -e
|
||||||
|
cmp go.mod go.mod.orig
|
||||||
|
|
||||||
|
|
||||||
|
# If we upgrade away all of the packages simultaneously, the resulting tidy
|
||||||
|
# state converges at "no dependencies", because simultaneously adding all of the
|
||||||
|
# packages simultaneously over-upgrades all of the dependencies, and 'go mod
|
||||||
|
# tidy' treats "no package can be added" as a terminal state.
|
||||||
|
|
||||||
|
go get -d example.net/w@v0.2.0-pre example.net/x@v0.2.0-pre example.net/y@v0.2.0-pre example.net/z@v0.2.0-pre
|
||||||
|
go mod tidy -e
|
||||||
|
cmp go.mod go.mod.postget
|
||||||
|
go mod tidy -e
|
||||||
|
cmp go.mod go.mod.postget
|
||||||
|
|
||||||
|
|
||||||
|
# TODO(#36460): Repeat this test with a lazy main module.
|
||||||
|
|
||||||
|
|
||||||
|
-- m.go --
|
||||||
|
package m
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "example.net/w"
|
||||||
|
_ "example.net/x"
|
||||||
|
_ "example.net/y"
|
||||||
|
_ "example.net/z"
|
||||||
|
)
|
||||||
|
|
||||||
|
-- go.mod --
|
||||||
|
module example.net/m
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
replace (
|
||||||
|
example.net/w v0.1.0 => ./w1
|
||||||
|
example.net/w v0.2.0-pre => ./w2-pre
|
||||||
|
example.net/x v0.1.0 => ./x1
|
||||||
|
example.net/x v0.2.0-pre => ./x2-pre
|
||||||
|
example.net/y v0.1.0 => ./y1
|
||||||
|
example.net/y v0.2.0-pre => ./y2-pre
|
||||||
|
example.net/z v0.1.0 => ./z1
|
||||||
|
example.net/z v0.2.0-pre => ./z2-pre
|
||||||
|
)
|
||||||
|
|
||||||
|
require example.net/w v0.1.0
|
||||||
|
-- go.mod.tidye1 --
|
||||||
|
module example.net/m
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
replace (
|
||||||
|
example.net/w v0.1.0 => ./w1
|
||||||
|
example.net/w v0.2.0-pre => ./w2-pre
|
||||||
|
example.net/x v0.1.0 => ./x1
|
||||||
|
example.net/x v0.2.0-pre => ./x2-pre
|
||||||
|
example.net/y v0.1.0 => ./y1
|
||||||
|
example.net/y v0.2.0-pre => ./y2-pre
|
||||||
|
example.net/z v0.1.0 => ./z1
|
||||||
|
example.net/z v0.2.0-pre => ./z2-pre
|
||||||
|
)
|
||||||
|
|
||||||
|
require example.net/x v0.1.0
|
||||||
|
-- go.mod.tidye2 --
|
||||||
|
module example.net/m
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
replace (
|
||||||
|
example.net/w v0.1.0 => ./w1
|
||||||
|
example.net/w v0.2.0-pre => ./w2-pre
|
||||||
|
example.net/x v0.1.0 => ./x1
|
||||||
|
example.net/x v0.2.0-pre => ./x2-pre
|
||||||
|
example.net/y v0.1.0 => ./y1
|
||||||
|
example.net/y v0.2.0-pre => ./y2-pre
|
||||||
|
example.net/z v0.1.0 => ./z1
|
||||||
|
example.net/z v0.2.0-pre => ./z2-pre
|
||||||
|
)
|
||||||
|
|
||||||
|
require example.net/y v0.1.0
|
||||||
|
-- go.mod.tidye3 --
|
||||||
|
module example.net/m
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
replace (
|
||||||
|
example.net/w v0.1.0 => ./w1
|
||||||
|
example.net/w v0.2.0-pre => ./w2-pre
|
||||||
|
example.net/x v0.1.0 => ./x1
|
||||||
|
example.net/x v0.2.0-pre => ./x2-pre
|
||||||
|
example.net/y v0.1.0 => ./y1
|
||||||
|
example.net/y v0.2.0-pre => ./y2-pre
|
||||||
|
example.net/z v0.1.0 => ./z1
|
||||||
|
example.net/z v0.2.0-pre => ./z2-pre
|
||||||
|
)
|
||||||
|
|
||||||
|
require example.net/z v0.1.0
|
||||||
|
-- go.mod.postget --
|
||||||
|
module example.net/m
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
replace (
|
||||||
|
example.net/w v0.1.0 => ./w1
|
||||||
|
example.net/w v0.2.0-pre => ./w2-pre
|
||||||
|
example.net/x v0.1.0 => ./x1
|
||||||
|
example.net/x v0.2.0-pre => ./x2-pre
|
||||||
|
example.net/y v0.1.0 => ./y1
|
||||||
|
example.net/y v0.2.0-pre => ./y2-pre
|
||||||
|
example.net/z v0.1.0 => ./z1
|
||||||
|
example.net/z v0.2.0-pre => ./z2-pre
|
||||||
|
)
|
||||||
|
-- w1/go.mod --
|
||||||
|
module example.net/w
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
example.net/y v0.2.0-pre
|
||||||
|
example.net/z v0.2.0-pre
|
||||||
|
)
|
||||||
|
-- w1/w.go --
|
||||||
|
package w
|
||||||
|
-- w2-pre/go.mod --
|
||||||
|
module example.net/w
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
-- w2-pre/README.txt --
|
||||||
|
Package w has been removed.
|
||||||
|
|
||||||
|
-- x1/go.mod --
|
||||||
|
module example.net/x
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
example.net/z v0.2.0-pre
|
||||||
|
example.net/w v0.2.0-pre
|
||||||
|
)
|
||||||
|
-- x1/x.go --
|
||||||
|
package x
|
||||||
|
-- x2-pre/go.mod --
|
||||||
|
module example.net/x
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
-- x2-pre/README.txt --
|
||||||
|
Package x has been removed.
|
||||||
|
|
||||||
|
-- y1/go.mod --
|
||||||
|
module example.net/y
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
example.net/w v0.2.0-pre
|
||||||
|
example.net/x v0.2.0-pre
|
||||||
|
)
|
||||||
|
-- y1/y.go --
|
||||||
|
package y
|
||||||
|
|
||||||
|
-- y2-pre/go.mod --
|
||||||
|
module example.net/y
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
-- y2-pre/README.txt --
|
||||||
|
Package y has been removed.
|
||||||
|
|
||||||
|
-- z1/go.mod --
|
||||||
|
module example.net/z
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
example.net/x v0.2.0-pre
|
||||||
|
example.net/y v0.2.0-pre
|
||||||
|
)
|
||||||
|
-- z1/z.go --
|
||||||
|
package z
|
||||||
|
|
||||||
|
-- z2-pre/go.mod --
|
||||||
|
module example.net/z
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
-- z2-pre/README.txt --
|
||||||
|
Package z has been removed.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue