.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / cmd / stringer / endtoend_test.go
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.
4
5 // go command is not available on android
6
7 //go:build !android
8 // +build !android
9
10 package main
11
12 import (
13         "bytes"
14         "fmt"
15         "go/build"
16         "io"
17         "io/ioutil"
18         "os"
19         "os/exec"
20         "path/filepath"
21         "strings"
22         "testing"
23
24         "golang.org/x/tools/internal/testenv"
25 )
26
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.
31
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")
37         if err != nil {
38                 t.Fatal(err)
39         }
40         defer fd.Close()
41         names, err := fd.Readdirnames(-1)
42         if err != nil {
43                 t.Fatalf("Readdirnames: %s", err)
44         }
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)
49                         continue
50                 }
51                 if strings.HasPrefix(name, "tag_") || strings.HasPrefix(name, "vary_") {
52                         // This file is used for tag processing in TestTags or TestConstValueChange, below.
53                         continue
54                 }
55                 if name == "cgo.go" && !build.Default.CgoEnabled {
56                         t.Logf("cgo is not enabled for %s", name)
57                         continue
58                 }
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)
62         }
63 }
64
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)
69         var (
70                 protectedConst = []byte("TagProtected")
71                 output         = filepath.Join(dir, "const_string.go")
72         )
73         for _, file := range []string{"tag_main.go", "tag_tag.go"} {
74                 err := copy(filepath.Join(dir, file), filepath.Join("testdata", file))
75                 if err != nil {
76                         t.Fatal(err)
77                 }
78         }
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", ".")
85         if err != nil {
86                 t.Fatal(err)
87         }
88         result, err := ioutil.ReadFile(output)
89         if err != nil {
90                 t.Fatal(err)
91         }
92         if bytes.Contains(result, protectedConst) {
93                 t.Fatal("tagged variable appears in untagged run")
94         }
95         err = os.Remove(output)
96         if err != nil {
97                 t.Fatal(err)
98         }
99         err = runInDir(dir, stringer, "-type", "Const", "-tags", "tag", ".")
100         if err != nil {
101                 t.Fatal(err)
102         }
103         result, err = ioutil.ReadFile(output)
104         if err != nil {
105                 t.Fatal(err)
106         }
107         if !bytes.Contains(result, protectedConst) {
108                 t.Fatal("tagged variable does not appear in tagged run")
109         }
110 }
111
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"))
119         if err != nil {
120                 t.Fatal(err)
121         }
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)
125         if err != nil {
126                 t.Fatal(err)
127         }
128         // Run the binary in the temporary directory as a sanity check.
129         err = run("go", "run", stringSource, source)
130         if err != nil {
131                 t.Fatal(err)
132         }
133         // Overwrite the source file with a version that has changed constants.
134         err = copy(source, filepath.Join("testdata", "vary_day.go"))
135         if err != nil {
136                 t.Fatal(err)
137         }
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
141         // file is invalid.
142         //
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
146         // Go compilers.
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)
149         if err == nil {
150                 t.Fatal("unexpected compiler success")
151         }
152 }
153
154 // buildStringer creates a temporary directory and installs stringer there.
155 func buildStringer(t *testing.T) (dir string, stringer string) {
156         t.Helper()
157         testenv.NeedsTool(t, "go")
158
159         dir, err := ioutil.TempDir("", "stringer")
160         if err != nil {
161                 t.Fatal(err)
162         }
163         stringer = filepath.Join(dir, "stringer.exe")
164         err = run("go", "build", "-o", stringer)
165         if err != nil {
166                 t.Fatalf("building stringer: %s", err)
167         }
168         return dir, stringer
169 }
170
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) {
174         t.Helper()
175         t.Logf("run: %s %s\n", fileName, typeName)
176         source := filepath.Join(dir, fileName)
177         err := copy(source, filepath.Join("testdata", fileName))
178         if err != nil {
179                 t.Fatalf("copying file to temporary directory: %s", err)
180         }
181         stringSource := filepath.Join(dir, typeName+"_string.go")
182         // Run stringer in temporary directory.
183         err = run(stringer, "-type", typeName, "-output", stringSource, source)
184         if err != nil {
185                 t.Fatal(err)
186         }
187         // Run the binary in the temporary directory.
188         err = run("go", "run", stringSource, source)
189         if err != nil {
190                 t.Fatal(err)
191         }
192 }
193
194 // copy copies the from file to the to file.
195 func copy(to, from string) error {
196         toFd, err := os.Create(to)
197         if err != nil {
198                 return err
199         }
200         defer toFd.Close()
201         fromFd, err := os.Open(from)
202         if err != nil {
203                 return err
204         }
205         defer fromFd.Close()
206         _, err = io.Copy(toFd, fromFd)
207         return err
208 }
209
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...)
214 }
215
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...)
220         cmd.Dir = dir
221         cmd.Stdout = os.Stdout
222         cmd.Stderr = os.Stderr
223         cmd.Env = append(os.Environ(), "GO111MODULE=auto")
224         return cmd.Run()
225 }