Skip to content

Commit 4e80b32

Browse files
vikblomgopherbot
authored andcommitted
cmd/stringer: support types declared in test files
Adds additional passes over the test variants of the given package. Picks the most "narrow" (least amount of files) package where each type can be found. The generated code will be placed in the same package and test-or-not-test file as the original type declaration. Closes golang/go#66557. Change-Id: I2354cd44e357a9ce0b45f5e2fadc1c141455a88d Reviewed-on: https://go-review.googlesource.com/c/tools/+/604535 Reviewed-by: Robert Findley <rfindley@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Robert Findley <rfindley@google.com> Reviewed-by: Michael Knyszek <mknyszek@google.com>
1 parent 60b6bcd commit 4e80b32

File tree

4 files changed

+736
-70
lines changed

4 files changed

+736
-70
lines changed

cmd/stringer/endtoend_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"os"
1818
"path"
1919
"path/filepath"
20+
"reflect"
21+
"slices"
2022
"strings"
2123
"sync"
2224
"testing"
@@ -197,6 +199,127 @@ func TestConstValueChange(t *testing.T) {
197199
}
198200
}
199201

202+
var testfileSrcs = map[string]string{
203+
"go.mod": "module foo",
204+
205+
// Normal file in the package.
206+
"main.go": `package foo
207+
208+
type Foo int
209+
210+
const (
211+
fooX Foo = iota
212+
fooY
213+
fooZ
214+
)
215+
`,
216+
217+
// Test file in the package.
218+
"main_test.go": `package foo
219+
220+
type Bar int
221+
222+
const (
223+
barX Bar = iota
224+
barY
225+
barZ
226+
)
227+
`,
228+
229+
// Test file in the test package.
230+
"main_pkg_test.go": `package foo_test
231+
232+
type Baz int
233+
234+
const (
235+
bazX Baz = iota
236+
bazY
237+
bazZ
238+
)
239+
`,
240+
}
241+
242+
// Test stringer on types defined in different kinds of tests.
243+
// The generated code should not interfere between itself.
244+
func TestTestFiles(t *testing.T) {
245+
testenv.NeedsTool(t, "go")
246+
stringer := stringerPath(t)
247+
248+
dir := t.TempDir()
249+
t.Logf("TestTestFiles in: %s \n", dir)
250+
for name, src := range testfileSrcs {
251+
source := filepath.Join(dir, name)
252+
err := os.WriteFile(source, []byte(src), 0666)
253+
if err != nil {
254+
t.Fatalf("write file: %s", err)
255+
}
256+
}
257+
258+
// Must run stringer in the temp directory, see TestTags.
259+
err := runInDir(t, dir, stringer, "-type=Foo,Bar,Baz", dir)
260+
if err != nil {
261+
t.Fatalf("run stringer: %s", err)
262+
}
263+
264+
// Check that stringer has created the expected files.
265+
content, err := os.ReadDir(dir)
266+
if err != nil {
267+
t.Fatalf("read dir: %s", err)
268+
}
269+
gotFiles := []string{}
270+
for _, f := range content {
271+
if !f.IsDir() {
272+
gotFiles = append(gotFiles, f.Name())
273+
}
274+
}
275+
wantFiles := []string{
276+
// Original.
277+
"go.mod",
278+
"main.go",
279+
"main_test.go",
280+
"main_pkg_test.go",
281+
// Generated.
282+
"foo_string.go",
283+
"bar_string_test.go",
284+
"baz_string_test.go",
285+
}
286+
slices.Sort(gotFiles)
287+
slices.Sort(wantFiles)
288+
if !reflect.DeepEqual(gotFiles, wantFiles) {
289+
t.Errorf("stringer generated files:\n%s\n\nbut want:\n%s",
290+
strings.Join(gotFiles, "\n"),
291+
strings.Join(wantFiles, "\n"),
292+
)
293+
}
294+
295+
// Run go test as a smoke test.
296+
err = runInDir(t, dir, "go", "test", "-count=1", ".")
297+
if err != nil {
298+
t.Fatalf("go test: %s", err)
299+
}
300+
}
301+
302+
// The -output flag cannot be used in combiation with matching types across multiple packages.
303+
func TestCollidingOutput(t *testing.T) {
304+
testenv.NeedsTool(t, "go")
305+
stringer := stringerPath(t)
306+
307+
dir := t.TempDir()
308+
for name, src := range testfileSrcs {
309+
source := filepath.Join(dir, name)
310+
err := os.WriteFile(source, []byte(src), 0666)
311+
if err != nil {
312+
t.Fatalf("write file: %s", err)
313+
}
314+
}
315+
316+
// Must run stringer in the temp directory, see TestTags.
317+
err := runInDir(t, dir, stringer, "-type=Foo,Bar,Baz", "-output=somefile.go", dir)
318+
if err == nil {
319+
t.Fatal("unexpected stringer success")
320+
}
321+
}
322+
200323
var exe struct {
201324
path string
202325
err error

cmd/stringer/golden_test.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -455,11 +455,6 @@ func TestGolden(t *testing.T) {
455455
for _, test := range golden {
456456
test := test
457457
t.Run(test.name, func(t *testing.T) {
458-
g := Generator{
459-
trimPrefix: test.trimPrefix,
460-
lineComment: test.lineComment,
461-
logf: t.Logf,
462-
}
463458
input := "package test\n" + test.input
464459
file := test.name + ".go"
465460
absFile := filepath.Join(dir, file)
@@ -468,16 +463,24 @@ func TestGolden(t *testing.T) {
468463
t.Fatal(err)
469464
}
470465

471-
g.parsePackage([]string{absFile}, nil)
466+
pkgs := loadPackages([]string{absFile}, nil, test.trimPrefix, test.lineComment, t.Logf)
467+
if len(pkgs) != 1 {
468+
t.Fatalf("got %d parsed packages but expected 1", len(pkgs))
469+
}
472470
// Extract the name and type of the constant from the first line.
473471
tokens := strings.SplitN(test.input, " ", 3)
474472
if len(tokens) != 3 {
475473
t.Fatalf("%s: need type declaration on first line", test.name)
476474
}
477-
g.generate(tokens[1])
475+
476+
g := Generator{
477+
pkg: pkgs[0],
478+
logf: t.Logf,
479+
}
480+
g.generate(tokens[1], findValues(tokens[1], pkgs[0]))
478481
got := string(g.format())
479482
if got != test.output {
480-
t.Errorf("%s: got(%d)\n====\n%q====\nexpected(%d)\n====%q", test.name, len(got), got, len(test.output), test.output)
483+
t.Errorf("%s: got(%d)\n====\n%q====\nexpected(%d)\n====\n%q", test.name, len(got), got, len(test.output), test.output)
481484
}
482485
})
483486
}

0 commit comments

Comments
 (0)