...

Source file src/cuelang.org/go/cue/testdata/gen.go

Documentation: cuelang.org/go/cue/testdata

     1  // Copyright 2020 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"bytes"
    19  	"flag"
    20  	"fmt"
    21  	"go/ast"
    22  	"go/constant"
    23  	"go/format"
    24  	"log"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  
    29  	"golang.org/x/tools/go/packages"
    30  	"golang.org/x/tools/txtar"
    31  
    32  	"cuelang.org/go/cue"
    33  	cueast "cuelang.org/go/cue/ast"
    34  	cueformat "cuelang.org/go/cue/format"
    35  	"cuelang.org/go/cue/parser"
    36  	"cuelang.org/go/internal"
    37  	internaljson "cuelang.org/go/internal/encoding/json"
    38  	"cuelang.org/go/pkg/encoding/yaml"
    39  	"cuelang.org/go/tools/fix"
    40  )
    41  
    42  //go:generate go run gen.go
    43  //go:generate go test ../internal/compile --update
    44  //go:generate go test ../internal/eval --update
    45  
    46  func main() {
    47  	flag.Parse()
    48  	log.SetFlags(log.Lshortfile)
    49  
    50  	cfg := &packages.Config{
    51  		Mode: packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo,
    52  
    53  		Tests: true,
    54  	}
    55  	a, err := packages.Load(cfg, "..")
    56  	if err != nil {
    57  		log.Fatal(err)
    58  	}
    59  
    60  	for _, p := range a {
    61  		e := extractor{p: p}
    62  		e.extractFromPackage()
    63  	}
    64  }
    65  
    66  type extractor struct {
    67  	p *packages.Package
    68  
    69  	dir    string
    70  	name   string
    71  	count  int
    72  	a      *txtar.Archive
    73  	header *bytes.Buffer
    74  
    75  	tags []string
    76  }
    77  
    78  func (e *extractor) fatalf(format string, args ...interface{}) {
    79  	prefix := fmt.Sprintf("%s/%d[%s]: ", e.dir, e.count, e.name)
    80  	log.Fatalf(prefix+format, args...)
    81  }
    82  
    83  func (e *extractor) warnf(format string, args ...interface{}) {
    84  	prefix := fmt.Sprintf("%s/%d[%s]: ", e.dir, e.count, e.name)
    85  	log.Printf(prefix+format, args...)
    86  }
    87  
    88  func (e *extractor) extractFromPackage() {
    89  	for _, file := range e.p.Syntax {
    90  		for _, d := range file.Decls {
    91  			e.processTestFunc(d)
    92  		}
    93  	}
    94  }
    95  
    96  func (e *extractor) processTestFunc(d ast.Decl) {
    97  	p := e.p
    98  	f, ok := d.(*ast.FuncDecl)
    99  	if !ok {
   100  		return
   101  	}
   102  
   103  	if !strings.HasPrefix(f.Name.Name, "Test") {
   104  		return
   105  	}
   106  	e.dir = strings.ToLower(f.Name.Name[len("Test"):])
   107  	e.count = 0
   108  	e.tags = nil
   109  	if e.dir == "x" { // TestX
   110  		return
   111  	}
   112  
   113  	if len(f.Type.Params.List) != 1 {
   114  		return
   115  	}
   116  
   117  	if p.TypesInfo.TypeOf(f.Type.Params.List[0].Type).String() != "*testing.T" {
   118  		return
   119  	}
   120  	e.extractFromTestFunc(f)
   121  }
   122  
   123  func (e *extractor) isConstant(x ast.Expr) bool {
   124  	return constant.Val(e.p.TypesInfo.Types[x].Value) != nil
   125  }
   126  
   127  func (e *extractor) stringConst(x ast.Expr) string {
   128  	v := e.p.TypesInfo.Types[x].Value
   129  	if v.Kind() != constant.String {
   130  		return v.String()
   131  	}
   132  	return constant.StringVal(v)
   133  }
   134  
   135  func (e *extractor) boolConst(x ast.Expr) bool {
   136  	v := e.p.TypesInfo.Types[x].Value
   137  	return constant.BoolVal(v)
   138  }
   139  
   140  func (e *extractor) exprString(x ast.Expr) string {
   141  	w := &bytes.Buffer{}
   142  	_ = format.Node(w, e.p.Fset, x)
   143  	return w.String()
   144  }
   145  
   146  func (e *extractor) extractFromTestFunc(f *ast.FuncDecl) {
   147  	defer func() {
   148  		if err := recover(); err != nil {
   149  			e.warnf("PANIC: %v", err)
   150  			panic(err)
   151  		}
   152  	}()
   153  	// Extract meta data.
   154  	for _, stmt := range f.Body.List {
   155  		es, ok := stmt.(*ast.ExprStmt)
   156  		if !ok {
   157  			continue
   158  		}
   159  		if call, ok := es.X.(*ast.CallExpr); ok {
   160  			if e.exprString(call.Fun) != "rewriteHelper" {
   161  				continue
   162  			}
   163  			e.tags = append(e.tags, e.exprString(call.Args[2]))
   164  		}
   165  	}
   166  
   167  	// Extract test data.
   168  	for _, stmt := range f.Body.List {
   169  		ast.Inspect(stmt, func(n ast.Node) bool {
   170  			switch x := n.(type) {
   171  			case *ast.CompositeLit:
   172  				t := e.p.TypesInfo.TypeOf(x.Type)
   173  
   174  				switch t.String() {
   175  				case "[]cuelang.org/go/cue.testCase",
   176  					"[]cuelang.org/go/cue.exportTest":
   177  					// TODO: "[]cuelang.org/go/cue.subsumeTest",
   178  				default:
   179  					return false
   180  				}
   181  
   182  				for _, elt := range x.Elts {
   183  					if kv, ok := elt.(*ast.KeyValueExpr); ok {
   184  						elt = kv.Value
   185  					}
   186  					e.extractTest(elt.(*ast.CompositeLit))
   187  					e.count++
   188  				}
   189  
   190  				return false
   191  			}
   192  			return true
   193  		})
   194  	}
   195  }
   196  
   197  func (e *extractor) extractTest(x *ast.CompositeLit) {
   198  	e.name = ""
   199  	e.header = &bytes.Buffer{}
   200  	e.a = &txtar.Archive{}
   201  
   202  	e.header.WriteString(`# DO NOT EDIT; generated by go run testdata/gen.go
   203  #
   204  `)
   205  
   206  	for _, elmt := range x.Elts {
   207  		f, ok := elmt.(*ast.KeyValueExpr)
   208  		if !ok {
   209  			e.fatalf("Invalid slice element: %T", elmt)
   210  			continue
   211  		}
   212  
   213  		switch key := e.exprString(f.Key); key {
   214  		case "name", "desc":
   215  			e.name = e.stringConst(f.Value)
   216  			fmt.Fprintf(e.header, "#name: %v\n", e.stringConst(f.Value))
   217  
   218  		case "in":
   219  			src := []byte(e.stringConst(f.Value))
   220  			src, err := cueformat.Source(src)
   221  
   222  			if f, err := parser.ParseFile("in.cue", src, parser.ParseComments); err == nil {
   223  				f = fix.File(f)
   224  				b, err := cueformat.Node(f)
   225  				if err == nil {
   226  					src = b
   227  				}
   228  			}
   229  
   230  			if err != nil {
   231  				fmt.Fprintln(e.header, "#skip")
   232  				e.warnf("Skipped: %v", err)
   233  				continue
   234  			}
   235  			e.a.Files = append(e.a.Files, txtar.File{
   236  				Name: "in.cue",
   237  				Data: src,
   238  			})
   239  
   240  			e.populate(src)
   241  
   242  		case "out":
   243  			if !e.isConstant(f.Value) {
   244  				e.warnf("Could not determine value for 'out' field")
   245  				continue
   246  			}
   247  			e.a.Files = append(e.a.Files, txtar.File{
   248  				Name: "out/legacy-debug",
   249  				Data: []byte(e.stringConst(f.Value)),
   250  			})
   251  		default:
   252  			fmt.Fprintf(e.header, "%s: %v\n", key, e.exprString(f.Value))
   253  		}
   254  	}
   255  
   256  	for _, t := range e.tags {
   257  		fmt.Fprintf(e.header, "#%s\n", t)
   258  	}
   259  
   260  	e.a.Comment = e.header.Bytes()
   261  
   262  	_ = os.Mkdir(e.dir, 0755)
   263  
   264  	name := fmt.Sprintf("%03d", e.count)
   265  	if e.name != "" {
   266  		name += "_" + e.name
   267  	}
   268  	name = strings.ReplaceAll(name, " ", "_")
   269  	name = strings.ReplaceAll(name, ":", "_")
   270  	filename := filepath.Join(e.dir, name+".txtar")
   271  	err := os.WriteFile(filename, txtar.Format(e.a), 0644)
   272  	if err != nil {
   273  		e.fatalf("Could not write file: %v", err)
   274  	}
   275  }
   276  
   277  // populate sets the golden tests based on the old compiler, evaluator,
   278  // and exporter.
   279  func (e *extractor) populate(src []byte) {
   280  	r := cue.Runtime{}
   281  	inst, err := r.Compile("in.cue", src)
   282  	if err != nil {
   283  		e.fatalf("Failed to parse: %v", err)
   284  	}
   285  
   286  	v := inst.Value()
   287  
   288  	e.addFile(e.a, "out/def", v.Syntax(
   289  		cue.Docs(true),
   290  		cue.Attributes(true),
   291  		cue.Optional(true),
   292  		cue.Definitions(true)))
   293  
   294  	if v.Validate(cue.Concrete(true)) == nil {
   295  		e.addFile(e.a, "out/export", v.Syntax(
   296  			cue.Concrete(true),
   297  			cue.Final(),
   298  			cue.Docs(true),
   299  			cue.Attributes(true)))
   300  
   301  		s, err := yaml.Marshal(v)
   302  		if err != nil {
   303  			fmt.Fprintln(e.header, "#bug: true")
   304  			e.warnf("Could not encode as YAML: %v", err)
   305  		}
   306  		e.a.Files = append(e.a.Files,
   307  			txtar.File{Name: "out/yaml", Data: []byte(s)})
   308  
   309  		b, err := internaljson.Marshal(v)
   310  		if err != nil {
   311  			fmt.Fprintln(e.header, "#bug: true")
   312  			e.warnf("Could not encode as JSON: %v", err)
   313  		}
   314  		e.a.Files = append(e.a.Files,
   315  			txtar.File{Name: "out/json", Data: b})
   316  	}
   317  }
   318  
   319  func (e *extractor) addFile(a *txtar.Archive, name string, n cueast.Node) {
   320  	b, err := cueformat.Node(internal.ToFile(n))
   321  	if err != nil {
   322  		e.fatalf("Failed to format %s: %v\n", name, err)
   323  	}
   324  	a.Files = append(a.Files, txtar.File{Name: name, Data: b})
   325  }
   326  

View as plain text