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