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