mirror of
https://github.com/golang/go
synced 2024-11-18 13:04:46 -07:00
go.tools/cmd/stringer: add end-to-end test that compiles, runs, and verifies the generated method
In the process, fix a bug in one of the method generators. LGTM=gri R=gri CC=golang-codereviews https://golang.org/cl/141130043
This commit is contained in:
parent
d30a33e346
commit
d0448f16e3
99
cmd/stringer/endtoend_test.go
Normal file
99
cmd/stringer/endtoend_test.go
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This file contains a test that compiles and runs each program in testdata
|
||||
// after generating the string method for its type. The rule is that for testdata/x.go
|
||||
// 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 TestEndToEnd(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "stringer")
|
||||
defer os.RemoveAll(dir)
|
||||
// Create stringer in temporary directory.
|
||||
stringer := filepath.Join(dir, "stringer")
|
||||
err = run("go", "build", "-o", stringer, "stringer.go")
|
||||
if err != nil {
|
||||
t.Fatalf("building stringer: %s", err)
|
||||
}
|
||||
// Read the testdata directory.
|
||||
fd, err := os.Open("testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fd.Close()
|
||||
names, err := fd.Readdirnames(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Readdirnames: %s", err)
|
||||
}
|
||||
// Generate, compile, and run the test programs.
|
||||
for _, name := range names {
|
||||
if !strings.HasSuffix(name, ".go") {
|
||||
t.Errorf("%s is not a Go file", name)
|
||||
continue
|
||||
}
|
||||
// Names are known to be ASCII and long enough.
|
||||
typeName := fmt.Sprintf("%c%s", name[0]+'A'-'a', name[1:len(name)-len(".go")])
|
||||
stringerCompileAndRun(t, dir, stringer, typeName, name)
|
||||
}
|
||||
}
|
||||
|
||||
// stringerCompileAndRun runs stringer for the named file and compiles and
|
||||
// runs the target binary in directory dir. That binary will panic if the String method is incorrect.
|
||||
func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName string) {
|
||||
t.Logf("run: %s %s\n", fileName, typeName)
|
||||
source := filepath.Join(dir, fileName)
|
||||
err := copy(source, filepath.Join("testdata", fileName))
|
||||
if err != nil {
|
||||
t.Fatalf("copying file to temporary directory: %s", err)
|
||||
}
|
||||
stringSource := filepath.Join(dir, typeName+"_string.go")
|
||||
// Run stringer in temporary directory.
|
||||
err = run(stringer, "-type", typeName, "-output", stringSource, source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Run the binary in the temporary directory.
|
||||
err = run("go", "run", stringSource, source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// copy copies the from file to the to file.
|
||||
func copy(to, from string) error {
|
||||
toFd, err := os.Create(to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer toFd.Close()
|
||||
fromFd, err := os.Open(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fromFd.Close()
|
||||
_, err = io.Copy(toFd, fromFd)
|
||||
return err
|
||||
}
|
||||
|
||||
// run runs a single command and returns an error if it does not succeed.
|
||||
// os/exec should have this function, to be honest.
|
||||
func run(name string, arg ...string) error {
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
@ -25,9 +25,9 @@ var golden = []Golden{
|
||||
{"day", day_in, day_out},
|
||||
{"offset", offset_in, offset_out},
|
||||
{"gap", gap_in, gap_out},
|
||||
{"neg", neg_in, neg_out},
|
||||
{"uneg", uneg_in, uneg_out},
|
||||
{"map", map_in, map_out},
|
||||
{"num", num_in, num_out},
|
||||
{"unum", unum_in, unum_out},
|
||||
{"prime", prime_in, prime_out},
|
||||
}
|
||||
|
||||
// Each example starts with "type XXX [u]int", with a single space separating them.
|
||||
@ -95,60 +95,58 @@ func (i Number) String() string {
|
||||
`
|
||||
|
||||
// Gaps and an offset.
|
||||
const gap_in = `type Num int
|
||||
const gap_in = `type Gap int
|
||||
const (
|
||||
Two Num = 2
|
||||
Three Num = 3
|
||||
Five Num = 5
|
||||
Six Num = 6
|
||||
Seven Num = 7
|
||||
Eight Num = 8
|
||||
Nine Num = 9
|
||||
Eleven Num = 11
|
||||
Two Gap = 2
|
||||
Three Gap = 3
|
||||
Five Gap = 5
|
||||
Six Gap = 6
|
||||
Seven Gap = 7
|
||||
Eight Gap = 8
|
||||
Nine Gap = 9
|
||||
Eleven Gap = 11
|
||||
)
|
||||
`
|
||||
|
||||
const gap_out = `
|
||||
const (
|
||||
_Num_name_0 = "TwoThree"
|
||||
_Num_name_1 = "FiveSixSevenEightNine"
|
||||
_Num_name_2 = "Eleven"
|
||||
_Gap_name_0 = "TwoThree"
|
||||
_Gap_name_1 = "FiveSixSevenEightNine"
|
||||
_Gap_name_2 = "Eleven"
|
||||
)
|
||||
|
||||
var (
|
||||
_Num_index_0 = [...]uint8{3, 8}
|
||||
_Num_index_1 = [...]uint8{4, 7, 12, 17, 21}
|
||||
_Num_index_2 = [...]uint8{6}
|
||||
_Gap_index_0 = [...]uint8{3, 8}
|
||||
_Gap_index_1 = [...]uint8{4, 7, 12, 17, 21}
|
||||
_Gap_index_2 = [...]uint8{6}
|
||||
)
|
||||
|
||||
func (i Num) String() string {
|
||||
func (i Gap) String() string {
|
||||
switch {
|
||||
case 2 <= i && i < 3:
|
||||
case 2 <= i && i <= 3:
|
||||
i -= 2
|
||||
lo := uint8(0)
|
||||
if i > 2 {
|
||||
i -= 2
|
||||
} else {
|
||||
lo = _Num_index_0[i-1]
|
||||
if i > 0 {
|
||||
lo = _Gap_index_0[i-1]
|
||||
}
|
||||
return _Num_name_0[lo:_Num_index_0[i]]
|
||||
case 5 <= i && i < 9:
|
||||
return _Gap_name_0[lo:_Gap_index_0[i]]
|
||||
case 5 <= i && i <= 9:
|
||||
i -= 5
|
||||
lo := uint8(0)
|
||||
if i > 5 {
|
||||
i -= 5
|
||||
} else {
|
||||
lo = _Num_index_1[i-1]
|
||||
if i > 0 {
|
||||
lo = _Gap_index_1[i-1]
|
||||
}
|
||||
return _Num_name_1[lo:_Num_index_1[i]]
|
||||
return _Gap_name_1[lo:_Gap_index_1[i]]
|
||||
case i == 11:
|
||||
return _Num_name_2
|
||||
return _Gap_name_2
|
||||
default:
|
||||
return fmt.Sprintf("Num(%d)", i)
|
||||
return fmt.Sprintf("Gap(%d)", i)
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// Signed integers spanning zero.
|
||||
const neg_in = `type Num int
|
||||
const num_in = `type Num int
|
||||
const (
|
||||
m_2 Num = -2 + iota
|
||||
m_1
|
||||
@ -158,7 +156,7 @@ const (
|
||||
)
|
||||
`
|
||||
|
||||
const neg_out = `
|
||||
const num_out = `
|
||||
const _Num_name = "m_2m_1m0m1m2"
|
||||
|
||||
var _Num_index = [...]uint8{3, 6, 8, 10, 12}
|
||||
@ -178,38 +176,55 @@ func (i Num) String() string {
|
||||
`
|
||||
|
||||
// Unsigned integers spanning zero.
|
||||
const uneg_in = `type UNum uint
|
||||
const unum_in = `type Unum uint
|
||||
const (
|
||||
m_2 UNum = ^UNum(0)-2
|
||||
m_2 Unum = iota + 253
|
||||
m_1
|
||||
m0
|
||||
)
|
||||
|
||||
const (
|
||||
m0 Unum = iota
|
||||
m1
|
||||
m2
|
||||
)
|
||||
`
|
||||
|
||||
const uneg_out = `
|
||||
const _UNum_name = "m_2"
|
||||
const unum_out = `
|
||||
const (
|
||||
_Unum_name_0 = "m0m1m2"
|
||||
_Unum_name_1 = "m_2m_1"
|
||||
)
|
||||
|
||||
var _UNum_index = [...]uint8{3}
|
||||
var (
|
||||
_Unum_index_0 = [...]uint8{2, 4, 6}
|
||||
_Unum_index_1 = [...]uint8{3, 6}
|
||||
)
|
||||
|
||||
func (i UNum) String() string {
|
||||
i -= 18446744073709551613
|
||||
if i >= UNum(len(_UNum_index)) {
|
||||
return fmt.Sprintf("UNum(%d)", i+18446744073709551613)
|
||||
func (i Unum) String() string {
|
||||
switch {
|
||||
case 0 <= i && i <= 2:
|
||||
i -= 0
|
||||
lo := uint8(0)
|
||||
if i > 0 {
|
||||
lo = _Unum_index_0[i-1]
|
||||
}
|
||||
return _Unum_name_0[lo:_Unum_index_0[i]]
|
||||
case 253 <= i && i <= 254:
|
||||
i -= 253
|
||||
lo := uint8(0)
|
||||
if i > 0 {
|
||||
lo = _Unum_index_1[i-1]
|
||||
}
|
||||
return _Unum_name_1[lo:_Unum_index_1[i]]
|
||||
default:
|
||||
return fmt.Sprintf("Unum(%d)", i)
|
||||
}
|
||||
hi := _UNum_index[i]
|
||||
lo := uint8(0)
|
||||
if i > 0 {
|
||||
lo = _UNum_index[i-1]
|
||||
}
|
||||
return _UNum_name[lo:hi]
|
||||
}
|
||||
`
|
||||
|
||||
// Enough gaps to trigger a map implementation of the method.
|
||||
// Also includes a duplicate to test that it doesn't cause problems
|
||||
const map_in = `type Prime int
|
||||
const prime_in = `type Prime int
|
||||
const (
|
||||
p2 Prime = 2
|
||||
p3 Prime = 3
|
||||
@ -228,7 +243,7 @@ const (
|
||||
)
|
||||
`
|
||||
|
||||
const map_out = `
|
||||
const prime_out = `
|
||||
const _Prime_name = "p2p3p5p7p11p13p17p19p23p29p37p41p43"
|
||||
|
||||
var _Prime_map = map[Prime]string{
|
||||
|
@ -103,11 +103,11 @@ func main() {
|
||||
log.SetPrefix("stringer: ")
|
||||
flag.Usage = Usage
|
||||
flag.Parse()
|
||||
types := strings.Split(*typeNames, ",")
|
||||
if len(types) == 0 {
|
||||
if len(*typeNames) == 0 {
|
||||
flag.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
types := strings.Split(*typeNames, ",")
|
||||
|
||||
// We accept either one directory or a list of files. Which do we have?
|
||||
args := flag.Args()
|
||||
@ -610,11 +610,10 @@ func (g *Generator) buildMultipleRuns(runs [][]Value, typeName string) {
|
||||
g.Printf("\t\treturn _%s_name_%d\n", typeName, i)
|
||||
continue
|
||||
}
|
||||
g.Printf("\tcase %s <= i && i < %s:\n", &values[0], &values[len(values)-1])
|
||||
g.Printf("\tcase %s <= i && i <= %s:\n", &values[0], &values[len(values)-1])
|
||||
g.Printf("\t\ti -= %s\n", &values[0])
|
||||
g.Printf("\t\tlo := uint%d(0)\n", usize(len(values)))
|
||||
g.Printf("\t\tif i > %s {\n", &values[0])
|
||||
g.Printf("\t\t\ti -= %s\n", &values[0])
|
||||
g.Printf("\t\t} else {\n")
|
||||
g.Printf("\t\tif i > 0 {\n")
|
||||
g.Printf("\t\t\tlo = _%s_index_%d[i-1]\n", typeName, i)
|
||||
g.Printf("\t\t}\n")
|
||||
g.Printf("\t\treturn _%s_name_%d[lo:_%s_index_%d[i]]\n", typeName, i, typeName, i)
|
||||
|
39
cmd/stringer/testdata/day.go
vendored
Normal file
39
cmd/stringer/testdata/day.go
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Simple test: enumeration of type int starting at 0.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Day int
|
||||
|
||||
const (
|
||||
Monday Day = iota
|
||||
Tuesday
|
||||
Wednesday
|
||||
Thursday
|
||||
Friday
|
||||
Saturday
|
||||
Sunday
|
||||
)
|
||||
|
||||
func main() {
|
||||
ck(Monday, "Monday")
|
||||
ck(Tuesday, "Tuesday")
|
||||
ck(Wednesday, "Wednesday")
|
||||
ck(Thursday, "Thursday")
|
||||
ck(Friday, "Friday")
|
||||
ck(Saturday, "Saturday")
|
||||
ck(Sunday, "Sunday")
|
||||
ck(-127, "Day(-127)")
|
||||
ck(127, "Day(127)")
|
||||
}
|
||||
|
||||
func ck(day Day, str string) {
|
||||
if fmt.Sprint(day) != str {
|
||||
panic("day.go: " + str)
|
||||
}
|
||||
}
|
44
cmd/stringer/testdata/gap.go
vendored
Normal file
44
cmd/stringer/testdata/gap.go
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Gaps and an offset.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Gap int
|
||||
|
||||
const (
|
||||
Two Gap = 2
|
||||
Three Gap = 3
|
||||
Five Gap = 5
|
||||
Six Gap = 6
|
||||
Seven Gap = 7
|
||||
Eight Gap = 8
|
||||
Nine Gap = 9
|
||||
Eleven Gap = 11
|
||||
)
|
||||
|
||||
func main() {
|
||||
ck(0, "Gap(0)")
|
||||
ck(1, "Gap(1)")
|
||||
ck(Two, "Two")
|
||||
ck(Three, "Three")
|
||||
ck(4, "Gap(4)")
|
||||
ck(Five, "Five")
|
||||
ck(Six, "Six")
|
||||
ck(Seven, "Seven")
|
||||
ck(Eight, "Eight")
|
||||
ck(Nine, "Nine")
|
||||
ck(10, "Gap(10)")
|
||||
ck(Eleven, "Eleven")
|
||||
ck(12, "Gap(12)")
|
||||
}
|
||||
|
||||
func ck(gap Gap, str string) {
|
||||
if fmt.Sprint(gap) != str {
|
||||
panic("gap.go: " + str)
|
||||
}
|
||||
}
|
35
cmd/stringer/testdata/num.go
vendored
Normal file
35
cmd/stringer/testdata/num.go
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Signed integers spanning zero.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Num int
|
||||
|
||||
const (
|
||||
m_2 Num = -2 + iota
|
||||
m_1
|
||||
m0
|
||||
m1
|
||||
m2
|
||||
)
|
||||
|
||||
func main() {
|
||||
ck(-3, "Num(-3)")
|
||||
ck(m_2, "m_2")
|
||||
ck(m_1, "m_1")
|
||||
ck(m0, "m0")
|
||||
ck(m1, "m1")
|
||||
ck(m2, "m2")
|
||||
ck(3, "Num(3)")
|
||||
}
|
||||
|
||||
func ck(num Num, str string) {
|
||||
if fmt.Sprint(num) != str {
|
||||
panic("num.go: " + str)
|
||||
}
|
||||
}
|
34
cmd/stringer/testdata/number.go
vendored
Normal file
34
cmd/stringer/testdata/number.go
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Enumeration with an offset.
|
||||
// Also includes a duplicate.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Number int
|
||||
|
||||
const (
|
||||
_ Number = iota
|
||||
One
|
||||
Two
|
||||
Three
|
||||
AnotherOne = One // Duplicate; note that AnotherOne doesn't appear below.
|
||||
)
|
||||
|
||||
func main() {
|
||||
ck(One, "One")
|
||||
ck(Two, "Two")
|
||||
ck(Three, "Three")
|
||||
ck(AnotherOne, "One")
|
||||
ck(127, "Number(127)")
|
||||
}
|
||||
|
||||
func ck(num Number, str string) {
|
||||
if fmt.Sprint(num) != str {
|
||||
panic("number.go: " + str)
|
||||
}
|
||||
}
|
56
cmd/stringer/testdata/prime.go
vendored
Normal file
56
cmd/stringer/testdata/prime.go
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Enough gaps to trigger a map implementation of the method.
|
||||
// Also includes a duplicate to test that it doesn't cause problems
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Prime int
|
||||
|
||||
const (
|
||||
p2 Prime = 2
|
||||
p3 Prime = 3
|
||||
p5 Prime = 5
|
||||
p7 Prime = 7
|
||||
p77 Prime = 7 // Duplicate; note that p77 doesn't appear below.
|
||||
p11 Prime = 11
|
||||
p13 Prime = 13
|
||||
p17 Prime = 17
|
||||
p19 Prime = 19
|
||||
p23 Prime = 23
|
||||
p29 Prime = 29
|
||||
p37 Prime = 31
|
||||
p41 Prime = 41
|
||||
p43 Prime = 43
|
||||
)
|
||||
|
||||
func main() {
|
||||
ck(0, "Prime(0)")
|
||||
ck(1, "Prime(1)")
|
||||
ck(p2, "p2")
|
||||
ck(p3, "p3")
|
||||
ck(4, "Prime(4)")
|
||||
ck(p5, "p5")
|
||||
ck(p7, "p7")
|
||||
ck(p77, "p7")
|
||||
ck(p11, "p11")
|
||||
ck(p13, "p13")
|
||||
ck(p17, "p17")
|
||||
ck(p19, "p19")
|
||||
ck(p23, "p23")
|
||||
ck(p29, "p29")
|
||||
ck(p37, "p37")
|
||||
ck(p41, "p41")
|
||||
ck(p43, "p43")
|
||||
ck(44, "Prime(44)")
|
||||
}
|
||||
|
||||
func ck(prime Prime, str string) {
|
||||
if fmt.Sprint(prime) != str {
|
||||
panic("prime.go: " + str)
|
||||
}
|
||||
}
|
38
cmd/stringer/testdata/unum.go
vendored
Normal file
38
cmd/stringer/testdata/unum.go
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Unsigned integers spanning zero.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Unum uint8
|
||||
|
||||
const (
|
||||
m_2 Unum = iota + 253
|
||||
m_1
|
||||
)
|
||||
|
||||
const (
|
||||
m0 Unum = iota
|
||||
m1
|
||||
m2
|
||||
)
|
||||
|
||||
func main() {
|
||||
ck(^Unum(0)-3, "Unum(252)")
|
||||
ck(m_2, "m_2")
|
||||
ck(m_1, "m_1")
|
||||
ck(m0, "m0")
|
||||
ck(m1, "m1")
|
||||
ck(m2, "m2")
|
||||
ck(3, "Unum(3)")
|
||||
}
|
||||
|
||||
func ck(unum Unum, str string) {
|
||||
if fmt.Sprint(unum) != str {
|
||||
panic("unum.go: " + str)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user