diff --git a/cmd/stringer/endtoend_test.go b/cmd/stringer/endtoend_test.go new file mode 100644 index 0000000000..69a417b6c3 --- /dev/null +++ b/cmd/stringer/endtoend_test.go @@ -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() +} diff --git a/cmd/stringer/golden_test.go b/cmd/stringer/golden_test.go index 284de4bf8c..709ae08e7c 100644 --- a/cmd/stringer/golden_test.go +++ b/cmd/stringer/golden_test.go @@ -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{ diff --git a/cmd/stringer/stringer.go b/cmd/stringer/stringer.go index 3bc5000d4f..02d51a1a63 100644 --- a/cmd/stringer/stringer.go +++ b/cmd/stringer/stringer.go @@ -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) diff --git a/cmd/stringer/testdata/day.go b/cmd/stringer/testdata/day.go new file mode 100644 index 0000000000..35fa8dce8a --- /dev/null +++ b/cmd/stringer/testdata/day.go @@ -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) + } +} diff --git a/cmd/stringer/testdata/gap.go b/cmd/stringer/testdata/gap.go new file mode 100644 index 0000000000..bc8a90c547 --- /dev/null +++ b/cmd/stringer/testdata/gap.go @@ -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) + } +} diff --git a/cmd/stringer/testdata/num.go b/cmd/stringer/testdata/num.go new file mode 100644 index 0000000000..0d5ab10707 --- /dev/null +++ b/cmd/stringer/testdata/num.go @@ -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) + } +} diff --git a/cmd/stringer/testdata/number.go b/cmd/stringer/testdata/number.go new file mode 100644 index 0000000000..7f1c8246c0 --- /dev/null +++ b/cmd/stringer/testdata/number.go @@ -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) + } +} diff --git a/cmd/stringer/testdata/prime.go b/cmd/stringer/testdata/prime.go new file mode 100644 index 0000000000..f551a1a0bb --- /dev/null +++ b/cmd/stringer/testdata/prime.go @@ -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) + } +} diff --git a/cmd/stringer/testdata/unum.go b/cmd/stringer/testdata/unum.go new file mode 100644 index 0000000000..2f8508f49c --- /dev/null +++ b/cmd/stringer/testdata/unum.go @@ -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) + } +}