github.com/rgonomic/rgo@v0.2.2-0.20220708095818-4747f0905d6e/internal/rgo/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  	"fmt"
    14  	"go/format"
    15  	"go/types"
    16  	"log"
    17  	"os"
    18  	"os/exec"
    19  	"path/filepath"
    20  	"text/template"
    21  )
    22  
    23  var crafted = []pkg{
    24  	{
    25  		Name:  "mixed",
    26  		Path:  "github.com/rgonomic/rgo/internal/rgo/testdata",
    27  		Types: []string{"T int", "S1 string"},
    28  		Funcs: []fn{
    29  			{In: []string{"int"}, Out: []string{"int", "int"}, Named: false},
    30  			{In: []string{"int"}, Out: []string{"float64", "int"}, Named: true},
    31  			{In: []string{"int"}},
    32  			{In: []string{"T", "S1"}, Out: []string{"S1"}},
    33  		},
    34  	},
    35  	{
    36  		Name: "slice_of_slices",
    37  		Path: "github.com/rgonomic/rgo/internal/rgo/testdata",
    38  		Funcs: []fn{
    39  			{In: []string{"[][]float64"}, Out: []string{"[][]float64"}, Named: false},
    40  		},
    41  	},
    42  	{
    43  		Name: "map_of_slices",
    44  		Path: "github.com/rgonomic/rgo/internal/rgo/testdata",
    45  		Funcs: []fn{
    46  			{In: []string{"map[string][]float64"}, Out: []string{"map[string][]float64"}, Named: false},
    47  		},
    48  	},
    49  }
    50  
    51  type pkg struct {
    52  	Name  string
    53  	UID   int
    54  	Path  string
    55  	Types []string
    56  	Funcs []fn
    57  }
    58  
    59  type fn struct {
    60  	pkg   *pkg
    61  	In    []string // Input parameter types.
    62  	Out   []string // Output parameter types.
    63  	Named bool     // Whether the output parameter types are named.
    64  }
    65  
    66  func builtins() []pkg {
    67  	skip := map[types.BasicKind]bool{
    68  		types.Uintptr: true,
    69  		types.Uint64:  true,
    70  		types.Int64:   true,
    71  	}
    72  	var pkgs []pkg
    73  	for t := types.Bool; t <= types.String; t++ {
    74  		if skip[t] {
    75  			continue
    76  		}
    77  		pkgs = addTypeTest(pkgs, types.Typ[t])
    78  	}
    79  	pkgs = addTypeTest(pkgs, types.Universe.Lookup("byte").Type().(*types.Basic))
    80  	pkgs = addTypeTest(pkgs, types.Universe.Lookup("rune").Type().(*types.Basic))
    81  
    82  	return pkgs
    83  }
    84  
    85  func addTypeTest(dst []pkg, typ *types.Basic) []pkg {
    86  	name := typ.String()
    87  
    88  	// Generate scalar value test functions.
    89  	dst = append(dst,
    90  		pkg{Name: fmt.Sprintf("%s_in", name), Funcs: []fn{
    91  			{In: []string{name}}}},
    92  		pkg{Name: fmt.Sprintf("%s_out", name), Funcs: []fn{
    93  			{Out: []string{name}}}},
    94  		pkg{Name: fmt.Sprintf("%s_out_named", name), Funcs: []fn{
    95  			{Out: []string{name}, Named: true}}},
    96  	)
    97  
    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  	dst = append(dst,
   109  		pkg{Name: fmt.Sprintf("%s_array_in", name), Funcs: []fn{
   110  			{In: []string{"[4]" + name}}}},
   111  		pkg{Name: fmt.Sprintf("%s_array_out", name), Funcs: []fn{
   112  			{Out: []string{"[4]" + name}}}},
   113  		pkg{Name: fmt.Sprintf("%s_array_out_named", name), Funcs: []fn{
   114  			{Out: []string{"[4]" + name}, Named: true}}},
   115  	)
   116  
   117  	// Generate struct value test functions.
   118  	st := fmt.Sprintf(`struct{F1 %[1]s; F2 %[1]s "rgo:\"Rname\""}`, name)
   119  	dst = append(dst,
   120  		pkg{Name: fmt.Sprintf("struct_%s_in", name), Funcs: []fn{
   121  			{In: []string{st}}}},
   122  		pkg{Name: fmt.Sprintf("struct_%s_out", name), Funcs: []fn{
   123  			{Out: []string{st}}}},
   124  		pkg{Name: fmt.Sprintf("struct_%s_out_named", name), Funcs: []fn{
   125  			{Out: []string{st}, Named: true}}},
   126  	)
   127  
   128  	// Generate map[string]T value test functions.
   129  	mt := fmt.Sprintf("map[string]%s", name)
   130  	dst = append(dst,
   131  		pkg{Name: fmt.Sprintf("string_%s_map_in", name), Funcs: []fn{
   132  			{In: []string{mt}}}},
   133  		pkg{Name: fmt.Sprintf("string_%s_map_out", name), Funcs: []fn{
   134  			{Out: []string{mt}}}},
   135  		pkg{Name: fmt.Sprintf("string_%s_map_out_named", name), Funcs: []fn{
   136  			{Out: []string{mt}, Named: true}}},
   137  	)
   138  
   139  	return dst
   140  }
   141  
   142  func main() {
   143  	suff := make(map[string]int)
   144  	for _, cases := range [][]pkg{builtins(), crafted} {
   145  		for _, c := range cases {
   146  			c.UID = suff[c.Name]
   147  			suff[c.Name]++
   148  			for i := range c.Funcs {
   149  				c.Funcs[i].pkg = &c
   150  			}
   151  
   152  			pkg := fmt.Sprintf("%s_%d", c.Name, c.UID)
   153  			err := os.Mkdir(pkg, 0o755)
   154  			if err != nil && !os.IsExist(err) {
   155  				log.Fatalf("failed to create testing package dir: %v", err)
   156  			}
   157  			f, err := os.Create(filepath.Join(pkg, c.Name+".go"))
   158  			if err != nil {
   159  				log.Fatalf("failed to create testing source file: %v", err)
   160  			}
   161  
   162  			var buf bytes.Buffer
   163  			err = src.Execute(&buf, c)
   164  			if err != nil {
   165  				log.Fatalf("failed to execute template: %v", err)
   166  			}
   167  			b, err := format.Source(buf.Bytes())
   168  			if err != nil {
   169  				log.Fatalf("failed to format source: %v", err)
   170  			}
   171  			_, err = f.Write(b)
   172  			if err != nil {
   173  				log.Fatalf("failed to write source: %v", err)
   174  			}
   175  
   176  			err = f.Close()
   177  			if err != nil {
   178  				log.Fatalf("failed to close testing source file: %v", err)
   179  			}
   180  
   181  			err = os.Remove(filepath.Join(pkg, "go.mod"))
   182  			if err != nil && !os.IsNotExist(err) {
   183  				log.Fatalf("failed to remove go.mod file: %v", err)
   184  			}
   185  			cmd := exec.Command("go", "mod", "init", pkg)
   186  			cmd.Dir = filepath.Join(".", pkg)
   187  			err = cmd.Run()
   188  			if err != nil {
   189  				log.Fatalf("failed create go.mod file: %v", err)
   190  			}
   191  		}
   192  	}
   193  }
   194  
   195  var src = template.Must(template.New("Go source").Parse(`// Code generated by "go generate github.com/rgonomic/rgo/internal/pkg/testdata"; DO NOT EDIT.
   196  
   197  package {{.Name}}_{{.UID}}
   198  {{if .Types}}
   199  type (
   200  {{- range $i, $t := .Types}}
   201  	{{$t}}{{end}}
   202  ){{end}}
   203  {{- range $i, $fn := .Funcs}}
   204  
   205  // Test{{$i}} does things with {{$fn.In}} and returns {{$fn.Out}}.
   206  func Test{{$i}}({{if $fn.In}}{{range $j, $p := $fn.In -}}
   207  	{{- if ne $j 0}}, {{end}}par{{$j}} {{$p -}}
   208  {{- end}}{{end}}){{if $fn.Out}} ({{range $j, $p := $fn.Out -}}
   209  	{{- if ne $j 0}}, {{end}}{{if $fn.Named}}res{{$j}} {{end}}{{$p}}{{end}}){{end}} { {{if not $fn.Named}}{{range $j, $p := $fn.Out}}
   210  	var res{{$j}} {{$p}}{{end}}{{end}}
   211  {{if $fn.Out}}	return {{range $j, $p := $fn.Out -}}
   212  	{{- if ne $j 0}}, {{end}}res{{$j}}{{end}}
   213  {{end}}}{{end}}
   214  `))