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