cuelang.org/go@v0.10.1/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 "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 }