cmd/stringer: streamline test subprocesses

- Execute the test binary itself as cmd/stringer instead of invoking (and
cleaning up after) 'go build'.
- Replace os.MkdirTemp with T.TempDir

Changes are similar to https://go.dev/cl/377836.

Change-Id: I5f9fca20e0f1f045826c385d556257fc5982ed53
GitHub-Last-Rev: f6c6b7735c97d3c2f508de91a268cf6795291ffd
GitHub-Pull-Request: golang/tools#425
Reviewed-on: https://go-review.googlesource.com/c/tools/+/464350
Reviewed-by: Bryan Mills <bcmills@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Bryan Mills <bcmills@google.com>
diff --git a/cmd/stringer/endtoend_test.go b/cmd/stringer/endtoend_test.go
index 268c2f6..29eb918 100644
--- a/cmd/stringer/endtoend_test.go
+++ b/cmd/stringer/endtoend_test.go
@@ -19,9 +19,9 @@
 	"path"
 	"path/filepath"
 	"strings"
+	"sync"
 	"testing"
 
-	"golang.org/x/tools/internal/testenv"
 	"golang.org/x/tools/internal/typeparams"
 )
 
@@ -30,9 +30,22 @@
 // we run stringer -type X and then compile and run the program. The resulting
 // binary panics if the String method for X is not correct, including for error cases.
 
+func TestMain(m *testing.M) {
+	if os.Getenv("STRINGER_TEST_IS_STRINGER") != "" {
+		main()
+		os.Exit(0)
+	}
+
+	// Inform subprocesses that they should run the cmd/stringer main instead of
+	// running tests. It's a close approximation to building and running the real
+	// command, and much less complicated and expensive to build and clean up.
+	os.Setenv("STRINGER_TEST_IS_STRINGER", "1")
+
+	os.Exit(m.Run())
+}
+
 func TestEndToEnd(t *testing.T) {
-	dir, stringer := buildStringer(t)
-	defer os.RemoveAll(dir)
+	stringer := stringerPath(t)
 	// Read the testdata directory.
 	fd, err := os.Open("testdata")
 	if err != nil {
@@ -64,7 +77,7 @@
 			t.Logf("cgo is not enabled for %s", name)
 			continue
 		}
-		stringerCompileAndRun(t, dir, stringer, typeName(name), name)
+		stringerCompileAndRun(t, t.TempDir(), stringer, typeName(name), name)
 	}
 }
 
@@ -91,8 +104,8 @@
 
 // TestTags verifies that the -tags flag works as advertised.
 func TestTags(t *testing.T) {
-	dir, stringer := buildStringer(t)
-	defer os.RemoveAll(dir)
+	stringer := stringerPath(t)
+	dir := t.TempDir()
 	var (
 		protectedConst = []byte("TagProtected")
 		output         = filepath.Join(dir, "const_string.go")
@@ -139,8 +152,8 @@
 // TestConstValueChange verifies that if a constant value changes and
 // the stringer code is not regenerated, we'll get a compiler error.
 func TestConstValueChange(t *testing.T) {
-	dir, stringer := buildStringer(t)
-	defer os.RemoveAll(dir)
+	stringer := stringerPath(t)
+	dir := t.TempDir()
 	source := filepath.Join(dir, "day.go")
 	err := copy(source, filepath.Join("testdata", "day.go"))
 	if err != nil {
@@ -178,21 +191,20 @@
 	}
 }
 
-// buildStringer creates a temporary directory and installs stringer there.
-func buildStringer(t *testing.T) (dir string, stringer string) {
-	t.Helper()
-	testenv.NeedsTool(t, "go")
+var exe struct {
+	path string
+	err  error
+	once sync.Once
+}
 
-	dir, err := os.MkdirTemp("", "stringer")
-	if err != nil {
-		t.Fatal(err)
+func stringerPath(t *testing.T) string {
+	exe.once.Do(func() {
+		exe.path, exe.err = os.Executable()
+	})
+	if exe.err != nil {
+		t.Fatal(exe.err)
 	}
-	stringer = filepath.Join(dir, "stringer.exe")
-	err = run("go", "build", "-o", stringer)
-	if err != nil {
-		t.Fatalf("building stringer: %s", err)
-	}
-	return dir, stringer
+	return exe.path
 }
 
 // stringerCompileAndRun runs stringer for the named file and compiles and
diff --git a/cmd/stringer/golden_test.go b/cmd/stringer/golden_test.go
index 2e2ec58..250af05 100644
--- a/cmd/stringer/golden_test.go
+++ b/cmd/stringer/golden_test.go
@@ -451,12 +451,7 @@
 func TestGolden(t *testing.T) {
 	testenv.NeedsTool(t, "go")
 
-	dir, err := os.MkdirTemp("", "stringer")
-	if err != nil {
-		t.Error(err)
-	}
-	defer os.RemoveAll(dir)
-
+	dir := t.TempDir()
 	for _, test := range golden {
 		g := Generator{
 			trimPrefix:  test.trimPrefix,