cmd/go: additional doc-inspired tests and bug fixes

Additional tests and bug fixes realized while writing go.dev/doc/gotoolchain (CL 500775).

- Handle go get toolchain@go1.22 (resolve to latest patch release, same as go get go@1.22).
  (See modload/query.go and gover/mod.go.)

- Handle go get go@patch toolchain@patch.
  (See modload/query.go and gover/mod.go.)

- Remove prefix-goVERSION-suffix form for toolchain name,
  standardizing on goVERSION-suffix.
  I have no good explanation for having two forms, so simplify to one.
  (See vendor and gover.)

- Fail toolchain downloads when GOSUMDB=off.
  Because toolchain downloads cannot always be predicted
  (especially during switching rather than selection),
  they cannot be listed in go.sum.
  We rely on the checksum database for integrity of the download,
  especially if proxied. If the checksum database is disabled,
  this integrity check won't happen, so fail toolchain downloads.
  (See modfetch/sumdb.go and script/gotoolchain_net.txt)

- Use names from documentation in package toolchain
  (Select, Switch; SwitchTo renamed to Exec to avoid both names;
  reqs.go renamed to switch.go; toolchain.go renamed to select.go.)

- Make "go env GOTOOLCHAIN" and "go env -w GOTOOLCHAIN"
  work even when GOTOOLCHAIN is misconfigured.
  (See special case at top of Select in select.go.)

- Clarify what goInstallVersion does
  (report whether this is go install or go run pkg@version)
  and explain the potential version switch more clearly.
  Use the Switcher directly instead of reimplementing it.
  (See select.go.)

- Document go@ and toolchain@ forms in go help get,
  linking to go.dev/doc/toolchain.
  (See modget/get.go.)

- Update URL of documentation in $GOROOT/go.env.

For #57001.

Change-Id: I895ef3519ff95db8710ed23b36ebaf4f648120cb
Reviewed-on: https://go-review.googlesource.com/c/go/+/500797
Reviewed-by: Michael Matloob <matloob@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Bypass: Russ Cox <rsc@golang.org>
changes/97/500797/8
Russ Cox 2023-06-04 22:34:52 -04:00
parent bf016520e2
commit 35268a9960
16 changed files with 355 additions and 175 deletions

2
go.env
View File

@ -8,5 +8,5 @@ GOPROXY=https://proxy.golang.org,direct
GOSUMDB=sum.golang.org
# Automatically download newer toolchains as directed by go.mod files.
# See https://go.dev/s/gotoolchain for details.
# See https://go.dev/doc/toolchain for details.
GOTOOLCHAIN=auto

View File

@ -671,6 +671,14 @@
//
// go get example.com/mod@none
//
// To upgrade the minimum required Go version to the latest released Go version:
//
// go get go@latest
//
// To upgrade the Go toolchain to the latest patch release of the current Go toolchain:
//
// go get toolchain@patch
//
// See https://golang.org/ref/mod#go-get for details.
//
// In earlier versions of Go, 'go get' was used to build and install packages.
@ -705,6 +713,9 @@
//
// For more about modules, see https://golang.org/ref/mod.
//
// For more about using 'go get' to update the minimum Go version and
// suggested Go toolchain, see https://go.dev/doc/toolchain.
//
// For more about specifying packages, see 'go help packages'.
//
// This text describes the behavior of get using modules to manage source

View File

@ -84,6 +84,9 @@ func ModIsValid(path, vers string) bool {
// The caller is assumed to have checked that ModIsValid(path, vers) is true.
func ModIsPrefix(path, vers string) bool {
if IsToolchain(path) {
if path == "toolchain" {
return IsLang(FromToolchain(vers))
}
return IsLang(vers)
}
// Semver
@ -110,3 +113,15 @@ func ModIsPrerelease(path, vers string) bool {
}
return semver.Prerelease(vers) != ""
}
// ModMajorMinor returns the "major.minor" truncation of the version v,
// for use as a prefix in "@patch" queries.
func ModMajorMinor(path, vers string) string {
if IsToolchain(path) {
if path == "toolchain" {
return "go" + Lang(FromToolchain(vers))
}
return Lang(vers)
}
return semver.MajorMinor(vers)
}

View File

@ -14,20 +14,17 @@ import (
// FromToolchain returns the Go version for the named toolchain,
// derived from the name itself (not by running the toolchain).
// A toolchain is named "goVERSION" or "anything-goVERSION".
// A toolchain is named "goVERSION".
// A suffix after the VERSION introduced by a +, -, space, or tab is removed.
// Examples:
//
// FromToolchain("go1.2.3") == "1.2.3"
// FromToolchain("go1.2.3-bigcorp") == "1.2.3"
// FromToolchain("gccgo-go1.23rc4") == "1.23rc4"
// FromToolchain("invalid") == ""
func FromToolchain(name string) string {
var v string
if strings.HasPrefix(name, "go") {
v = name[2:]
} else if i := strings.Index(name, "-go"); i >= 0 {
v = name[i+3:]
} else {
return ""
}

View File

@ -14,6 +14,6 @@ var fromToolchainTests = []testCase1[string, string]{
{"go1.2.3+bigcorp", ""},
{"go1.2.3-bigcorp", "1.2.3"},
{"go1.2.3-bigcorp more text", "1.2.3"},
{"gccgo-go1.23rc4", "1.23rc4"},
{"gccgo-go1.23rc4-bigdwarf", "1.23rc4"},
{"gccgo-go1.23rc4", ""},
{"gccgo-go1.23rc4-bigdwarf", ""},
}

View File

@ -33,6 +33,14 @@ import (
// useSumDB reports whether to use the Go checksum database for the given module.
func useSumDB(mod module.Version) bool {
if mod.Path == "golang.org/toolchain" {
// Downloaded toolchains cannot be listed in go.sum,
// so we require checksum database lookups even if
// GOSUMDB=off or GONOSUMDB matches the pattern.
// If GOSUMDB=off, then the eventual lookup will fail
// with a good error message.
return true
}
return cfg.GOSUMDB != "off" && !module.MatchPrefixPatterns(cfg.GONOSUMDB, mod.Path)
}
@ -70,6 +78,10 @@ func dbDial() (dbName string, db *sumdb.Client, err error) {
gosumdb = "sum.golang.org https://sum.golang.google.cn"
}
if gosumdb == "off" {
return "", nil, fmt.Errorf("checksum database disabled by GOSUMDB=off")
}
key := strings.Fields(gosumdb)
if len(key) >= 1 {
if k := knownGOSUMDB[key[0]]; k != "" {

View File

@ -72,6 +72,14 @@ To remove a dependency on a module and downgrade modules that require it:
go get example.com/mod@none
To upgrade the minimum required Go version to the latest released Go version:
go get go@latest
To upgrade the Go toolchain to the latest patch release of the current Go toolchain:
go get toolchain@patch
See https://golang.org/ref/mod#go-get for details.
In earlier versions of Go, 'go get' was used to build and install packages.
@ -106,6 +114,9 @@ from a repository.
For more about modules, see https://golang.org/ref/mod.
For more about using 'go get' to update the minimum Go version and
suggested Go toolchain, see https://go.dev/doc/toolchain.
For more about specifying packages, see 'go help packages'.
This text describes the behavior of get using modules to manage source

View File

@ -393,7 +393,7 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (*
qm.mayUseLatest = true
} else {
qm.mayUseLatest = module.IsPseudoVersion(current)
qm.prefix = semver.MajorMinor(current) + "."
qm.prefix = gover.ModMajorMinor(qm.path, current) + "."
qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, current) >= 0 }
}

View File

@ -81,11 +81,12 @@ func FilterEnv(env []string) []string {
return out
}
// Switch invokes a different Go toolchain if directed by
// Select invokes a different Go toolchain if directed by
// the GOTOOLCHAIN environment variable or the user's configuration
// or go.mod file.
// It must be called early in startup.
func Switch() {
// See https://go.dev/doc/toolchain#select.
func Select() {
log.SetPrefix("go: ")
defer log.SetPrefix("")
@ -93,6 +94,21 @@ func Switch() {
return
}
// As a special case, let "go env GOTOOLCHAIN" and "go env -w GOTOOLCHAIN=..."
// be handled by the local toolchain, since an older toolchain may not understand it.
// This provides an easy way out of "go env -w GOTOOLCHAIN=go1.19" and makes
// sure that "go env GOTOOLCHAIN" always prints the local go command's interpretation of it.
// We look for these specific command lines in order to avoid mishandling
//
// GOTOOLCHAIN=go1.999 go env -newflag GOTOOLCHAIN
//
// where -newflag is a flag known to Go 1.999 but not known to us.
if (len(os.Args) == 3 && os.Args[1] == "env" && os.Args[2] == "GOTOOLCHAIN") ||
(len(os.Args) == 4 && os.Args[1] == "env" && os.Args[2] == "-w" && strings.HasPrefix(os.Args[3], "GOTOOLCHAIN=")) {
return
}
// Interpret GOTOOLCHAIN to select the Go toolchain to run.
gotoolchain := cfg.Getenv("GOTOOLCHAIN")
gover.Startup.GOTOOLCHAIN = gotoolchain
if gotoolchain == "" {
@ -105,77 +121,70 @@ func Switch() {
return
}
// Note: minToolchain is what https://go.dev/doc/toolchain#select calls the default toolchain.
minToolchain := gover.LocalToolchain()
minVers := gover.Local()
if min, mode, ok := strings.Cut(gotoolchain, "+"); ok { // go1.2.3+auto
v := gover.FromToolchain(min)
if v == "" {
base.Fatalf("invalid GOTOOLCHAIN %q: invalid minimum toolchain %q", gotoolchain, min)
var mode string
if gotoolchain == "auto" {
mode = "auto"
} else if gotoolchain == "path" {
mode = "path"
} else {
min, suffix, plus := strings.Cut(gotoolchain, "+") // go1.2.3+auto
if min != "local" {
v := gover.FromToolchain(gotoolchain)
if v == "" {
if plus {
base.Fatalf("invalid GOTOOLCHAIN %q: invalid minimum toolchain %q", gotoolchain, min)
}
base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain)
}
minToolchain = min
minVers = v
}
minToolchain = min
minVers = v
if mode != "auto" && mode != "path" {
if plus && suffix != "auto" && suffix != "path" {
base.Fatalf("invalid GOTOOLCHAIN %q: only version suffixes are +auto and +path", gotoolchain)
}
gotoolchain = mode
mode = suffix
}
if gotoolchain == "auto" || gotoolchain == "path" {
gotoolchain = minToolchain
// Locate and read go.mod or go.work.
// For go install m@v, it's the installed module's go.mod.
if m, goVers, ok := goInstallVersion(); ok {
if gover.Compare(goVers, minVers) > 0 {
// Always print, because otherwise there's no way for the user to know
// that a non-default toolchain version is being used here.
// (Normally you can run "go version", but go install m@v ignores the
// context that "go version" works in.)
var err error
gotoolchain, err = NewerToolchain(context.Background(), goVers)
if err != nil {
fmt.Fprintf(os.Stderr, "go: %v\n", err)
gotoolchain = "go" + goVers
}
fmt.Fprintf(os.Stderr, "go: using %s for %v\n", gotoolchain, m)
}
gotoolchain = minToolchain
if (mode == "auto" || mode == "path") && !goInstallVersion() {
// Read go.mod to find new minimum and suggested toolchain.
file, goVers, toolchain := modGoToolchain()
gover.Startup.AutoFile = file
if toolchain == "default" {
// "default" means always use the default toolchain,
// which is already set, so nothing to do here.
// Note that if we have Go 1.21 installed originally,
// GOTOOLCHAIN=go1.30.0+auto or GOTOOLCHAIN=go1.30.0,
// and the go.mod says "toolchain default", we use Go 1.30, not Go 1.21.
// That is, default overrides the "auto" part of the calculation
// but not the minimum that the user has set.
// Of course, if the go.mod also says "go 1.35", using Go 1.30
// will provoke an error about the toolchain being too old.
// That's what people who use toolchain default want:
// only ever use the toolchain configured by the user
// (including its environment and go env -w file).
gover.Startup.AutoToolchain = toolchain
} else {
file, goVers, toolchain := modGoToolchain()
gover.Startup.AutoFile = file
if toolchain == "local" {
// Local means always use the default local toolchain,
// which is already set, so nothing to do here.
// Note that if we have Go 1.21 installed originally,
// GOTOOLCHAIN=go1.30.0+auto or GOTOOLCHAIN=go1.30.0,
// and the go.mod says "toolchain local", we use Go 1.30, not Go 1.21.
// That is, local overrides the "auto" part of the calculation
// but not the minimum that the user has set.
// Of course, if the go.mod also says "go 1.35", using Go 1.30
// will provoke an error about the toolchain being too old.
// That's what people who use toolchain local want:
// only ever use the toolchain configured in the local system
// (including its environment and go env -w file).
gover.Startup.AutoToolchain = toolchain
gotoolchain = "local"
} else {
if toolchain != "" {
// Accept toolchain only if it is >= our min.
toolVers := gover.FromToolchain(toolchain)
if toolVers == "" || (!strings.HasPrefix(toolchain, "go") && !strings.Contains(toolchain, "-go")) {
base.Fatalf("invalid toolchain %q in %s", toolchain, base.ShortPath(file))
}
if gover.Compare(toolVers, minVers) >= 0 {
gotoolchain = toolchain
minVers = toolVers
gover.Startup.AutoToolchain = toolchain
}
if toolchain != "" {
// Accept toolchain only if it is >= our min.
toolVers := gover.FromToolchain(toolchain)
if toolVers == "" || (!strings.HasPrefix(toolchain, "go") && !strings.Contains(toolchain, "-go")) {
base.Fatalf("invalid toolchain %q in %s", toolchain, base.ShortPath(file))
}
if gover.Compare(goVers, minVers) > 0 {
gotoolchain = "go" + goVers
gover.Startup.AutoGoVersion = goVers
gover.Startup.AutoToolchain = "" // in case we are overriding it for being too old
if gover.Compare(toolVers, minVers) >= 0 {
gotoolchain = toolchain
minVers = toolVers
gover.Startup.AutoToolchain = toolchain
}
}
if gover.Compare(goVers, minVers) > 0 {
gotoolchain = "go" + goVers
gover.Startup.AutoGoVersion = goVers
gover.Startup.AutoToolchain = "" // in case we are overriding it for being too old
}
}
}
@ -219,11 +228,12 @@ func Switch() {
base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain)
}
SwitchTo(gotoolchain)
Exec(gotoolchain)
}
// NewerToolchain returns the name of the toolchain to use when we need
// to reinvoke a newer toolchain that must support at least the given Go version.
// to switch to a newer toolchain that must support at least the given Go version.
// See https://go.dev/doc/toolchain#switch.
//
// If the latest major release is 1.N.0, we use the latest patch release of 1.(N-1) if that's >= version.
// Otherwise we use the latest 1.N if that's allowed.
@ -345,14 +355,14 @@ func HasPath() bool {
//
// "switch" - simulate version switches by reinvoking the test go binary with a different TESTGO_VERSION.
// "mismatch" - like "switch" but forget to set TESTGO_VERSION, so it looks like we invoked a mismatched toolchain
// "loop" - like "switch" but
// "loop" - like "mismatch" but forget the target check, causing a toolchain switching loop
var TestVersionSwitch string
// SwitchTo invokes the specified Go toolchain or else prints an error and exits the process.
// If $GOTOOLCHAIN is set to path or min+path, SwitchTo only considers the PATH
// as a source of Go toolchains. Otherwise SwitchTo tries the PATH but then downloads
// Exec invokes the specified Go toolchain or else prints an error and exits the process.
// If $GOTOOLCHAIN is set to path or min+path, Exec only considers the PATH
// as a source of Go toolchains. Otherwise Exec tries the PATH but then downloads
// a toolchain if necessary.
func SwitchTo(gotoolchain string) {
func Exec(gotoolchain string) {
log.SetPrefix("go: ")
count, _ := strconv.Atoi(os.Getenv(countEnv))
@ -496,28 +506,32 @@ func modGoToolchain() (file, goVers, toolchain string) {
return file, gover.GoModLookup(data, "go"), gover.GoModLookup(data, "toolchain")
}
// goInstallVersion looks at the command line to see if it is go install m@v or go run m@v.
// If so, it returns the m@v and the go version from that module's go.mod.
func goInstallVersion() (m module.Version, goVers string, found bool) {
// goInstallVersion reports whether the command line is go install m@v or go run m@v.
// If so, Select must not read the go.mod or go.work file in "auto" or "path" mode.
func goInstallVersion() bool {
// Note: We assume there are no flags between 'go' and 'install' or 'run'.
// During testing there are some debugging flags that are accepted
// in that position, but in production go binaries there are not.
if len(os.Args) < 3 || (os.Args[1] != "install" && os.Args[1] != "run") {
return module.Version{}, "", false
return false
}
// Check for pkg@version.
var arg string
switch os.Args[1] {
default:
return false
case "install":
// Cannot parse 'go install' command line precisely, because there
// may be new flags we don't know about. Instead, assume the final
// argument is a pkg@version we can use.
// We would like to let 'go install -newflag pkg@version' work even
// across a toolchain switch. To make that work, assume the pkg@version
// is the last argument and skip the flag parsing.
arg = os.Args[len(os.Args)-1]
case "run":
// For run, the pkg@version can be anywhere on the command line.
// We don't know the flags, so we can't strictly speaking do this correctly.
// We do the best we can by interrogating the CmdRun flags and assume
// that any unknown flag does not take an argument.
// For run, the pkg@version can be anywhere on the command line,
// because it is preceded by run flags and followed by arguments to the
// program being run. To handle that precisely, we have to interpret the
// flags a little bit, to know whether each flag takes an optional argument.
// We can still allow unknown flags as long as they have an explicit =value.
args := os.Args[2:]
for i := 0; i < len(args); i++ {
a := args[i]
@ -526,19 +540,21 @@ func goInstallVersion() (m module.Version, goVers string, found bool) {
break
}
if a == "-" {
break
// non-flag but also non-pkg@version
return false
}
if a == "--" {
if i+1 < len(args) {
arg = args[i+1]
if i+1 >= len(args) {
return false
}
arg = args[i+1]
break
}
a = strings.TrimPrefix(a, "-")
a = strings.TrimPrefix(a, "-")
if strings.HasPrefix(a, "-") {
// non-flag but also non-m@v
break
// non-flag but also non-pkg@version
return false
}
if strings.Contains(a, "=") {
// already has value
@ -546,8 +562,8 @@ func goInstallVersion() (m module.Version, goVers string, found bool) {
}
f := run.CmdRun.Flag.Lookup(a)
if f == nil {
// Unknown flag. Assume it doesn't take a value: best we can do.
continue
// Unknown flag. Give up. The command is going to fail in flag parsing.
return false
}
if bf, ok := f.Value.(interface{ IsBoolFlag() bool }); ok && bf.IsBoolFlag() {
// Does not take value.
@ -557,13 +573,24 @@ func goInstallVersion() (m module.Version, goVers string, found bool) {
}
}
if !strings.Contains(arg, "@") || build.IsLocalImport(arg) || filepath.IsAbs(arg) {
return module.Version{}, "", false
return false
}
m.Path, m.Version, _ = strings.Cut(arg, "@")
if m.Path == "" || m.Version == "" || gover.IsToolchain(m.Path) {
return module.Version{}, "", false
path, version, _ := strings.Cut(arg, "@")
if path == "" || version == "" || gover.IsToolchain(path) {
return false
}
// It would be correct to simply return true here, bypassing use
// of the current go.mod or go.work, and let "go run" or "go install"
// do the rest, including a toolchain switch.
// Our goal instead is, since we have gone to the trouble of handling
// unknown flags to some degree, to run the switch now, so that
// these commands can switch to a newer toolchain directed by the
// go.mod which may actually understand the flag.
// This was brought up during the go.dev/issue/57001 proposal discussion
// and may end up being common in self-contained "go install" or "go run"
// command lines if we add new flags in the future.
// Set up modules without an explicit go.mod, to download go.mod.
modload.ForceUseModules = true
modload.RootMode = modload.NoRoot
@ -573,21 +600,17 @@ func goInstallVersion() (m module.Version, goVers string, found bool) {
// See internal/load.PackagesAndErrorsOutsideModule
ctx := context.Background()
allowed := modload.CheckAllowed
if modload.IsRevisionQuery(m.Path, m.Version) {
if modload.IsRevisionQuery(path, version) {
// Don't check for retractions if a specific revision is requested.
allowed = nil
}
noneSelected := func(path string) (version string) { return "none" }
_, err := modload.QueryPackages(ctx, m.Path, m.Version, noneSelected, allowed)
if tooNew := (*gover.TooNewError)(nil); errors.As(err, &tooNew) {
m.Path, m.Version, _ = strings.Cut(tooNew.What, "@")
return m, tooNew.GoVersion, true
_, err := modload.QueryPackages(ctx, path, version, noneSelected, allowed)
if errors.Is(err, gover.ErrTooNew) {
// Run early switch, same one go install or go run would eventually do,
// if it understood all the command-line flags.
SwitchOrFatal(ctx, err)
}
// QueryPackages succeeded, or it failed for a reason other than
// this Go toolchain being too old for the modules encountered.
// Either way, we identified the m@v on the command line,
// so return found == true so the caller does not fall back to
// consulting go.mod.
return m, "", true
return true // pkg@version found
}

View File

@ -22,6 +22,8 @@ import (
// *gover.TooNewErrors (potentially wrapped) and switching is
// permitted by GOTOOLCHAIN, Switch switches to a new toolchain.
// Otherwise Switch prints all the errors using base.Error.
//
// See https://go.dev/doc/toolchain#switch.
type Switcher struct {
TooNew *gover.TooNewError // max go requirement observed
Errors []error // errors collected so far
@ -91,7 +93,7 @@ func (s *Switcher) Switch(ctx context.Context) {
}
fmt.Fprintf(os.Stderr, "go: %v requires go >= %v; switching to %v\n", s.TooNew.What, s.TooNew.GoVersion, tv)
SwitchTo(tv)
Exec(tv)
panic("unreachable")
}

View File

@ -92,7 +92,7 @@ var _ = go11tag
func main() {
log.SetFlags(0)
toolchain.Switch()
toolchain.Select()
flag.Usage = base.Usage
flag.Parse()

View File

@ -70,15 +70,15 @@ go mod edit -go=1.700 -toolchain=go1.300
go version
stdout go1.700 # toolchain too old, ignored
go mod edit -go=1.300 -toolchain=local
go mod edit -go=1.300 -toolchain=default
go version
stdout go1.500
go mod edit -go=1.700 -toolchain=local
go mod edit -go=1.700 -toolchain=default
go version
stdout go1.500 # toolchain local is like GOTOOLCHAIN=local and wins
! go build
stderr '^go: go.mod requires go >= 1.700 \(running go 1.500; go.mod sets toolchain local\)'
stderr '^go: go.mod requires go >= 1.700 \(running go 1.500; go.mod sets toolchain default\)'
# GOTOOLCHAIN=path does the same.
env GOTOOLCHAIN=path
@ -94,25 +94,33 @@ go mod edit -go=1.700 -toolchain=go1.300
go version
stdout go1.700 # toolchain too old, ignored
go mod edit -go=1.300 -toolchain=local
go mod edit -go=1.300 -toolchain=default
go version
stdout go1.500
go mod edit -go=1.700 -toolchain=local
go mod edit -go=1.700 -toolchain=default
go version
stdout go1.500 # toolchain applies even if older than go line
stdout go1.500 # toolchain default applies even if older than go line
! go build
stderr '^go: go.mod requires go >= 1.700 \(running go 1.500; GOTOOLCHAIN=path; go.mod sets toolchain local\)'
stderr '^go: go.mod requires go >= 1.700 \(running go 1.500; GOTOOLCHAIN=path; go.mod sets toolchain default\)'
# GOTOOLCHAIN names can have prefix- or -suffix
# GOTOOLCHAIN=min+auto with toolchain default uses min, not local
env GOTOOLCHAIN=go1.400+auto
go mod edit -go=1.300 -toolchain=default
go version
stdout 1.400 # not 1.500 local toolchain
env GOTOOLCHAIN=go1.600+auto
go mod edit -go=1.300 -toolchain=default
go version
stdout 1.600 # not 1.500 local toolchain
# GOTOOLCHAIN names can have -suffix
env GOTOOLCHAIN=go1.800-bigcorp
go version
stdout go1.800-bigcorp
env GOTOOLCHAIN=bigcorp-go1.100
go version
stdout bigcorp-go1.100
env GOTOOLCHAIN=auto
go mod edit -go=1.999 -toolchain=go1.800-bigcorp
go version
@ -122,14 +130,6 @@ go mod edit -go=1.777 -toolchain=go1.800-bigcorp
go version
stdout go1.800-bigcorp
go mod edit -go=1.999 -toolchain=bigcorp-go1.800
go version
stdout go1.999
go mod edit -go=1.777 -toolchain=bigcorp-go1.800
go version
stdout bigcorp-go1.800
# go.work takes priority over go.mod
go mod edit -go=1.700 -toolchain=go1.999-wrong
go work init
@ -137,13 +137,13 @@ go work edit -go=1.400 -toolchain=go1.600-right
go version
stdout go1.600-right
go work edit -go=1.400 -toolchain=local
go work edit -go=1.400 -toolchain=default
go version
stdout go1.500
# go.work misconfiguration does not break go work edit
# ('go 1.600 / toolchain local' forces use of 1.500 which can't normally load that go.work; allow work edit to fix it.)
go work edit -go=1.600 -toolchain=local
go work edit -go=1.600 -toolchain=default
go version
stdout go1.500
@ -154,7 +154,7 @@ stdout go1.600
rm go.work
# go.mod misconfiguration does not break go mod edit
go mod edit -go=1.600 -toolchain=local
go mod edit -go=1.600 -toolchain=default
go version
stdout go1.500
@ -177,19 +177,6 @@ go mod edit -go=1.501 -toolchain=none
go version
stdout go1.501
env TESTGO_VERSION=bigcorp-go1.500
go mod edit -go=1.499 -toolchain=none
go version
stdout bigcorp-go1.500
go mod edit -go=1.500 -toolchain=none
go version
stdout bigcorp-go1.500
go mod edit -go=1.501 -toolchain=none
go version
stdout go1.501
env TESTGO_VERSION='go1.500 (bigcorp)'
go mod edit -go=1.499 -toolchain=none
go version
@ -208,60 +195,55 @@ env TESTGO_VERSION=go1.2.3
go mod edit -go=1.999 -toolchain=go1.998
! go install rsc.io/fortune/nonexist@v0.0.1
stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^go: rsc.io/fortune/nonexist@v0.0.1: module rsc.io/fortune@v0.0.1 found, but does not contain package rsc.io/fortune/nonexist'
! go run rsc.io/fortune/nonexist@v0.0.1
stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^go: rsc.io/fortune/nonexist@v0.0.1: module rsc.io/fortune@v0.0.1 found, but does not contain package rsc.io/fortune/nonexist'
# go install should handle unknown flags to find m@v
! go install -unknownflag rsc.io/fortune/nonexist@v0.0.1
stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^flag provided but not defined: -unknownflag'
! go install -unknownflag arg rsc.io/fortune/nonexist@v0.0.1
stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^flag provided but not defined: -unknownflag'
# go run should handle unknown boolean flags and flags with =arg
# go run cannot handle unknown boolean flags
! go run -unknownflag rsc.io/fortune/nonexist@v0.0.1
stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
! stderr switching
stderr '^flag provided but not defined: -unknownflag'
! go run -unknownflag oops rsc.io/fortune/nonexist@v0.0.1
! stderr switching
stderr '^flag provided but not defined: -unknownflag'
# go run can handle unknown flag with argument.
! go run -unknown=flag rsc.io/fortune/nonexist@v0.0.1
stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^flag provided but not defined: -unknown'
# go run assumes unknown flags don't take arguments
! go run -unknownflag rsc.io/fortune/nonexist@v0.0.1
stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
stderr '^flag provided but not defined: -unknownflag'
! go run -unknownflag oops rsc.io/fortune/nonexist@v0.0.1 # lost parse, cannot find m@v
! stderr go1.22.9
! stderr '^go: using'
stderr '^flag provided but not defined: -unknownflag'
# go install m@v should handle queries
! go install rsc.io/fortune/nonexist@v0.0
stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^go: rsc.io/fortune/nonexist@v0.0: module rsc.io/fortune@v0.0 found \(v0.0.1\), but does not contain package rsc.io/fortune/nonexist'
# go run m@v should handle queries
! go install rsc.io/fortune/nonexist@v0
stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^go: rsc.io/fortune/nonexist@v0: module rsc.io/fortune@v0 found \(v0.0.1\), but does not contain package rsc.io/fortune/nonexist'
# go install m@v should use local toolchain if not upgrading
! go install rsc.io/fortune/nonexist@v1
! stderr go1.22.9
! stderr '^go: using'
! stderr switching
stderr '^go: downloading rsc.io/fortune v1.0.0$'
stderr '^go: rsc.io/fortune/nonexist@v1: module rsc.io/fortune@v1 found \(v1.0.0\), but does not contain package rsc.io/fortune/nonexist'
# go run m@v should use local toolchain if not upgrading
! go run rsc.io/fortune/nonexist@v1
! stderr go1.22.9
! stderr '^go: using'
! stderr switching
stderr '^go: rsc.io/fortune/nonexist@v1: module rsc.io/fortune@v1 found \(v1.0.0\), but does not contain package rsc.io/fortune/nonexist'

View File

@ -1,8 +1,6 @@
# This test only checks that basic network lookups work.
# The full test of toolchain version selection is in gotoolchain.txt.
[short] skip
env TESTGO_VERSION=go1.21actual
# GOTOOLCHAIN from network, does not exist
@ -16,7 +14,34 @@ env GOTOOLCHAIN=go1.999testmod
go version
stderr 'go: downloading go1.999testmod \(.*/.*\)'
# GOTOOLCHAIN cached from network
go version
! stderr downloading
stdout go1.999testmod
# GOTOOLCHAIN with GOSUMDB enabled but at a bad URL should operate in cache and not try badurl
env oldsumdb=$GOSUMDB
env GOSUMDB=$oldsumdb' http://badurl'
go version
! stderr downloading
stdout go1.999testmod
# GOTOOLCHAIN with GOSUMB=off should fail, because it cannot access even the cached sumdb info
# without the sumdb name.
env GOSUMDB=off
! go version
stderr '^go: golang.org/toolchain@v0.0.1-go1.999testmod.[a-z0-9\-]*: verifying module: checksum database disabled by GOSUMDB=off$'
# GOTOOLCHAIN with GOSUMDB enabled but at a bad URL should fail if cache is incomplete
env GOSUMDB=$oldsumdb' http://badurl'
rm $GOPATH/pkg/mod/cache/download/sumdb
! go version
! stderr downloading
stderr 'panic: use of network' # test catches network access
env GOSUMDB=$oldsumdb
# Test a real GOTOOLCHAIN
[short] skip
[!net:golang.org] skip
[!GOOS:darwin] [!GOOS:windows] [!GOOS:linux] skip
[!GOARCH:amd64] [!GOARCH:arm64] skip

View File

@ -7,8 +7,8 @@ env GO111MODULE=on
go mod edit -toolchain=go1.9
grep 'toolchain go1.9' go.mod
go mod edit -toolchain=local
grep 'toolchain local' go.mod
go mod edit -toolchain=default
grep 'toolchain default' go.mod
go mod edit -toolchain=none
! grep toolchain go.mod

View File

@ -0,0 +1,102 @@
# setup
env TESTGO_VERSION=go1.99.0
env TESTGO_VERSION_SWITCH=switch
# go get go should use the latest Go 1.23
cp go.mod.orig go.mod
go get go
stderr '^go: upgraded go 1.21 => 1.23.9$'
grep 'go 1.23.9' go.mod
grep 'toolchain go1.99.0' go.mod
# go get go@1.23 should use the latest Go 1.23
cp go.mod.orig go.mod
go get go@1.23
stderr '^go: upgraded go 1.21 => 1.23.9$'
grep 'go 1.23.9' go.mod
grep 'toolchain go1.99.0' go.mod
# go get go@1.22 should use the latest Go 1.22
cp go.mod.orig go.mod
go get go@1.22
stderr '^go: upgraded go 1.21 => 1.22.9$'
grep 'go 1.22.9' go.mod
grep 'toolchain go1.99.0' go.mod
# go get go@patch should use the latest patch release
go get go@1.22.1
go get go@patch
stderr '^go: upgraded go 1.22.1 => 1.22.9$'
grep 'go 1.22.9' go.mod
grep 'toolchain go1.99.0' go.mod
# go get go@1.24 does NOT find the release candidate
cp go.mod.orig go.mod
! go get go@1.24
stderr '^go: go@1.24: no matching versions for query "1.24"$'
# go get go@1.24rc1 works
cp go.mod.orig go.mod
go get go@1.24rc1
stderr '^go: upgraded go 1.21 => 1.24rc1$'
grep 'go 1.24rc1' go.mod
grep 'toolchain go1.99.0' go.mod
# go get go@latest finds the latest Go 1.23
cp go.mod.orig go.mod
go get go@latest
stderr '^go: upgraded go 1.21 => 1.23.9$'
grep 'go 1.23.9' go.mod
grep 'toolchain go1.99.0' go.mod
# Again, with toolchains.
# go get toolchain should find go1.999testmod.
go get toolchain
stderr '^go: upgraded toolchain go1.99.0 => go1.999testmod$'
grep 'go 1.23.9' go.mod
grep 'toolchain go1.999testmod' go.mod
# go get toolchain@go1.23 should use the latest Go 1.23
go get toolchain@go1.23
stderr '^go: removed toolchain go1.999testmod$'
grep 'go 1.23.9' go.mod
! grep 'toolchain go1.23.9' go.mod # implied
# go get toolchain@go1.22 should use the latest Go 1.22 and downgrade go.
go get toolchain@go1.22
stderr '^go: downgraded go 1.23.9 => 1.22.9$'
grep 'go 1.22.9' go.mod
! grep 'toolchain go1.22.9' go.mod # implied
# go get toolchain@patch should use the latest patch release
go get toolchain@go1.22.1
go get toolchain@patch
stderr '^go: added toolchain go1.22.9$'
grep 'go 1.22.1' go.mod
grep 'toolchain go1.22.9' go.mod
go get go@1.22.9 toolchain@none
grep 'go 1.22.9' go.mod
! grep 'toolchain go1.22.9' go.mod
# go get toolchain@go1.24 does NOT find the release candidate
! go get toolchain@go1.24
stderr '^go: toolchain@go1.24: no matching versions for query "go1.24"$'
# go get toolchain@go1.24rc1 works
go get toolchain@go1.24rc1
stderr '^go: added toolchain go1.24rc1$'
grep 'go 1.22.9' go.mod # no longer implied
grep 'toolchain go1.24rc1' go.mod
# go get toolchain@latest finds go1.999testmod.
cp go.mod.orig go.mod
go get toolchain@latest
stderr '^go: added toolchain go1.999testmod$'
grep 'go 1.21' go.mod
grep 'toolchain go1.999testmod' go.mod
-- go.mod.orig --
module m
go 1.21

View File

@ -7,8 +7,8 @@ env GO111MODULE=on
go work edit -toolchain=go1.9
grep 'toolchain go1.9' go.work
go work edit -toolchain=local
grep 'toolchain local' go.work
go work edit -toolchain=default
grep 'toolchain default' go.work
go work edit -toolchain=none
! grep toolchain go.work