github.com/rgonomic/rgo@v0.2.2-0.20220708095818-4747f0905d6e/internal/pkg/testdata/testgen.go (about) 1 // Copyright ©2020 The rgonomic 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 //go:generate go run ./testgen.go 6 7 // The testgen command constructs small single file packages with no-op, but 8 // buildable source for testing the internal/pkg code. 9 package main 10 11 import ( 12 "bytes" 13 "encoding/json" 14 "fmt" 15 "go/format" 16 "go/token" 17 "go/types" 18 "log" 19 "os" 20 "path" 21 "path/filepath" 22 "sort" 23 "strings" 24 "text/template" 25 ) 26 27 var crafted = []pkg{ 28 { 29 Name: "mixed", 30 Path: "github.com/rgonomic/rgo/internal/pkg/testdata", 31 Types: []string{"T int", "S1 string"}, 32 Funcs: []fn{ 33 {In: []string{"int"}, Out: []string{"int", "int"}, Named: false}, 34 {In: []string{"int"}, Out: []string{"float64", "int"}, Named: true}, 35 {In: []string{"int"}}, 36 {In: []string{"T", "S1"}, HelpIn: []string{"int", "string"}, Out: []string{"S1"}, HelpOut: []string{"string"}}, 37 }, 38 }, 39 } 40 41 type pkg struct { 42 Name string 43 UID int 44 Path string 45 Types []string 46 Funcs []fn 47 } 48 49 type fn struct { 50 pkg *pkg 51 In []string `json:"in,omitempty"` // Input parameter types. 52 HelpIn []string `json:"-"` // Composing types for input parameters. 53 Out []string `json:"out,omitempty"` // Output parameter types. 54 HelpOut []string `json:"-"` // Composing types for output parameters. 55 Named bool `json:"-"` // Whether the output parameter types are named. 56 } 57 58 func builtins() []pkg { 59 skip := map[types.BasicKind]bool{ 60 types.Uint64: true, 61 types.Int64: true, 62 } 63 var pkgs []pkg 64 for t := types.Bool; t <= types.String; t++ { 65 if skip[t] { 66 continue 67 } 68 pkgs = addTypeTest(pkgs, types.Typ[t]) 69 } 70 pkgs = addTypeTest(pkgs, types.Universe.Lookup("byte").Type().(*types.Basic)) 71 pkgs = addTypeTest(pkgs, types.Universe.Lookup("rune").Type().(*types.Basic)) 72 73 return pkgs 74 } 75 76 func addTypeTest(dst []pkg, typ *types.Basic) []pkg { 77 name := typ.String() 78 79 // Set up the helpers we will need for non-R native types. 80 helpIn := []string{name} 81 helpOut := []string{name} 82 if typ.Kind() == types.Complex64 { 83 // complex64 is not handled by R, so we need to convert via complex128. 84 helpIn = append(helpIn, "complex128") 85 } 86 87 // Generate scalar value test functions. 88 dst = append(dst, 89 pkg{Name: fmt.Sprintf("%s_in", name), Funcs: []fn{ 90 {In: []string{name}, HelpIn: helpIn}}}, 91 pkg{Name: fmt.Sprintf("%s_out", name), Funcs: []fn{ 92 {Out: []string{name}}}}, 93 pkg{Name: fmt.Sprintf("%s_out_named", name), Funcs: []fn{ 94 {Out: []string{name}, Named: true}}}, 95 ) 96 97 // Generate slice value test functions. 98 dst = append(dst, 99 pkg{Name: fmt.Sprintf("%s_slice_in", name), Funcs: []fn{ 100 {In: []string{"[]" + name}}}}, 101 pkg{Name: fmt.Sprintf("%s_slice_out", name), Funcs: []fn{ 102 {Out: []string{"[]" + name}}}}, 103 pkg{Name: fmt.Sprintf("%s_slice_out_named", name), Funcs: []fn{ 104 {Out: []string{"[]" + name}, Named: true}}}, 105 ) 106 107 // Generate array value test functions. 108 arrayHelp := []string{"[]" + name} 109 dst = append(dst, 110 pkg{Name: fmt.Sprintf("%s_array_in", name), Funcs: []fn{ 111 {In: []string{"[4]" + name}, HelpIn: arrayHelp}}}, 112 pkg{Name: fmt.Sprintf("%s_array_out", name), Funcs: []fn{ 113 {Out: []string{"[4]" + name}, HelpOut: arrayHelp}}}, 114 pkg{Name: fmt.Sprintf("%s_array_out_named", name), Funcs: []fn{ 115 {Out: []string{"[4]" + name}, HelpOut: arrayHelp, Named: true}}}, 116 ) 117 118 // Generate struct value test functions. 119 st := fmt.Sprintf(`struct{F1 %[1]s; F2 %[1]s "rgo:\"Rname\""}`, name) 120 dst = append(dst, 121 pkg{Name: fmt.Sprintf("struct_%s_in", name), Funcs: []fn{ 122 {In: []string{st}, HelpIn: helpIn}}}, 123 pkg{Name: fmt.Sprintf("struct_%s_out", name), Funcs: []fn{ 124 {Out: []string{st}, HelpOut: helpOut}}}, 125 pkg{Name: fmt.Sprintf("struct_%s_out_named", name), Funcs: []fn{ 126 {Out: []string{st}, HelpOut: helpOut, Named: true}}}, 127 ) 128 129 // Generate map[string]T value test functions. 130 // 131 // Maps also require that we can obtain strings for keys. 132 mt := fmt.Sprintf("map[string]%s", name) 133 mapHelpIn := append(helpIn[:len(helpIn):len(helpIn)], "string") 134 mapHelpOut := append(helpOut[:len(helpOut):len(helpOut)], "string") 135 dst = append(dst, 136 pkg{Name: fmt.Sprintf("string_%s_map_in", name), Funcs: []fn{ 137 {In: []string{mt}, HelpIn: mapHelpIn}}}, 138 pkg{Name: fmt.Sprintf("string_%s_map_out", name), Funcs: []fn{ 139 {Out: []string{mt}, HelpOut: mapHelpOut}}}, 140 pkg{Name: fmt.Sprintf("string_%s_map_out_named", name), Funcs: []fn{ 141 {Out: []string{mt}, HelpOut: mapHelpOut, Named: true}}}, 142 ) 143 144 return dst 145 } 146 147 func main() { 148 suff := make(map[string]int) 149 for _, cases := range [][]pkg{builtins(), crafted} { 150 for _, c := range cases { 151 c.UID = suff[c.Name] 152 suff[c.Name]++ 153 for i := range c.Funcs { 154 c.Funcs[i].pkg = &c 155 } 156 157 pkg := fmt.Sprintf("%s_%d", c.Name, c.UID) 158 err := os.Mkdir(pkg, 0o755) 159 if err != nil && !os.IsExist(err) { 160 log.Fatalf("failed to create testing package dir: %v", err) 161 } 162 f, err := os.Create(filepath.Join(pkg, c.Name+".go")) 163 if err != nil { 164 log.Fatalf("failed to create testing source file: %v", err) 165 } 166 167 var buf bytes.Buffer 168 err = src.Execute(&buf, c) 169 if err != nil { 170 log.Fatalf("failed to execute template: %v", err) 171 } 172 b, err := format.Source(buf.Bytes()) 173 if err != nil { 174 log.Fatalf("failed to format source: %v", err) 175 } 176 _, err = f.Write(b) 177 if err != nil { 178 log.Fatalf("failed to write source: %v", err) 179 } 180 181 err = f.Close() 182 if err != nil { 183 log.Fatalf("failed to close testing source file: %v", err) 184 } 185 } 186 } 187 } 188 189 func (f fn) JSON() string { 190 pth := path.Join(f.pkg.Path, fmt.Sprintf("%s_%d", f.pkg.Name, f.pkg.UID)) 191 var t fn 192 t.In = uniq(f.In, f.HelpIn...) 193 addPathPrefix(pth, t.In) 194 t.Out = uniq(f.Out, f.HelpOut...) 195 addPathPrefix(pth, t.Out) 196 b, err := json.Marshal(t) 197 if err != nil { 198 panic(err) 199 } 200 return string(b) 201 } 202 203 func addPathPrefix(path string, types []string) { 204 for i, typ := range types { 205 if token.IsExported(typ) { 206 types[i] = fmt.Sprintf("%s.%s", path, typ) 207 } 208 } 209 } 210 211 func uniq(s []string, extra ...string) []string { 212 if len(s)+len(extra) == 0 { 213 return nil 214 } 215 t := make([]string, len(s), len(s)+len(extra)) 216 copy(t, s) 217 t = append(t, extra...) 218 219 // Convert basic types to their kind. 220 for i, v := range t { 221 t[i] = strings.Replace(v, "byte", "uint8", -1) 222 t[i] = strings.Replace(t[i], "rune", "int32", -1) 223 } 224 225 sort.Strings(t) 226 i := 0 227 for _, v := range t { 228 if v != t[i] { 229 i++ 230 t[i] = v 231 } 232 } 233 return t[:i+1] 234 } 235 236 var src = template.Must(template.New("Go source").Parse(`// Code generated by "go generate github.com/rgonomic/rgo/internal/pkg/testdata"; DO NOT EDIT. 237 238 package {{.Name}}_{{.UID}} 239 {{if .Types}} 240 type ( 241 {{- range $i, $t := .Types}} 242 {{$t}}{{end}} 243 ){{end}} 244 {{- range $i, $fn := .Funcs}} 245 246 //{{.JSON}} 247 func Test{{$i}}({{if $fn.In}}{{range $j, $p := $fn.In -}} 248 {{- if ne $j 0}}, {{end}}par{{$j}} {{$p -}} 249 {{- end}}{{end}}){{if $fn.Out}} ({{range $j, $p := $fn.Out -}} 250 {{- if ne $j 0}}, {{end}}{{if $fn.Named}}res{{$j}} {{end}}{{$p}}{{end}}){{end}} { {{if not $fn.Named}}{{range $j, $p := $fn.Out}} 251 var res{{$j}} {{$p}}{{end}}{{end}} 252 {{if $fn.Out}} return {{range $j, $p := $fn.Out -}} 253 {{- if ne $j 0}}, {{end}}res{{$j}}{{end}} 254 {{end}}}{{end}} 255 `))