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  `))