GoPLS Viewer

Home|gopls/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
10package main
11
12import (
13    "bytes"
14    "fmt"
15    "go/build"
16    "io"
17    "os"
18    "os/exec"
19    "path"
20    "path/filepath"
21    "strings"
22    "testing"
23
24    "golang.org/x/tools/internal/testenv"
25    "golang.org/x/tools/internal/typeparams"
26)
27
28// This file contains a test that compiles and runs each program in testdata
29// after generating the string method for its type. The rule is that for testdata/x.go
30// we run stringer -type X and then compile and run the program. The resulting
31// binary panics if the String method for X is not correct, including for error cases.
32
33func TestEndToEnd(t *testing.T) {
34    dirstringer := buildStringer(t)
35    defer os.RemoveAll(dir)
36    // Read the testdata directory.
37    fderr := os.Open("testdata")
38    if err != nil {
39        t.Fatal(err)
40    }
41    defer fd.Close()
42    nameserr := fd.Readdirnames(-1)
43    if err != nil {
44        t.Fatalf("Readdirnames: %s"err)
45    }
46    if typeparams.Enabled {
47        names = append(namesmoreTests(t"testdata/typeparams""typeparams")...)
48    }
49    // Generate, compile, and run the test programs.
50    for _name := range names {
51        if name == "typeparams" {
52            // ignore the directory containing the tests with type params
53            continue
54        }
55        if !strings.HasSuffix(name".go") {
56            t.Errorf("%s is not a Go file"name)
57            continue
58        }
59        if strings.HasPrefix(name"tag_") || strings.HasPrefix(name"vary_") {
60            // This file is used for tag processing in TestTags or TestConstValueChange, below.
61            continue
62        }
63        if name == "cgo.go" && !build.Default.CgoEnabled {
64            t.Logf("cgo is not enabled for %s"name)
65            continue
66        }
67        stringerCompileAndRun(tdirstringertypeName(name), name)
68    }
69}
70
71// a type name for stringer. use the last component of the file name with the .go
72func typeName(fname stringstring {
73    // file names are known to be ascii and end .go
74    base := path.Base(fname)
75    return fmt.Sprintf("%c%s"base[0]+'A'-'a'base[1:len(base)-len(".go")])
76}
77
78func moreTests(t *testing.Tdirnameprefix string) []string {
79    xerr := os.ReadDir(dirname)
80    if err != nil {
81        // error, but try the rest of the tests
82        t.Errorf("can't read type param tess from %s: %v"dirnameerr)
83        return nil
84    }
85    names := make([]stringlen(x))
86    for if := range x {
87        names[i] = prefix + "/" + f.Name()
88    }
89    return names
90}
91
92// TestTags verifies that the -tags flag works as advertised.
93func TestTags(t *testing.T) {
94    dirstringer := buildStringer(t)
95    defer os.RemoveAll(dir)
96    var (
97        protectedConst = []byte("TagProtected")
98        output         = filepath.Join(dir"const_string.go")
99    )
100    for _file := range []string{"tag_main.go""tag_tag.go"} {
101        err := copy(filepath.Join(dirfile), filepath.Join("testdata"file))
102        if err != nil {
103            t.Fatal(err)
104        }
105    }
106    // Run stringer in the directory that contains the package files.
107    // We cannot run stringer in the current directory for the following reasons:
108    // - Versions of Go earlier than Go 1.11, do not support absolute directories as a pattern.
109    // - When the current directory is inside a go module, the path will not be considered
110    //   a valid path to a package.
111    err := runInDir(dirstringer"-type""Const"".")
112    if err != nil {
113        t.Fatal(err)
114    }
115    resulterr := os.ReadFile(output)
116    if err != nil {
117        t.Fatal(err)
118    }
119    if bytes.Contains(resultprotectedConst) {
120        t.Fatal("tagged variable appears in untagged run")
121    }
122    err = os.Remove(output)
123    if err != nil {
124        t.Fatal(err)
125    }
126    err = runInDir(dirstringer"-type""Const""-tags""tag"".")
127    if err != nil {
128        t.Fatal(err)
129    }
130    resulterr = os.ReadFile(output)
131    if err != nil {
132        t.Fatal(err)
133    }
134    if !bytes.Contains(resultprotectedConst) {
135        t.Fatal("tagged variable does not appear in tagged run")
136    }
137}
138
139// TestConstValueChange verifies that if a constant value changes and
140// the stringer code is not regenerated, we'll get a compiler error.
141func TestConstValueChange(t *testing.T) {
142    dirstringer := buildStringer(t)
143    defer os.RemoveAll(dir)
144    source := filepath.Join(dir"day.go")
145    err := copy(sourcefilepath.Join("testdata""day.go"))
146    if err != nil {
147        t.Fatal(err)
148    }
149    stringSource := filepath.Join(dir"day_string.go")
150    // Run stringer in the directory that contains the package files.
151    err = runInDir(dirstringer"-type""Day""-output"stringSource)
152    if err != nil {
153        t.Fatal(err)
154    }
155    // Run the binary in the temporary directory as a sanity check.
156    err = run("go""run"stringSourcesource)
157    if err != nil {
158        t.Fatal(err)
159    }
160    // Overwrite the source file with a version that has changed constants.
161    err = copy(sourcefilepath.Join("testdata""vary_day.go"))
162    if err != nil {
163        t.Fatal(err)
164    }
165    // Unfortunately different compilers may give different error messages,
166    // so there's no easy way to verify that the build failed specifically
167    // because the constants changed rather than because the vary_day.go
168    // file is invalid.
169    //
170    // Instead we'll just rely on manual inspection of the polluted test
171    // output. An alternative might be to check that the error output
172    // matches a set of possible error strings emitted by known
173    // Go compilers.
174    fmt.Fprintf(os.Stderr"Note: the following messages should indicate an out-of-bounds compiler error\n")
175    err = run("go""build"stringSourcesource)
176    if err == nil {
177        t.Fatal("unexpected compiler success")
178    }
179}
180
181// buildStringer creates a temporary directory and installs stringer there.
182func buildStringer(t *testing.T) (dir stringstringer string) {
183    t.Helper()
184    testenv.NeedsTool(t"go")
185
186    direrr := os.MkdirTemp("""stringer")
187    if err != nil {
188        t.Fatal(err)
189    }
190    stringer = filepath.Join(dir"stringer.exe")
191    err = run("go""build""-o"stringer)
192    if err != nil {
193        t.Fatalf("building stringer: %s"err)
194    }
195    return dirstringer
196}
197
198// stringerCompileAndRun runs stringer for the named file and compiles and
199// runs the target binary in directory dir. That binary will panic if the String method is incorrect.
200func stringerCompileAndRun(t *testing.TdirstringertypeNamefileName string) {
201    t.Helper()
202    t.Logf("run: %s %s\n"fileNametypeName)
203    source := filepath.Join(dirpath.Base(fileName))
204    err := copy(sourcefilepath.Join("testdata"fileName))
205    if err != nil {
206        t.Fatalf("copying file to temporary directory: %s"err)
207    }
208    stringSource := filepath.Join(dirtypeName+"_string.go")
209    // Run stringer in temporary directory.
210    err = run(stringer"-type"typeName"-output"stringSourcesource)
211    if err != nil {
212        t.Fatal(err)
213    }
214    // Run the binary in the temporary directory.
215    err = run("go""run"stringSourcesource)
216    if err != nil {
217        t.Fatal(err)
218    }
219}
220
221// copy copies the from file to the to file.
222func copy(tofrom stringerror {
223    toFderr := os.Create(to)
224    if err != nil {
225        return err
226    }
227    defer toFd.Close()
228    fromFderr := os.Open(from)
229    if err != nil {
230        return err
231    }
232    defer fromFd.Close()
233    _err = io.Copy(toFdfromFd)
234    return err
235}
236
237// run runs a single command and returns an error if it does not succeed.
238// os/exec should have this function, to be honest.
239func run(name stringarg ...stringerror {
240    return runInDir("."namearg...)
241}
242
243// runInDir runs a single command in directory dir and returns an error if
244// it does not succeed.
245func runInDir(dirname stringarg ...stringerror {
246    cmd := exec.Command(namearg...)
247    cmd.Dir = dir
248    cmd.Stdout = os.Stdout
249    cmd.Stderr = os.Stderr
250    cmd.Env = append(os.Environ(), "GO111MODULE=auto")
251    return cmd.Run()
252}
253
MembersX
TestTags.dir
TestConstValueChange.dir
run
exec
path
filepath
stringerCompileAndRun.typeName
copy.from
runInDir
runInDir.name
moreTests.x
moreTests.err
stringerCompileAndRun.stringer
io
typeparams
TestTags.RangeStmt_2721.BlockStmt.err
runInDir.dir
moreTests.names
TestTags.RangeStmt_2721.file
TestConstValueChange.source
stringerCompileAndRun.t
runInDir.arg
fmt
TestEndToEnd.err
TestTags.t
TestTags.result
buildStringer.stringer
os
moreTests.RangeStmt_2380.f
TestTags.stringer
testing
moreTests.RangeStmt_2380.i
typeName.base
TestTags.err
TestConstValueChange
TestConstValueChange.t
TestConstValueChange.stringer
bytes
TestEndToEnd.fd
typeName
TestConstValueChange.err
stringerCompileAndRun
strings
TestEndToEnd.dir
testenv
run.name
stringerCompileAndRun.dir
copy
TestEndToEnd
moreTests.t
buildStringer.err
buildStringer
stringerCompileAndRun.source
stringerCompileAndRun.stringSource
copy.to
copy.fromFd
TestEndToEnd.names
TestEndToEnd.RangeStmt_1241.name
TestConstValueChange.stringSource
runInDir.cmd
stringerCompileAndRun.fileName
moreTests
moreTests.dirname
buildStringer.dir
TestEndToEnd.stringer
typeName.fname
TestTags
copy.err
moreTests.prefix
buildStringer.t
stringerCompileAndRun.err
run.arg
build
TestEndToEnd.t
copy.toFd
Members
X