Home | History | Annotate | Download | only in gen
      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 
     11 package main
     12 
     13 import (
     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
     23 type 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
     29 func (s tmplData) SymFirst() string {
     30 	return string(s.Symbol[0])
     31 }
     32 
     33 // ucast casts an unsigned int to the size in s
     34 func 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
     47 func 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 
     59 type 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.
     69 var 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 
     85 type op struct {
     86 	name, symbol string
     87 }
     88 
     89 // ops that we will be generating tests for
     90 var ops = []op{op{"add", "+"}, op{"sub", "-"}, op{"div", "/"}, op{"mod", "%%"}, op{"mul", "*"}}
     91 
     92 func main() {
     93 	w := new(bytes.Buffer)
     94 	fmt.Fprintf(w, "// run\n")
     95 	fmt.Fprintf(w, "// Code generated by gen/arithBoundaryGen.go. DO NOT EDIT.\n\n")
     96 	fmt.Fprintf(w, "package main;\n")
     97 	fmt.Fprintf(w, "import \"fmt\"\n")
     98 
     99 	for _, sz := range []int{64, 32, 16, 8} {
    100 		fmt.Fprintf(w, "type utd%d struct {\n", sz)
    101 		fmt.Fprintf(w, "  a,b uint%d\n", sz)
    102 		fmt.Fprintf(w, "  add,sub,mul,div,mod uint%d\n", sz)
    103 		fmt.Fprintf(w, "}\n")
    104 
    105 		fmt.Fprintf(w, "type itd%d struct {\n", sz)
    106 		fmt.Fprintf(w, "  a,b int%d\n", sz)
    107 		fmt.Fprintf(w, "  add,sub,mul,div,mod int%d\n", sz)
    108 		fmt.Fprintf(w, "}\n")
    109 	}
    110 
    111 	// the function being tested
    112 	testFunc, err := template.New("testFunc").Parse(
    113 		`//go:noinline
    114 		func {{.Name}}_{{.Stype}}_ssa(a, b {{.Stype}}) {{.Stype}} {
    115 	return a {{.SymFirst}} b
    116 }
    117 `)
    118 	if err != nil {
    119 		panic(err)
    120 	}
    121 
    122 	// generate our functions to be tested
    123 	for _, s := range szs {
    124 		for _, o := range ops {
    125 			fd := tmplData{o.name, s.name, o.symbol}
    126 			err = testFunc.Execute(w, fd)
    127 			if err != nil {
    128 				panic(err)
    129 			}
    130 		}
    131 	}
    132 
    133 	// generate the test data
    134 	for _, s := range szs {
    135 		if len(s.u) > 0 {
    136 			fmt.Fprintf(w, "var %s_data []utd%s = []utd%s{", s.name, s.sn, s.sn)
    137 			for _, i := range s.u {
    138 				for _, j := range s.u {
    139 					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))
    140 					if j != 0 {
    141 						fmt.Fprintf(w, ", div: %d, mod: %d", ucast(i/j, s), ucast(i%j, s))
    142 					}
    143 					fmt.Fprint(w, "},\n")
    144 				}
    145 			}
    146 			fmt.Fprintf(w, "}\n")
    147 		} else {
    148 			// TODO: clean up this duplication
    149 			fmt.Fprintf(w, "var %s_data []itd%s = []itd%s{", s.name, s.sn, s.sn)
    150 			for _, i := range s.i {
    151 				for _, j := range s.i {
    152 					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))
    153 					if j != 0 {
    154 						fmt.Fprintf(w, ", div: %d, mod: %d", icast(i/j, s), icast(i%j, s))
    155 					}
    156 					fmt.Fprint(w, "},\n")
    157 				}
    158 			}
    159 			fmt.Fprintf(w, "}\n")
    160 		}
    161 	}
    162 
    163 	fmt.Fprintf(w, "var failed bool\n\n")
    164 	fmt.Fprintf(w, "func main() {\n\n")
    165 
    166 	verify, err := template.New("tst").Parse(
    167 		`if got := {{.Name}}_{{.Stype}}_ssa(v.a, v.b); got != v.{{.Name}} {
    168        fmt.Printf("{{.Name}}_{{.Stype}} %d{{.Symbol}}%d = %d, wanted %d\n",v.a,v.b,got,v.{{.Name}})
    169        failed = true
    170 }
    171 `)
    172 
    173 	for _, s := range szs {
    174 		fmt.Fprintf(w, "for _, v := range %s_data {\n", s.name)
    175 
    176 		for _, o := range ops {
    177 			// avoid generating tests that divide by zero
    178 			if o.name == "div" || o.name == "mod" {
    179 				fmt.Fprint(w, "if v.b != 0 {")
    180 			}
    181 
    182 			err = verify.Execute(w, tmplData{o.name, s.name, o.symbol})
    183 
    184 			if o.name == "div" || o.name == "mod" {
    185 				fmt.Fprint(w, "\n}\n")
    186 			}
    187 
    188 			if err != nil {
    189 				panic(err)
    190 			}
    191 
    192 		}
    193 		fmt.Fprint(w, "    }\n")
    194 	}
    195 
    196 	fmt.Fprintf(w, `if failed {
    197         panic("tests failed")
    198     }
    199 `)
    200 	fmt.Fprintf(w, "}\n")
    201 
    202 	// gofmt result
    203 	b := w.Bytes()
    204 	src, err := format.Source(b)
    205 	if err != nil {
    206 		fmt.Printf("%s\n", b)
    207 		panic(err)
    208 	}
    209 
    210 	// write to file
    211 	err = ioutil.WriteFile("../arithBoundary.go", src, 0666)
    212 	if err != nil {
    213 		log.Fatalf("can't write output: %v\n", err)
    214 	}
    215 }
    216