From 96a3d36dc6124e7659866f62c1b963db7369fcf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 10 Jul 2025 11:18:28 +0100 Subject: [PATCH 1/2] update dependencies To make sure we are testing with the latest versions, which our downstream users are likely using. --- go.mod | 8 ++++---- go.sum | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 01ce72e..52b6a3a 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/rogpeppe/go-internal -go 1.23 +go 1.23.0 require ( - golang.org/x/mod v0.21.0 - golang.org/x/sys v0.26.0 - golang.org/x/tools v0.26.0 + golang.org/x/mod v0.26.0 + golang.org/x/sys v0.34.0 + golang.org/x/tools v0.34.0 ) diff --git a/go.sum b/go.sum index 8693c9d..b19bd16 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= From 5c47756a688d0dd2a557d007b1e9b14156c625fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 10 Jul 2025 12:01:25 +0100 Subject: [PATCH 2/2] testscript: add GoTool to expose `go tool X` as a $PATH command `go get -tool` paired with `go tool X` is a helpful feature added in Go 1.24, and it's really useful to testscript users given that it tracks the dependency in go.mod and caches the built binaries too. Its current implementation is a bit limited given the API for Main. See the added comments and TODO for future tweaks. --- go.mod | 4 +++ go.sum | 4 +++ testscript/exe.go | 46 +++++++++++++++++++++++++++++++ testscript/testdata/go_tool.txtar | 20 ++++++++++++++ testscript/testscript.go | 6 ++++ testscript/testscript_test.go | 1 + 6 files changed, 81 insertions(+) create mode 100644 testscript/testdata/go_tool.txtar diff --git a/go.mod b/go.mod index 52b6a3a..d470c94 100644 --- a/go.mod +++ b/go.mod @@ -7,3 +7,7 @@ require ( golang.org/x/sys v0.34.0 golang.org/x/tools v0.34.0 ) + +require golang.org/x/sync v0.15.0 // indirect + +tool golang.org/x/tools/cmd/stringer diff --git a/go.sum b/go.sum index b19bd16..ebc6069 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= diff --git a/testscript/exe.go b/testscript/exe.go index 7e6983b..8e76442 100644 --- a/testscript/exe.go +++ b/testscript/exe.go @@ -5,9 +5,12 @@ package testscript import ( + "bytes" + "fmt" "io" "log" "os" + "os/exec" "path/filepath" "runtime" "strings" @@ -58,6 +61,49 @@ func Main(m TestingM, commands map[string]func()) { os.Exit(0) } +// GoTool exposes a Go program added to the module being tested via `go get -tool`, +// which can then be run via `go tool $name` and leverage Go's module and build caches. +// This function must be run as part of [Main]; for example, after setting up the tool +// via `go get -tool golang.org/x/tools/cmd/stringer`: +// +// testscript.Main(m, map[string]func(){ +// "stringer": testscript.GoTool("stringer"), +// }) +func GoTool(name string) func() { + // Since [Main] only takes a map[string]func() as a parameter, we cannot store the path + // to the cached tool anywhere, so we resort to setting one env var per tool. + // This is not ideal, but it works. + // + // We could also directly copy the cached tool binary into the $PATH that testscript sets up, + // to avoid an indirection via the test binary to use os/exec below. + // However, this again is very difficult given the current API of [Main]. + // + // TODO: rethink in a future iteration of the API. + envName := "TESTSCRIPT_GO_TOOL_" + name + cachedBin := os.Getenv(envName) + if cachedBin == "" { + cmd := exec.Command("go", "tool", "-n", name) + out, err := cmd.CombinedOutput() + if err != nil { + log.Fatalf("failed to run %v: %v\n%s", strings.Join(cmd.Args, " "), err, out) + } + os.Setenv(envName, string(bytes.TrimSpace(out))) + } + return func() { + cmd := exec.Command(cachedBin, os.Args[1:]...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + if err, ok := err.(*exec.ExitError); ok { + os.Exit(err.ExitCode()) + } + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + } +} + // testingMRun exists just so that we can use `defer`, given that [Main] above uses [os.Exit]. func testingMRun(m TestingM, commands map[string]func()) int { // Set up all commands in a directory, added in $PATH. diff --git a/testscript/testdata/go_tool.txtar b/testscript/testdata/go_tool.txtar new file mode 100644 index 0000000..b58e7eb --- /dev/null +++ b/testscript/testdata/go_tool.txtar @@ -0,0 +1,20 @@ +# We test the integration with `go tool X` with stringer, because it's contained +# by an existing dependency of ours in the form of x/tools. +# Moreover, it's a fairly simple tool to use, and given that it takes a relative +# path as an argument, it will catch whether the current directory is correct. + +env GOCACHE=${WORK}/.gocache + +stringer -type Foo foo.go +exists foo_string.go + +-- foo.go -- +package foo + +type Foo int + +const ( + _ Foo = iota + Foo1 + Foo2 +) diff --git a/testscript/testscript.go b/testscript/testscript.go index 4b790e8..83eb943 100644 --- a/testscript/testscript.go +++ b/testscript/testscript.go @@ -494,6 +494,12 @@ func (ts *TestScript) setup() string { env.Vars = append(env.Vars, name+"="+val) } } + // For [GoTool] to work, we must pass its env vars through. + for _, kv := range os.Environ() { + if strings.HasPrefix(kv, "TESTSCRIPT_GO_TOOL_") { + env.Vars = append(env.Vars, kv) + } + } // Must preserve SYSTEMROOT on Windows: https://github.com/golang/go/issues/25513 et al if runtime.GOOS == "windows" { env.Vars = append(env.Vars, diff --git a/testscript/testscript_test.go b/testscript/testscript_test.go index b2fea5d..82585ca 100644 --- a/testscript/testscript_test.go +++ b/testscript/testscript_test.go @@ -81,6 +81,7 @@ func TestMain(m *testing.M) { "status": exitWithStatus, "signalcatcher": signalCatcher, "terminalprompt": terminalPrompt, + "stringer": GoTool("stringer"), }) }