Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
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
)

require golang.org/x/sync v0.15.0 // indirect

tool golang.org/x/tools/cmd/stringer
16 changes: 10 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
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=
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=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
46 changes: 46 additions & 0 deletions testscript/exe.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
package testscript

import (
"bytes"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
Expand Down Expand Up @@ -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.
Expand Down
20 changes: 20 additions & 0 deletions testscript/testdata/go_tool.txtar
Original file line number Diff line number Diff line change
@@ -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
)
6 changes: 6 additions & 0 deletions testscript/testscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions testscript/testscript_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func TestMain(m *testing.M) {
"status": exitWithStatus,
"signalcatcher": signalCatcher,
"terminalprompt": terminalPrompt,
"stringer": GoTool("stringer"),
})
}

Expand Down
Loading