1
0
mirror of https://github.com/golang/go synced 2024-11-05 16:16:11 -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:
Rob Pike 2014-09-05 15:42:23 -07:00
parent d30a33e346
commit d0448f16e3
9 changed files with 418 additions and 59 deletions

View 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()
}

View File

@ -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{

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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)
}
}