github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/internal/core/export/export_test.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 export_test 16 17 import ( 18 "testing" 19 20 "github.com/joomcode/cue/cue" 21 "github.com/joomcode/cue/cue/ast" 22 "github.com/joomcode/cue/cue/cuecontext" 23 "github.com/joomcode/cue/cue/errors" 24 "github.com/joomcode/cue/cue/format" 25 "github.com/joomcode/cue/cue/parser" 26 "github.com/joomcode/cue/encoding/gocode/gocodec" 27 "github.com/joomcode/cue/internal/astinternal" 28 "github.com/joomcode/cue/internal/core/adt" 29 "github.com/joomcode/cue/internal/core/compile" 30 "github.com/joomcode/cue/internal/core/convert" 31 "github.com/joomcode/cue/internal/core/eval" 32 "github.com/joomcode/cue/internal/core/export" 33 "github.com/joomcode/cue/internal/core/runtime" 34 "github.com/joomcode/cue/internal/cuetest" 35 "github.com/joomcode/cue/internal/cuetxtar" 36 "github.com/joomcode/cue/internal/value" 37 "github.com/rogpeppe/go-internal/txtar" 38 ) 39 40 func TestDefinition(t *testing.T) { 41 test := cuetxtar.TxTarTest{ 42 Root: "./testdata", 43 Name: "definition", 44 Update: cuetest.UpdateGoldenFiles, 45 } 46 47 r := runtime.New() 48 49 test.Run(t, func(t *cuetxtar.Test) { 50 a := t.ValidInstances() 51 52 v, errs := compile.Files(nil, r, "", a[0].Files...) 53 if errs != nil { 54 t.Fatal(errs) 55 } 56 v.Finalize(eval.NewContext(r, v)) 57 58 // TODO: do we need to evaluate v? In principle not necessary. 59 // v.Finalize(eval.NewContext(r, v)) 60 61 file, errs := export.Def(r, "", v) 62 errors.Print(t, errs, nil) 63 _, _ = t.Write(formatNode(t.T, file)) 64 }) 65 } 66 67 func formatNode(t *testing.T, n ast.Node) []byte { 68 t.Helper() 69 70 b, err := format.Node(n) 71 if err != nil { 72 t.Fatal(err) 73 } 74 return b 75 } 76 77 // TestGenerated tests conversions of generated Go structs, which may be 78 // different from parsed or evaluated CUE, such as having Vertex values. 79 func TestGenerated(t *testing.T) { 80 ctx := cuecontext.New() 81 82 testCases := []struct { 83 in func(ctx *adt.OpContext) (adt.Expr, error) 84 out string 85 p *export.Profile 86 }{{ 87 in: func(ctx *adt.OpContext) (adt.Expr, error) { 88 in := &C{ 89 Terminals: []*A{{Name: "Name", Description: "Desc"}}, 90 } 91 return convert.GoValueToValue(ctx, in, false), nil 92 }, 93 out: `Terminals: [{Name: "Name", Description: "Desc"}]`, 94 }, { 95 in: func(ctx *adt.OpContext) (adt.Expr, error) { 96 in := &C{ 97 Terminals: []*A{{Name: "Name", Description: "Desc"}}, 98 } 99 return convert.GoTypeToExpr(ctx, in) 100 }, 101 out: `*null|{Terminals?: *null|[...*null|{Name: string, Description: string}]}`, 102 }, { 103 in: func(ctx *adt.OpContext) (adt.Expr, error) { 104 in := []*A{{Name: "Name", Description: "Desc"}} 105 return convert.GoValueToValue(ctx, in, false), nil 106 }, 107 out: `[{Name: "Name", Description: "Desc"}]`, 108 }, { 109 in: func(ctx *adt.OpContext) (adt.Expr, error) { 110 in := []*A{{Name: "Name", Description: "Desc"}} 111 return convert.GoTypeToExpr(ctx, in) 112 }, 113 out: `*null|[...*null|{Name: string, Description: string}]`, 114 }, { 115 in: func(ctx *adt.OpContext) (adt.Expr, error) { 116 expr, err := parser.ParseExpr("test", `{ 117 x: Guide.#Terminal 118 Guide: {} 119 }`) 120 if err != nil { 121 return nil, err 122 } 123 c, err := compile.Expr(nil, ctx, "_", expr) 124 if err != nil { 125 return nil, err 126 } 127 root := &adt.Vertex{} 128 root.AddConjunct(c) 129 root.Finalize(ctx) 130 131 // Simulate Value.Unify of Lookup("x") and Lookup("Guide"). 132 n := &adt.Vertex{} 133 n.AddConjunct(adt.MakeRootConjunct(nil, root.Arcs[0])) 134 n.AddConjunct(adt.MakeRootConjunct(nil, root.Arcs[1])) 135 n.Finalize(ctx) 136 137 return n, nil 138 }, 139 out: `<[l2// x: undefined field: #Terminal] _|_>`, 140 p: export.Final, 141 }, { 142 in: func(r *adt.OpContext) (adt.Expr, error) { 143 v := ctx.CompileString(` 144 #Provider: { 145 ID: string 146 notConcrete: bool 147 a: int 148 b: a + 1 149 }`) 150 151 spec := v.LookupPath(cue.ParsePath("#Provider")) 152 spec2 := spec.FillPath(cue.ParsePath("ID"), "12345") 153 root := v.FillPath(cue.ParsePath("providers.foo"), spec2) 154 _, n := value.ToInternal(root) 155 156 return n, nil 157 }, 158 out: `#Provider: {ID: string, notConcrete: bool, a: int, b: a+1}, providers: {foo: {ID: "12345", notConcrete: bool, a: int, b: a+1}}`, 159 p: export.All, 160 }, { 161 // Issue #882 162 in: func(r *adt.OpContext) (adt.Expr, error) { 163 valA := ctx.CompileString(` 164 #One: { version: string } 165 `) 166 167 valB := ctx.CompileString(` 168 #One: _ 169 ones: {[string]: #One} 170 `) 171 v := valB.Unify(valA) 172 _, n := value.ToInternal(v) 173 return n, nil 174 }, 175 out: `#One: {version: string}, ones: {[string]: #One}`, 176 p: export.All, 177 }, { 178 // Indicate closedness in an element that is closed and misses parent 179 // context. 180 // Issue #882 181 in: func(r *adt.OpContext) (adt.Expr, error) { 182 v := ctx.CompileString(` 183 #A: b: c: string 184 `) 185 v = v.LookupPath(cue.ParsePath("#A.b")) 186 187 _, n := value.ToInternal(v) 188 return n, nil 189 }, 190 out: `_#def, _#def: {c: string}`, 191 p: export.All, 192 }, { 193 // Don't wrap in def if the if the value is an embedded scalar. 194 // Issue #977 195 in: func(r *adt.OpContext) (adt.Expr, error) { 196 v := ctx.CompileString(` 197 #A: { "foo", #enum: 2 } 198 `) 199 v = v.LookupPath(cue.ParsePath("#A")) 200 201 _, n := value.ToInternal(v) 202 return n, nil 203 }, 204 out: `"foo", #enum: 2`, 205 p: export.All, 206 }, { 207 // Issue #1131 208 in: func(r *adt.OpContext) (adt.Expr, error) { 209 m := make(map[string]interface{}) 210 v := ctx.Encode(m) 211 _, x := value.ToInternal(v) 212 return x, nil 213 }, 214 out: ``, // empty file 215 }, { 216 in: func(r *adt.OpContext) (adt.Expr, error) { 217 v := &adt.Vertex{} 218 v.SetValue(r, adt.Finalized, &adt.StructMarker{}) 219 return v, nil 220 }, 221 out: ``, // empty file 222 }} 223 for _, tc := range testCases { 224 t.Run("", func(t *testing.T) { 225 ctx := adt.NewContext((*runtime.Runtime)(ctx), &adt.Vertex{}) 226 227 v, err := tc.in(ctx) 228 if err != nil { 229 t.Fatal("failed test case: ", err) 230 } 231 232 p := tc.p 233 if p == nil { 234 p = export.Simplified 235 } 236 237 var n ast.Node 238 switch x := v.(type) { 239 case *adt.Vertex: 240 n, err = p.Def(ctx, "", x) 241 default: 242 n, err = p.Expr(ctx, "", v) 243 } 244 if err != nil { 245 t.Fatal("failed export: ", err) 246 } 247 got := astinternal.DebugStr(n) 248 if got != tc.out { 249 t.Errorf("got: %s\nwant: %s", got, tc.out) 250 } 251 }) 252 } 253 } 254 255 type A struct { 256 Name string 257 Description string 258 } 259 260 type B struct { 261 Image string 262 } 263 264 type C struct { 265 Terminals []*A 266 } 267 268 // For debugging purposes. Do not delete. 269 func TestX(t *testing.T) { 270 t.Skip() 271 272 in := ` 273 -- in.cue -- 274 package test 275 276 // // Foo 277 // a: [X=string]: [Y=string]: { 278 // name: X+Y 279 // } 280 281 // [Y=string]: [X=string]: name: {Y+X} 282 // { 283 // name: X.other + Y 284 // other: string 285 // } 286 287 // c: [X=string]: X 288 289 // #pkg1: Object 290 291 // "Hello \(#pkg1)!" 292 293 294 // Object: "World" 295 296 // // A Foo fooses stuff. 297 // foos are instances of Foo. 298 // foos: [string]: {} 299 300 // // // My first little foo. 301 // foos: MyFoo: {} 302 ` 303 304 archive := txtar.Parse([]byte(in)) 305 a := cuetxtar.Load(archive, "/tmp/test") 306 if err := a[0].Err; err != nil { 307 t.Fatal(err) 308 } 309 310 // x := a[0].Files[0] 311 // astutil.Sanitize(x) 312 313 r := runtime.New() 314 v, errs := compile.Files(nil, r, "", a[0].Files...) 315 if errs != nil { 316 t.Fatal(errs) 317 } 318 v.Finalize(eval.NewContext(r, v)) 319 320 file, errs := export.Def(r, "main", v) 321 if errs != nil { 322 t.Fatal(errs) 323 } 324 325 t.Error(string(formatNode(t, file))) 326 } 327 328 func TestFromGo(t *testing.T) { 329 type Struct struct { 330 A string 331 B string 332 } 333 334 m := make(map[string]Struct) 335 m["hello"] = Struct{ 336 A: "a", 337 B: "b", 338 } 339 var r cue.Runtime 340 codec := gocodec.New(&r, nil) 341 v, err := codec.Decode(m) 342 if err != nil { 343 panic(err) 344 } 345 346 syn, _ := format.Node(v.Syntax()) 347 if got := string(syn); got != `{ 348 hello: { 349 A: "a" 350 B: "b" 351 } 352 }` { 353 t.Errorf("incorrect ordering: %s\n", got) 354 } 355 } 356 357 func TestFromAPI(t *testing.T) { 358 testCases := []struct { 359 expr ast.Expr 360 out string 361 }{{ 362 expr: ast.NewCall(ast.NewIdent("close"), ast.NewStruct()), 363 out: `close({})`, 364 }, { 365 expr: ast.NewCall(ast.NewIdent("close"), ast.NewStruct( 366 "a", ast.NewString("foo"), 367 )), 368 out: `close({a: "foo"})`, 369 }, { 370 expr: ast.NewCall(ast.NewIdent("close"), ast.NewStruct( 371 ast.Embed(ast.NewStruct("a", ast.NewString("foo"))), 372 )), 373 out: `close({a: "foo"})`, 374 }} 375 // Issue #1204 376 for _, tc := range testCases { 377 t.Run("", func(t *testing.T) { 378 ctx := cuecontext.New() 379 380 v := ctx.BuildExpr(tc.expr) 381 382 r, x := value.ToInternal(v) 383 file, err := export.Def(r, "foo", x) 384 385 if err != nil { 386 t.Fatal(err) 387 } 388 389 got := astinternal.DebugStr(file) 390 if got != tc.out { 391 t.Errorf("got: %s\nwant: %s", got, tc.out) 392 } 393 394 }) 395 } 396 }