1 // Copyright 2014 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // go command is not available on android
24 "golang.org/x/tools/internal/testenv"
27 // This file contains a test that compiles and runs each program in testdata
28 // after generating the string method for its type. The rule is that for testdata/x.go
29 // we run stringer -type X and then compile and run the program. The resulting
30 // binary panics if the String method for X is not correct, including for error cases.
32 func TestEndToEnd(t *testing.T) {
33 dir, stringer := buildStringer(t)
34 defer os.RemoveAll(dir)
35 // Read the testdata directory.
36 fd, err := os.Open("testdata")
41 names, err := fd.Readdirnames(-1)
43 t.Fatalf("Readdirnames: %s", err)
45 // Generate, compile, and run the test programs.
46 for _, name := range names {
47 if !strings.HasSuffix(name, ".go") {
48 t.Errorf("%s is not a Go file", name)
51 if strings.HasPrefix(name, "tag_") || strings.HasPrefix(name, "vary_") {
52 // This file is used for tag processing in TestTags or TestConstValueChange, below.
55 if name == "cgo.go" && !build.Default.CgoEnabled {
56 t.Logf("cgo is not enabled for %s", name)
59 // Names are known to be ASCII and long enough.
60 typeName := fmt.Sprintf("%c%s", name[0]+'A'-'a', name[1:len(name)-len(".go")])
61 stringerCompileAndRun(t, dir, stringer, typeName, name)
65 // TestTags verifies that the -tags flag works as advertised.
66 func TestTags(t *testing.T) {
67 dir, stringer := buildStringer(t)
68 defer os.RemoveAll(dir)
70 protectedConst = []byte("TagProtected")
71 output = filepath.Join(dir, "const_string.go")
73 for _, file := range []string{"tag_main.go", "tag_tag.go"} {
74 err := copy(filepath.Join(dir, file), filepath.Join("testdata", file))
79 // Run stringer in the directory that contains the package files.
80 // We cannot run stringer in the current directory for the following reasons:
81 // - Versions of Go earlier than Go 1.11, do not support absolute directories as a pattern.
82 // - When the current directory is inside a go module, the path will not be considered
83 // a valid path to a package.
84 err := runInDir(dir, stringer, "-type", "Const", ".")
88 result, err := ioutil.ReadFile(output)
92 if bytes.Contains(result, protectedConst) {
93 t.Fatal("tagged variable appears in untagged run")
95 err = os.Remove(output)
99 err = runInDir(dir, stringer, "-type", "Const", "-tags", "tag", ".")
103 result, err = ioutil.ReadFile(output)
107 if !bytes.Contains(result, protectedConst) {
108 t.Fatal("tagged variable does not appear in tagged run")
112 // TestConstValueChange verifies that if a constant value changes and
113 // the stringer code is not regenerated, we'll get a compiler error.
114 func TestConstValueChange(t *testing.T) {
115 dir, stringer := buildStringer(t)
116 defer os.RemoveAll(dir)
117 source := filepath.Join(dir, "day.go")
118 err := copy(source, filepath.Join("testdata", "day.go"))
122 stringSource := filepath.Join(dir, "day_string.go")
123 // Run stringer in the directory that contains the package files.
124 err = runInDir(dir, stringer, "-type", "Day", "-output", stringSource)
128 // Run the binary in the temporary directory as a sanity check.
129 err = run("go", "run", stringSource, source)
133 // Overwrite the source file with a version that has changed constants.
134 err = copy(source, filepath.Join("testdata", "vary_day.go"))
138 // Unfortunately different compilers may give different error messages,
139 // so there's no easy way to verify that the build failed specifically
140 // because the constants changed rather than because the vary_day.go
143 // Instead we'll just rely on manual inspection of the polluted test
144 // output. An alternative might be to check that the error output
145 // matches a set of possible error strings emitted by known
147 fmt.Fprintf(os.Stderr, "Note: the following messages should indicate an out-of-bounds compiler error\n")
148 err = run("go", "build", stringSource, source)
150 t.Fatal("unexpected compiler success")
154 // buildStringer creates a temporary directory and installs stringer there.
155 func buildStringer(t *testing.T) (dir string, stringer string) {
157 testenv.NeedsTool(t, "go")
159 dir, err := ioutil.TempDir("", "stringer")
163 stringer = filepath.Join(dir, "stringer.exe")
164 err = run("go", "build", "-o", stringer)
166 t.Fatalf("building stringer: %s", err)
171 // stringerCompileAndRun runs stringer for the named file and compiles and
172 // runs the target binary in directory dir. That binary will panic if the String method is incorrect.
173 func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName string) {
175 t.Logf("run: %s %s\n", fileName, typeName)
176 source := filepath.Join(dir, fileName)
177 err := copy(source, filepath.Join("testdata", fileName))
179 t.Fatalf("copying file to temporary directory: %s", err)
181 stringSource := filepath.Join(dir, typeName+"_string.go")
182 // Run stringer in temporary directory.
183 err = run(stringer, "-type", typeName, "-output", stringSource, source)
187 // Run the binary in the temporary directory.
188 err = run("go", "run", stringSource, source)
194 // copy copies the from file to the to file.
195 func copy(to, from string) error {
196 toFd, err := os.Create(to)
201 fromFd, err := os.Open(from)
206 _, err = io.Copy(toFd, fromFd)
210 // run runs a single command and returns an error if it does not succeed.
211 // os/exec should have this function, to be honest.
212 func run(name string, arg ...string) error {
213 return runInDir(".", name, arg...)
216 // runInDir runs a single command in directory dir and returns an error if
217 // it does not succeed.
218 func runInDir(dir, name string, arg ...string) error {
219 cmd := exec.Command(name, arg...)
221 cmd.Stdout = os.Stdout
222 cmd.Stderr = os.Stderr
223 cmd.Env = append(os.Environ(), "GO111MODULE=auto")