| // Copyright 2015 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. | |
| // This program generates a test to verify that the standard arithmetic | |
| // operators properly handle some special cases. The test file should be | |
| // generated with a known working version of go. | |
| // launch with `go run arithBoundaryGen.go` a file called arithBoundary.go | |
| // will be written into the parent directory containing the tests | |
| package main | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "go/format" | |
| "log" | |
| "text/template" | |
| ) | |
| // used for interpolation in a text template | |
| type tmplData struct { | |
| Name, Stype, Symbol string | |
| } | |
| // used to work around an issue with the mod symbol being | |
| // interpreted as part of a format string | |
| func (s tmplData) SymFirst() string { | |
| return string(s.Symbol[0]) | |
| } | |
| // ucast casts an unsigned int to the size in s | |
| func ucast(i uint64, s sizedTestData) uint64 { | |
| switch s.name { | |
| case "uint32": | |
| return uint64(uint32(i)) | |
| case "uint16": | |
| return uint64(uint16(i)) | |
| case "uint8": | |
| return uint64(uint8(i)) | |
| } | |
| return i | |
| } | |
| // icast casts a signed int to the size in s | |
| func icast(i int64, s sizedTestData) int64 { | |
| switch s.name { | |
| case "int32": | |
| return int64(int32(i)) | |
| case "int16": | |
| return int64(int16(i)) | |
| case "int8": | |
| return int64(int8(i)) | |
| } | |
| return i | |
| } | |
| type sizedTestData struct { | |
| name string | |
| sn string | |
| u []uint64 | |
| i []int64 | |
| } | |
| // values to generate tests. these should include the smallest and largest values, along | |
| // with any other values that might cause issues. we generate n^2 tests for each size to | |
| // cover all cases. | |
| var szs = []sizedTestData{ | |
| sizedTestData{name: "uint64", sn: "64", u: []uint64{0, 1, 4294967296, 0xffffFFFFffffFFFF}}, | |
| sizedTestData{name: "int64", sn: "64", i: []int64{-0x8000000000000000, -0x7FFFFFFFFFFFFFFF, | |
| -4294967296, -1, 0, 1, 4294967296, 0x7FFFFFFFFFFFFFFE, 0x7FFFFFFFFFFFFFFF}}, | |
| sizedTestData{name: "uint32", sn: "32", u: []uint64{0, 1, 4294967295}}, | |
| sizedTestData{name: "int32", sn: "32", i: []int64{-0x80000000, -0x7FFFFFFF, -1, 0, | |
| 1, 0x7FFFFFFF}}, | |
| sizedTestData{name: "uint16", sn: "16", u: []uint64{0, 1, 65535}}, | |
| sizedTestData{name: "int16", sn: "16", i: []int64{-32768, -32767, -1, 0, 1, 32766, 32767}}, | |
| sizedTestData{name: "uint8", sn: "8", u: []uint64{0, 1, 255}}, | |
| sizedTestData{name: "int8", sn: "8", i: []int64{-128, -127, -1, 0, 1, 126, 127}}, | |
| } | |
| type op struct { | |
| name, symbol string | |
| } | |
| // ops that we will be generating tests for | |
| var ops = []op{op{"add", "+"}, op{"sub", "-"}, op{"div", "/"}, op{"mod", "%%"}, op{"mul", "*"}} | |
| func main() { | |
| w := new(bytes.Buffer) | |
| fmt.Fprintf(w, "// Code generated by gen/arithBoundaryGen.go. DO NOT EDIT.\n\n") | |
| fmt.Fprintf(w, "package main;\n") | |
| fmt.Fprintf(w, "import \"testing\"\n") | |
| for _, sz := range []int{64, 32, 16, 8} { | |
| fmt.Fprintf(w, "type utd%d struct {\n", sz) | |
| fmt.Fprintf(w, " a,b uint%d\n", sz) | |
| fmt.Fprintf(w, " add,sub,mul,div,mod uint%d\n", sz) | |
| fmt.Fprintf(w, "}\n") | |
| fmt.Fprintf(w, "type itd%d struct {\n", sz) | |
| fmt.Fprintf(w, " a,b int%d\n", sz) | |
| fmt.Fprintf(w, " add,sub,mul,div,mod int%d\n", sz) | |
| fmt.Fprintf(w, "}\n") | |
| } | |
| // the function being tested | |
| testFunc, err := template.New("testFunc").Parse( | |
| `//go:noinline | |
| func {{.Name}}_{{.Stype}}_ssa(a, b {{.Stype}}) {{.Stype}} { | |
| return a {{.SymFirst}} b | |
| } | |
| `) | |
| if err != nil { | |
| panic(err) | |
| } | |
| // generate our functions to be tested | |
| for _, s := range szs { | |
| for _, o := range ops { | |
| fd := tmplData{o.name, s.name, o.symbol} | |
| err = testFunc.Execute(w, fd) | |
| if err != nil { | |
| panic(err) | |
| } | |
| } | |
| } | |
| // generate the test data | |
| for _, s := range szs { | |
| if len(s.u) > 0 { | |
| fmt.Fprintf(w, "var %s_data []utd%s = []utd%s{", s.name, s.sn, s.sn) | |
| for _, i := range s.u { | |
| for _, j := range s.u { | |
| fmt.Fprintf(w, "utd%s{a: %d, b: %d, add: %d, sub: %d, mul: %d", s.sn, i, j, ucast(i+j, s), ucast(i-j, s), ucast(i*j, s)) | |
| if j != 0 { | |
| fmt.Fprintf(w, ", div: %d, mod: %d", ucast(i/j, s), ucast(i%j, s)) | |
| } | |
| fmt.Fprint(w, "},\n") | |
| } | |
| } | |
| fmt.Fprintf(w, "}\n") | |
| } else { | |
| // TODO: clean up this duplication | |
| fmt.Fprintf(w, "var %s_data []itd%s = []itd%s{", s.name, s.sn, s.sn) | |
| for _, i := range s.i { | |
| for _, j := range s.i { | |
| fmt.Fprintf(w, "itd%s{a: %d, b: %d, add: %d, sub: %d, mul: %d", s.sn, i, j, icast(i+j, s), icast(i-j, s), icast(i*j, s)) | |
| if j != 0 { | |
| fmt.Fprintf(w, ", div: %d, mod: %d", icast(i/j, s), icast(i%j, s)) | |
| } | |
| fmt.Fprint(w, "},\n") | |
| } | |
| } | |
| fmt.Fprintf(w, "}\n") | |
| } | |
| } | |
| fmt.Fprintf(w, "//TestArithmeticBoundary tests boundary results for arithmetic operations.\n") | |
| fmt.Fprintf(w, "func TestArithmeticBoundary(t *testing.T) {\n\n") | |
| verify, err := template.New("tst").Parse( | |
| `if got := {{.Name}}_{{.Stype}}_ssa(v.a, v.b); got != v.{{.Name}} { | |
| t.Errorf("{{.Name}}_{{.Stype}} %d{{.Symbol}}%d = %d, wanted %d\n",v.a,v.b,got,v.{{.Name}}) | |
| } | |
| `) | |
| for _, s := range szs { | |
| fmt.Fprintf(w, "for _, v := range %s_data {\n", s.name) | |
| for _, o := range ops { | |
| // avoid generating tests that divide by zero | |
| if o.name == "div" || o.name == "mod" { | |
| fmt.Fprint(w, "if v.b != 0 {") | |
| } | |
| err = verify.Execute(w, tmplData{o.name, s.name, o.symbol}) | |
| if o.name == "div" || o.name == "mod" { | |
| fmt.Fprint(w, "\n}\n") | |
| } | |
| if err != nil { | |
| panic(err) | |
| } | |
| } | |
| fmt.Fprint(w, " }\n") | |
| } | |
| fmt.Fprintf(w, "}\n") | |
| // gofmt result | |
| b := w.Bytes() | |
| src, err := format.Source(b) | |
| if err != nil { | |
| fmt.Printf("%s\n", b) | |
| panic(err) | |
| } | |
| // write to file | |
| err = os.WriteFile("../arithBoundary_test.go", src, 0666) | |
| if err != nil { | |
| log.Fatalf("can't write output: %v\n", err) | |
| } | |
| } |