1// Copyright 2015 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// This program generates a test to verify that the standard arithmetic
6// operators properly handle some special cases. The test file should be
7// generated with a known working version of go.
8// launch with `go run arithBoundaryGen.go` a file called arithBoundary.go
9// will be written into the parent directory containing the tests
10
11package main
12
13import (
14	"bytes"
15	"fmt"
16	"go/format"
17	"io/ioutil"
18	"log"
19	"text/template"
20)
21
22// used for interpolation in a text template
23type tmplData struct {
24	Name, Stype, Symbol string
25}
26
27// used to work around an issue with the mod symbol being
28// interpreted as part of a format string
29func (s tmplData) SymFirst() string {
30	return string(s.Symbol[0])
31}
32
33// ucast casts an unsigned int to the size in s
34func ucast(i uint64, s sizedTestData) uint64 {
35	switch s.name {
36	case "uint32":
37		return uint64(uint32(i))
38	case "uint16":
39		return uint64(uint16(i))
40	case "uint8":
41		return uint64(uint8(i))
42	}
43	return i
44}
45
46// icast casts a signed int to the size in s
47func icast(i int64, s sizedTestData) int64 {
48	switch s.name {
49	case "int32":
50		return int64(int32(i))
51	case "int16":
52		return int64(int16(i))
53	case "int8":
54		return int64(int8(i))
55	}
56	return i
57}
58
59type sizedTestData struct {
60	name string
61	sn   string
62	u    []uint64
63	i    []int64
64}
65
66// values to generate tests. these should include the smallest and largest values, along
67// with any other values that might cause issues. we generate n^2 tests for each size to
68// cover all cases.
69var szs = []sizedTestData{
70	sizedTestData{name: "uint64", sn: "64", u: []uint64{0, 1, 4294967296, 0xffffFFFFffffFFFF}},
71	sizedTestData{name: "int64", sn: "64", i: []int64{-0x8000000000000000, -0x7FFFFFFFFFFFFFFF,
72		-4294967296, -1, 0, 1, 4294967296, 0x7FFFFFFFFFFFFFFE, 0x7FFFFFFFFFFFFFFF}},
73
74	sizedTestData{name: "uint32", sn: "32", u: []uint64{0, 1, 4294967295}},
75	sizedTestData{name: "int32", sn: "32", i: []int64{-0x80000000, -0x7FFFFFFF, -1, 0,
76		1, 0x7FFFFFFF}},
77
78	sizedTestData{name: "uint16", sn: "16", u: []uint64{0, 1, 65535}},
79	sizedTestData{name: "int16", sn: "16", i: []int64{-32768, -32767, -1, 0, 1, 32766, 32767}},
80
81	sizedTestData{name: "uint8", sn: "8", u: []uint64{0, 1, 255}},
82	sizedTestData{name: "int8", sn: "8", i: []int64{-128, -127, -1, 0, 1, 126, 127}},
83}
84
85type op struct {
86	name, symbol string
87}
88
89// ops that we will be generating tests for
90var ops = []op{op{"add", "+"}, op{"sub", "-"}, op{"div", "/"}, op{"mod", "%%"}, op{"mul", "*"}}
91
92func main() {
93	w := new(bytes.Buffer)
94	fmt.Fprintf(w, "// Code generated by gen/arithBoundaryGen.go. DO NOT EDIT.\n\n")
95	fmt.Fprintf(w, "package main;\n")
96	fmt.Fprintf(w, "import \"testing\"\n")
97
98	for _, sz := range []int{64, 32, 16, 8} {
99		fmt.Fprintf(w, "type utd%d struct {\n", sz)
100		fmt.Fprintf(w, "  a,b uint%d\n", sz)
101		fmt.Fprintf(w, "  add,sub,mul,div,mod uint%d\n", sz)
102		fmt.Fprintf(w, "}\n")
103
104		fmt.Fprintf(w, "type itd%d struct {\n", sz)
105		fmt.Fprintf(w, "  a,b int%d\n", sz)
106		fmt.Fprintf(w, "  add,sub,mul,div,mod int%d\n", sz)
107		fmt.Fprintf(w, "}\n")
108	}
109
110	// the function being tested
111	testFunc, err := template.New("testFunc").Parse(
112		`//go:noinline
113		func {{.Name}}_{{.Stype}}_ssa(a, b {{.Stype}}) {{.Stype}} {
114	return a {{.SymFirst}} b
115}
116`)
117	if err != nil {
118		panic(err)
119	}
120
121	// generate our functions to be tested
122	for _, s := range szs {
123		for _, o := range ops {
124			fd := tmplData{o.name, s.name, o.symbol}
125			err = testFunc.Execute(w, fd)
126			if err != nil {
127				panic(err)
128			}
129		}
130	}
131
132	// generate the test data
133	for _, s := range szs {
134		if len(s.u) > 0 {
135			fmt.Fprintf(w, "var %s_data []utd%s = []utd%s{", s.name, s.sn, s.sn)
136			for _, i := range s.u {
137				for _, j := range s.u {
138					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))
139					if j != 0 {
140						fmt.Fprintf(w, ", div: %d, mod: %d", ucast(i/j, s), ucast(i%j, s))
141					}
142					fmt.Fprint(w, "},\n")
143				}
144			}
145			fmt.Fprintf(w, "}\n")
146		} else {
147			// TODO: clean up this duplication
148			fmt.Fprintf(w, "var %s_data []itd%s = []itd%s{", s.name, s.sn, s.sn)
149			for _, i := range s.i {
150				for _, j := range s.i {
151					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))
152					if j != 0 {
153						fmt.Fprintf(w, ", div: %d, mod: %d", icast(i/j, s), icast(i%j, s))
154					}
155					fmt.Fprint(w, "},\n")
156				}
157			}
158			fmt.Fprintf(w, "}\n")
159		}
160	}
161
162	fmt.Fprintf(w, "//TestArithmeticBoundary tests boundary results for arithmetic operations.\n")
163	fmt.Fprintf(w, "func TestArithmeticBoundary(t *testing.T) {\n\n")
164
165	verify, err := template.New("tst").Parse(
166		`if got := {{.Name}}_{{.Stype}}_ssa(v.a, v.b); got != v.{{.Name}} {
167       t.Errorf("{{.Name}}_{{.Stype}} %d{{.Symbol}}%d = %d, wanted %d\n",v.a,v.b,got,v.{{.Name}})
168}
169`)
170
171	for _, s := range szs {
172		fmt.Fprintf(w, "for _, v := range %s_data {\n", s.name)
173
174		for _, o := range ops {
175			// avoid generating tests that divide by zero
176			if o.name == "div" || o.name == "mod" {
177				fmt.Fprint(w, "if v.b != 0 {")
178			}
179
180			err = verify.Execute(w, tmplData{o.name, s.name, o.symbol})
181
182			if o.name == "div" || o.name == "mod" {
183				fmt.Fprint(w, "\n}\n")
184			}
185
186			if err != nil {
187				panic(err)
188			}
189
190		}
191		fmt.Fprint(w, "    }\n")
192	}
193
194	fmt.Fprintf(w, "}\n")
195
196	// gofmt result
197	b := w.Bytes()
198	src, err := format.Source(b)
199	if err != nil {
200		fmt.Printf("%s\n", b)
201		panic(err)
202	}
203
204	// write to file
205	err = ioutil.WriteFile("../arithBoundary_test.go", src, 0666)
206	if err != nil {
207		log.Fatalf("can't write output: %v\n", err)
208	}
209}
210