blob: 7eeae0b730e707adf9060721b4b8d9dd225b4ea9 [file] [log] [blame]
Marcel van Lohuizen17157ea2018-12-11 10:41:10 +01001// Copyright 2018 The 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 cue
16
17import (
18 "bytes"
19 "fmt"
20 "io/ioutil"
21 "math"
22 "math/big"
23 "reflect"
24 "strings"
25 "testing"
26
27 "github.com/google/go-cmp/cmp"
28)
29
30func getInstance(t *testing.T, body ...string) *Instance {
31 t.Helper()
32
33 insts := Build(makeInstances([]*bimport{{files: body}}))
34 if insts[0].Err != nil {
35 t.Fatalf("unexpected parse error: %v", insts[0].Err)
36 }
37 return insts[0]
38}
39
40func TestValueType(t *testing.T) {
41 testCases := []struct {
42 value string
43 kind Kind
44 incompleteKind Kind
45 json string
46 valid bool
47 // pos token.Pos
48 }{{ // Not a concrete value.
49 value: `_`,
50 kind: BottomKind,
51 incompleteKind: nextKind - 1,
52 }, {
53 value: `_|_`,
54 kind: BottomKind,
55 incompleteKind: BottomKind,
56 }, {
57 value: `1&2`,
58 kind: BottomKind,
59 incompleteKind: BottomKind,
60 }, { // TODO: should be error{
61 value: `b`,
62 kind: BottomKind,
63 incompleteKind: BottomKind,
64 }, {
65 value: `(b[a])`,
66 kind: BottomKind,
67 incompleteKind: BottomKind,
68 }, { // TODO: should be error{
69 value: `(b)
70 b: bool`,
71 kind: BottomKind,
72 incompleteKind: BoolKind,
73 }, {
74 value: `([][b])`,
75 kind: BottomKind,
76 incompleteKind: BottomKind,
77 }, {
78 value: `null`,
79 kind: NullKind,
80 incompleteKind: NullKind,
81 }, {
82 value: `true`,
83 kind: BoolKind,
84 incompleteKind: BoolKind,
85 }, {
86 value: `false`,
87 kind: BoolKind,
88 incompleteKind: BoolKind,
89 }, {
90 value: `bool`,
91 kind: BottomKind,
92 incompleteKind: BoolKind,
93 }, {
94 value: `2`,
95 kind: NumberKind,
96 incompleteKind: NumberKind,
97 }, {
98 value: `2.0`,
99 kind: NumberKind,
100 incompleteKind: NumberKind,
101 }, {
102 value: `2.0Mi`,
103 kind: NumberKind,
104 incompleteKind: NumberKind,
105 }, {
106 value: `14_000`,
107 kind: NumberKind,
108 incompleteKind: NumberKind,
109 }, {
110 value: `0..5`,
111 kind: BottomKind,
112 incompleteKind: NumberKind,
113 }, {
114 value: `float`,
115 kind: BottomKind,
116 incompleteKind: NumberKind,
117 }, {
118 value: `"str"`,
119 kind: StringKind,
120 incompleteKind: StringKind,
121 }, {
122 value: "'''\n'''",
123 kind: BytesKind,
124 incompleteKind: BytesKind,
125 }, {
126 value: "string",
127 kind: BottomKind,
128 incompleteKind: StringKind,
129 }, {
130 value: `{}`,
131 kind: StructKind,
132 incompleteKind: StructKind,
133 }, {
134 value: `[]`,
135 kind: ListKind,
136 incompleteKind: ListKind,
137 }}
138 for _, tc := range testCases {
139 t.Run(tc.value, func(t *testing.T) {
140 inst := getInstance(t, tc.value)
141 v := inst.Value()
142 if got := v.Kind(); got != tc.kind {
143 t.Errorf("Kind: got %x; want %x", got, tc.kind)
144 }
145 want := tc.incompleteKind | BottomKind
146 if got := v.IncompleteKind(); got != want {
147 t.Errorf("IncompleteKind: got %x; want %x", got, want)
148 }
149 incomplete := tc.incompleteKind != tc.kind
150 if got := v.IsIncomplete(); got != incomplete {
151 t.Errorf("IsIncomplete: got %v; want %v", got, incomplete)
152 }
153 invalid := tc.kind == BottomKind
154 if got := v.IsValid(); got != !invalid {
155 t.Errorf("IsValid: got %v; want %v", got, !invalid)
156 }
157 // if got, want := v.Pos(), tc.pos+1; got != want {
158 // t.Errorf("pos: got %v; want %v", got, want)
159 // }
160 })
161 }
162}
163
164func TestInt(t *testing.T) {
165 testCases := []struct {
166 value string
167 int int64
168 uint uint64
169 base int
170 err string
171 errU string
172 notInt bool
173 }{{
174 value: "1",
175 int: 1,
176 uint: 1,
177 }, {
178 value: "-1",
179 int: -1,
180 uint: 0,
181 errU: ErrAbove.Error(),
182 }, {
183 value: "-111222333444555666777888999000",
184 int: math.MinInt64,
185 uint: 0,
186 err: ErrAbove.Error(),
187 errU: ErrAbove.Error(),
188 }, {
189 value: "111222333444555666777888999000",
190 int: math.MaxInt64,
191 uint: math.MaxUint64,
192 err: ErrBelow.Error(),
193 errU: ErrBelow.Error(),
194 }, {
195 value: "1.0",
196 err: "not of right kind (float vs int)",
197 errU: "not of right kind (float vs int)",
198 notInt: true,
199 }, {
200 value: "int",
201 err: "non-concrete value (int)*",
202 errU: "non-concrete value (int)*",
203 notInt: true,
204 }, {
205 value: "_|_",
206 err: "from source",
207 errU: "from source",
208 notInt: true,
209 }}
210 for _, tc := range testCases {
211 t.Run(tc.value, func(t *testing.T) {
212 n := getInstance(t, tc.value).Value()
213 base := 10
214 if tc.base > 0 {
215 base = tc.base
216 }
217 b, err := n.AppendInt(nil, base)
218 if checkFailed(t, err, tc.err, "append") {
219 want := tc.value
220 if got := string(b); got != want {
221 t.Errorf("append: got %v; want %v", got, want)
222 }
223 }
224
225 if got := n.IsInt(); got != !tc.notInt {
226 t.Errorf("isInt: got %v; want %v", got, !tc.notInt)
227 }
228
229 isUint := !tc.notInt && tc.int >= 0
230 if got := n.IsUint(); got != isUint {
231 t.Errorf("isUint: got %v; want %v", got, isUint)
232 }
233
234 vi, err := n.Int64()
235 checkErr(t, err, tc.err, "Int64")
236 if vi != tc.int {
237 t.Errorf("Int64: got %v; want %v", vi, tc.int)
238 }
239
240 vu, err := n.Uint64()
241 checkErr(t, err, tc.errU, "Uint64")
242 if vu != uint64(tc.uint) {
243 t.Errorf("Uint64: got %v; want %v", vu, tc.uint)
244 }
245 })
246 }
247}
248
249func TestFloat(t *testing.T) {
250 testCases := []struct {
251 value string
252 float string
253 float64 float64
254 mant string
255 exp int
256 fmt byte
257 prec int
258 err string
259 }{{
260 value: "1",
261 float: "1",
262 mant: "1",
263 exp: 0,
264 float64: 1,
265 fmt: 'g',
266 }, {
267 value: "-1",
268 float: "-1",
269 mant: "-1",
270 exp: 0,
271 float64: -1,
272 fmt: 'g',
273 }, {
274 value: "1.0",
275 float: "1.0",
276 mant: "10",
277 exp: -1,
278 float64: 1.0,
279 fmt: 'g',
280 }, {
281 value: "2.6",
282 float: "2.6",
283 mant: "26",
284 exp: -1,
285 float64: 2.6,
286 fmt: 'g',
287 }, {
288 value: "20.600",
289 float: "20.60",
290 mant: "20600",
291 exp: -3,
292 float64: 20.60,
293 prec: 2,
294 fmt: 'f',
295 }, {
296 value: "1/0",
297 float: "∞",
298 float64: math.Inf(1),
299 prec: 2,
300 fmt: 'f',
301 err: ErrAbove.Error(),
302 }, {
303 value: "-1/0",
304 float: "-∞",
305 float64: math.Inf(-1),
306 prec: 2,
307 fmt: 'f',
308 err: ErrBelow.Error(),
309 }, {
310 value: "1.797693134862315708145274237317043567982e+308",
311 float: "1.8e+308",
312 mant: "1797693134862315708145274237317043567982",
313 exp: 269,
314 float64: math.Inf(1),
315 prec: 2,
316 fmt: 'g',
317 err: ErrAbove.Error(),
318 }, {
319 value: "-1.797693134862315708145274237317043567982e+308",
320 float: "-1.8e+308",
321 mant: "-1797693134862315708145274237317043567982",
322 exp: 269,
323 float64: math.Inf(-1),
324 prec: 2,
325 fmt: 'g',
326 err: ErrBelow.Error(),
327 }, {
328 value: "4.940656458412465441765687928682213723650e-324",
329 float: "4.941e-324",
330 mant: "4940656458412465441765687928682213723650",
331 exp: -363,
332 float64: 0,
333 prec: 4,
334 fmt: 'g',
335 err: ErrBelow.Error(),
336 }, {
337 value: "-4.940656458412465441765687928682213723650e-324",
338 float: "-4.940656458412465441765687928682213723650e-324",
339 mant: "-4940656458412465441765687928682213723650",
340 exp: -363,
341 float64: 0,
342 prec: -1,
343 fmt: 'g',
344 err: ErrAbove.Error(),
345 }}
346 for _, tc := range testCases {
347 t.Run(tc.value, func(t *testing.T) {
348 n := getInstance(t, tc.value).Value()
349 if n.Kind() != NumberKind {
350 t.Fatal("Not a number")
351 }
352
353 var mant big.Int
354 exp, err := n.MantExp(&mant)
355 mstr := ""
356 if err == nil {
357 mstr = mant.String()
358 }
359 if exp != tc.exp || mstr != tc.mant {
360 t.Errorf("mantExp: got %s %d; want %s %d", mstr, exp, tc.mant, tc.exp)
361 }
362
363 b, err := n.AppendFloat(nil, tc.fmt, tc.prec)
364 want := tc.float
365 if got := string(b); got != want {
366 t.Errorf("append: got %v; want %v", got, want)
367 }
368
369 f, err := n.Float64()
370 checkErr(t, err, tc.err, "Float64")
371 if f != tc.float64 {
372 t.Errorf("Float64: got %v; want %v", f, tc.float64)
373 }
374 })
375 }
376}
377
378func TestString(t *testing.T) {
379 testCases := []struct {
380 value string
381 str string
382 err string
383 }{{
384 value: `""`,
385 str: ``,
386 }, {
387 value: `"Hello world!"`,
388 str: `Hello world!`,
389 }, {
390 value: `"Hello \(world)!"
391 world: "world"`,
392 str: `Hello world!`,
393 }, {
394 value: `string`,
395 err: "non-concrete value (string)*",
396 }}
397 for _, tc := range testCases {
398 t.Run(tc.value, func(t *testing.T) {
399 str, err := getInstance(t, tc.value).Value().String()
400 checkFatal(t, err, tc.err, "init")
401 if str != tc.str {
402 t.Errorf("String: got %q; want %q", str, tc.str)
403 }
404
405 b, err := getInstance(t, tc.value).Value().Bytes()
406 checkFatal(t, err, tc.err, "init")
407 if got := string(b); got != tc.str {
408 t.Errorf("Bytes: got %q; want %q", got, tc.str)
409 }
410
411 r, err := getInstance(t, tc.value).Value().Reader()
412 checkFatal(t, err, tc.err, "init")
413 b, _ = ioutil.ReadAll(r)
414 if got := string(b); got != tc.str {
415 t.Errorf("Reader: got %q; want %q", got, tc.str)
416 }
417 })
418 }
419}
420
421func TestError(t *testing.T) {
422 testCases := []struct {
423 value string
424 err string
425 }{{
426 value: `_|_`,
427 err: "from source",
428 }, {
429 value: `"Hello world!"`,
430 }, {
431 value: `string`,
432 err: "non-concrete value (string)*",
433 }}
434 for _, tc := range testCases {
435 t.Run(tc.value, func(t *testing.T) {
436 err := getInstance(t, tc.value).Value().Err()
437 checkErr(t, err, tc.err, "init")
438 })
439 }
440}
441
442func TestNull(t *testing.T) {
443 testCases := []struct {
444 value string
445 err string
446 }{{
447 value: `_|_`,
448 err: "from source",
449 }, {
450 value: `"str"`,
451 err: "not of right kind (string vs null)",
452 }, {
453 value: `null`,
454 }, {
455 value: `_`,
456 err: "non-concrete value (_)*",
457 }}
458 for _, tc := range testCases {
459 t.Run(tc.value, func(t *testing.T) {
460 err := getInstance(t, tc.value).Value().Null()
461 checkErr(t, err, tc.err, "init")
462 })
463 }
464}
465
466func TestBool(t *testing.T) {
467 testCases := []struct {
468 value string
469 bool bool
470 err string
471 }{{
472 value: `_|_`,
473 err: "from source",
474 }, {
475 value: `"str"`,
476 err: "not of right kind (string vs bool)",
477 }, {
478 value: `true`,
479 bool: true,
480 }, {
481 value: `false`,
482 }, {
483 value: `bool`,
484 err: "non-concrete value (bool)*",
485 }}
486 for _, tc := range testCases {
487 t.Run(tc.value, func(t *testing.T) {
488 got, err := getInstance(t, tc.value).Value().Bool()
489 if checkErr(t, err, tc.err, "init") {
490 if got != tc.bool {
491 t.Errorf("got %v; want %v", got, tc.bool)
492 }
493 }
494 })
495 }
496}
497
498func TestList(t *testing.T) {
499 testCases := []struct {
500 value string
501 res string
502 err string
503 }{{
504 value: `_|_`,
505 err: "from source",
506 }, {
507 value: `"str"`,
508 err: "not of right kind (string vs list)",
509 }, {
510 value: `[]`,
511 res: "[]",
512 }, {
513 value: `[1,2,3]`,
514 res: "[1,2,3,]",
515 }, {
516 value: `(0..5)*[1,2,3, ...int]`,
517 res: "[1,2,3,]",
518 }, {
519 value: `[x for x in y if x > 1]
520 y: [1,2,3]`,
521 res: "[2,3,]",
522 }, {
523 value: `[int]`,
524 err: "cannot convert incomplete value",
525 }}
526 for _, tc := range testCases {
527 t.Run(tc.value, func(t *testing.T) {
528 l, err := getInstance(t, tc.value).Value().List()
529 checkFatal(t, err, tc.err, "init")
530
531 buf := []byte{'['}
532 for l.Next() {
533 b, err := l.Value().MarshalJSON()
534 checkFatal(t, err, tc.err, "list.Value")
535 buf = append(buf, b...)
536 buf = append(buf, ',')
537 }
538 buf = append(buf, ']')
539 if got := string(buf); got != tc.res {
540 t.Errorf("got %v; want %v", got, tc.res)
541 }
542 })
543 }
544}
545
546func TestFields(t *testing.T) {
547 testCases := []struct {
548 value string
549 res string
550 err string
551 }{{
552 value: `_|_`,
553 err: "from source",
554 }, {
555 value: `"str"`,
556 err: "not of right kind (string vs struct)",
557 }, {
558 value: `{}`,
559 res: "{}",
560 }, {
561 value: `{a:1,b:2,c:3}`,
562 res: "{a:1,b:2,c:3,}",
563 }, {
564 value: `{a:1,"_b":2,c:3,_d:4}`,
565 res: "{a:1,_b:2,c:3,}",
566 }, {
567 value: `{_a:"a"}`,
568 res: "{}",
569 }, {
Marcel van Lohuizen76b92b52018-12-16 10:47:03 +0100570 value: `{"\(k)": v for k, v in y if v > 1}
Marcel van Lohuizen17157ea2018-12-11 10:41:10 +0100571 y: {a:1,b:2,c:3}`,
572 res: "{b:2,c:3,}",
573 }, {
574 value: `{a:1,b:2,c:int}`,
575 err: "cannot convert incomplete value",
576 }}
577 for _, tc := range testCases {
578 t.Run(tc.value, func(t *testing.T) {
579 obj := getInstance(t, tc.value).Value()
580
581 iter, err := obj.Fields()
582 checkFatal(t, err, tc.err, "init")
583
584 buf := []byte{'{'}
585 for iter.Next() {
586 buf = append(buf, iter.Label()...)
587 buf = append(buf, ':')
588 b, err := iter.Value().MarshalJSON()
589 checkFatal(t, err, tc.err, "Obj.At")
590 buf = append(buf, b...)
591 buf = append(buf, ',')
592 }
593 buf = append(buf, '}')
594 if got := string(buf); got != tc.res {
595 t.Errorf("got %v; want %v", got, tc.res)
596 }
597
598 iter, _ = obj.Fields()
599 for iter.Next() {
600 want, err := iter.Value().MarshalJSON()
601 checkFatal(t, err, tc.err, "Obj.At2")
602
603 got, err := obj.Lookup(iter.Label()).MarshalJSON()
604 checkFatal(t, err, tc.err, "Obj.At2")
605
606 if !bytes.Equal(got, want) {
607 t.Errorf("Lookup: got %q; want %q", got, want)
608 }
609 }
610 v := obj.Lookup("non-existing")
611 checkErr(t, v.Err(), "not found", "non-existing")
612 })
613 }
614}
615
616func TestAllFields(t *testing.T) {
617 testCases := []struct {
618 value string
619 res string
620 err string
621 }{{
622 value: `{a:1,"_b":2,c:3,_d:4}`,
623 res: "{a:1,_b:2,c:3,_d:4,}",
624 }, {
625 value: `{_a:"a"}`,
626 res: `{_a:"a",}`,
627 }}
628 for _, tc := range testCases {
629 t.Run(tc.value, func(t *testing.T) {
630 obj := getInstance(t, tc.value).Value()
631
632 iter, err := obj.AllFields()
633 checkFatal(t, err, tc.err, "init")
634
635 buf := []byte{'{'}
636 for iter.Next() {
637 buf = append(buf, iter.Label()...)
638 buf = append(buf, ':')
639 b, err := iter.Value().MarshalJSON()
640 checkFatal(t, err, tc.err, "Obj.At")
641 buf = append(buf, b...)
642 buf = append(buf, ',')
643 }
644 buf = append(buf, '}')
645 if got := string(buf); got != tc.res {
646 t.Errorf("got %v; want %v", got, tc.res)
647 }
648 })
649 }
650}
651
652func TestTemplate(t *testing.T) {
653 testCases := []struct {
654 value string
655 path []string
656 want string
657 }{{
658 value: `
659 a <Name>: Name
660 `,
661 path: []string{"a", ""},
662 want: `"label"`,
663 }, {
664 value: `
665 <Name>: { a: Name }
666 `,
667 path: []string{"", "a"},
668 want: `"label"`,
669 }, {
670 value: `
671 <Name>: { a: Name }
672 `,
673 path: []string{""},
674 want: `{"a":"label"}`,
675 }, {
676 value: `
677 a <Foo> <Bar>: { b: Foo+Bar }
678 `,
679 path: []string{"a", "", ""},
680 want: `{"b":"labellabel"}`,
681 }, {
682 value: `
683 a <Foo> b <Bar>: { c: Foo+Bar }
684 a foo b <Bar>: { d: Bar }
685 `,
686 path: []string{"a", "foo", "b", ""},
687 want: `{"c":"foolabel","d":"label"}`,
688 }}
689 for _, tc := range testCases {
690 t.Run("", func(t *testing.T) {
691 v := getInstance(t, tc.value).Value()
692 for _, p := range tc.path {
693 if p == "" {
694 v = v.Template()("label")
695 } else {
696 v = v.Lookup(p)
697 }
698 }
699 b, err := v.MarshalJSON()
700 if err != nil {
701 t.Fatal(err)
702 }
703 if got := string(b); got != tc.want {
704 t.Errorf("\n got: %q\nwant: %q", got, tc.want)
705 }
706 })
707 }
708}
709
710func TestSubsumes(t *testing.T) {
711 a := []string{"a"}
712 b := []string{"b"}
713 testCases := []struct {
714 value string
715 pathA []string
716 pathB []string
717 want bool
718 }{{
719 value: `4`,
720 want: true,
721 }, {
722 value: `a: string, b: "foo"`,
723 pathA: a,
724 pathB: b,
725 want: true,
726 }, {
727 value: `a: string, b: "foo"`,
728 pathA: b,
729 pathB: a,
730 want: false,
731 }, {
732 value: `a: {a: string, b: 4}, b: {a: "foo", b: 4}`,
733 pathA: a,
734 pathB: b,
735 want: true,
736 }, {
737 value: `a: [string, 4], b: ["foo", 4]`,
738 pathA: a,
739 pathB: b,
740 want: true,
741 }}
742 for _, tc := range testCases {
743 t.Run(tc.value, func(t *testing.T) {
744 v := getInstance(t, tc.value)
745 a := v.Lookup(tc.pathA...)
746 b := v.Lookup(tc.pathB...)
747 got := a.Subsumes(b)
748 if got != tc.want {
749 t.Errorf("got %v (%v); want %v (%v)", got, a, tc.want, b)
750 }
751 })
752 }
753}
754
755func TestUnify(t *testing.T) {
756 a := []string{"a"}
757 b := []string{"b"}
758 testCases := []struct {
759 value string
760 pathA []string
761 pathB []string
762 want string
763 }{{
764 value: `4`,
765 want: `4`,
766 }, {
767 value: `a: string, b: "foo"`,
768 pathA: a,
769 pathB: b,
770 want: `"foo"`,
771 }, {
772 value: `a: string, b: "foo"`,
773 pathA: b,
774 pathB: a,
775 want: `"foo"`,
776 }, {
777 value: `a: {a: string, b: 4}, b: {a: "foo", b: 4}`,
778 pathA: a,
779 pathB: b,
780 want: `{"a":"foo","b":4}`,
781 }, {
782 value: `a: [string, 4], b: ["foo", 4]`,
783 pathA: a,
784 pathB: b,
785 want: `["foo",4]`,
786 }}
787 for _, tc := range testCases {
788 t.Run(tc.value, func(t *testing.T) {
789 v := getInstance(t, tc.value).Value()
790 x := v.Lookup(tc.pathA...)
791 y := v.Lookup(tc.pathB...)
792 b, err := x.Unify(y).MarshalJSON()
793 if err != nil {
794 t.Fatal(err)
795 }
796 if got := string(b); got != tc.want {
797 t.Errorf("got %v; want %v", got, tc.want)
798 }
799 })
800 }
801}
802
803func TestDecode(t *testing.T) {
804 type fields struct {
805 A int `json:"A"`
806 B int `json:"B"`
807 C int `json:"C"`
808 }
809 intList := func(ints ...int) *[]int {
810 ints = append([]int{}, ints...)
811 return &ints
812 }
813 testCases := []struct {
814 value string
815 dst interface{}
816 want interface{}
817 err string
818 }{{
819 value: `_|_`,
820 err: "from source",
821 }, {
822 value: `"str"`,
823 dst: "",
824 want: "str",
825 }, {
826 value: `"str"`,
827 dst: new(int),
828 err: "cannot unmarshal string into Go value of type int",
829 }, {
830 value: `{}`,
831 dst: &fields{},
832 want: &fields{},
833 }, {
834 value: `{a:1,b:2,c:3}`,
835 dst: &fields{},
836 want: &fields{A: 1, B: 2, C: 3},
837 }, {
Marcel van Lohuizen76b92b52018-12-16 10:47:03 +0100838 value: `{"\(k)": v for k, v in y if v > 1}
Marcel van Lohuizen17157ea2018-12-11 10:41:10 +0100839 y: {a:1,b:2,c:3}`,
840 dst: &fields{},
841 want: &fields{B: 2, C: 3},
842 }, {
843 value: `{a:1,b:2,c:int}`,
844 dst: new(fields),
845 err: "cannot convert incomplete value",
846 }, {
847 value: `[]`,
848 dst: intList(),
849 want: intList(),
850 }, {
851 value: `[1,2,3]`,
852 dst: intList(),
853 want: intList(1, 2, 3),
854 }, {
855 value: `[x for x in y if x > 1]
856 y: [1,2,3]`,
857 dst: intList(),
858 want: intList(2, 3),
859 }, {
860 value: `[int]`,
861 err: "cannot convert incomplete value",
862 }}
863 for _, tc := range testCases {
864 t.Run(tc.value, func(t *testing.T) {
865 err := getInstance(t, tc.value).Value().Decode(&tc.dst)
866 checkFatal(t, err, tc.err, "init")
867
868 if !cmp.Equal(tc.dst, tc.want) {
869 t.Error(cmp.Diff(tc.dst, tc.want))
870 t.Errorf("\n%#v\n%#v", tc.dst, tc.want)
871 }
872 })
873 }
874}
875
876func TestValueLookup(t *testing.T) {
877 config := `
878 a: {
879 a: 0
880 b: 1
881 c: 2
882 }
883 b: {
884 d: a.a
885 e: int
886 }
887 `
888
889 strList := func(s ...string) []string { return s }
890
891 testCases := []struct {
892 config string
893 path []string
894 str string
895 notExists bool
896 }{{
897 config: "_|_",
898 path: strList(""),
899 str: "from source",
900 }, {
901 config: "_|_",
902 path: strList("a"),
903 str: "from source",
904 }, {
905 config: config,
906 path: strList(),
907 str: "<0>{a: <1>{a: 0, b: 1, c: 2}, b: <2>{d: <0>.a.a, e: int}",
908 }, {
909 config: config,
910 path: strList("a", "a"),
911 str: "0",
912 }, {
913 config: config,
914 path: strList("a"),
915 str: "<0>{a: 0, b: 1, c: 2}",
916 }, {
917 config: config,
918 path: strList("b", "d"),
919 str: "0",
920 }, {
921 config: config,
922 path: strList("c", "non-existing"),
923 str: "not found",
924 notExists: true,
925 }, {
926 config: config,
927 path: strList("b", "d", "lookup in non-struct"),
928 str: "not of right kind (int vs struct)",
929 }}
930 for _, tc := range testCases {
931 t.Run(tc.str, func(t *testing.T) {
932 v := getInstance(t, tc.config).Value().Lookup(tc.path...)
933 if got := !v.Exists(); got != tc.notExists {
934 t.Errorf("exists: got %v; want %v", got, tc.notExists)
935 }
936
937 got := fmt.Sprint(v)
938 if tc.str == "" {
939 t.Fatalf("str empty, got %q", got)
940 }
941 if !strings.Contains(got, tc.str) {
942 t.Errorf("\n got %v\nwant %v", got, tc.str)
943 }
944 })
945 }
946}
947
948func TestMashalJSON(t *testing.T) {
949 testCases := []struct {
950 value string
951 json string
952 err string
953 }{{
954 value: `""`,
955 json: `""`,
956 }, {
957 value: `null`,
958 json: `null`,
959 }, {
960 value: `_|_`,
961 err: "from source",
962 }, {
963 value: `(a.b)
964 a: {}`,
965 err: "undefined field",
966 }, {
Marcel van Lohuizen17157ea2018-12-11 10:41:10 +0100967 value: `true`,
968 json: `true`,
969 }, {
970 value: `false`,
971 json: `false`,
972 }, {
973 value: `bool`,
974 json: `bool`,
975 err: "cannot convert incomplete value",
976 }, {
977 value: `"str"`,
978 json: `"str"`,
979 }, {
980 value: `12_000`,
981 json: `12000`,
982 }, {
983 value: `12.000`,
984 json: `12.000`,
985 }, {
986 value: `12M`,
987 json: `12000000`,
988 }, {
989 value: `3.0e100`,
990 json: `3.0E+100`,
991 }, {
992 value: `[]`,
993 json: `[]`,
994 }, {
995 value: `[1, 2, 3]`,
996 json: `[1,2,3]`,
997 }, {
998 value: `[int]`,
999 err: `cannot convert incomplete value`,
1000 }, {
1001 value: `((0..3) * [1, 2])`,
1002 json: `[1,2]`,
1003 }, {
1004 value: `{}`,
1005 json: `{}`,
1006 }, {
1007 value: `{a: 2, b: 3, c: ["A", "B"]}`,
1008 json: `{"a":2,"b":3,"c":["A","B"]}`,
1009 }}
1010 for i, tc := range testCases {
1011 t.Run(fmt.Sprintf("%d/%v", i, tc.value), func(t *testing.T) {
1012 inst := getInstance(t, tc.value)
1013 b, err := inst.Value().MarshalJSON()
1014 checkFatal(t, err, tc.err, "init")
1015
1016 if got := string(b); got != tc.json {
1017 t.Errorf("\n got %v;\nwant %v", got, tc.json)
1018 }
1019 })
1020 }
1021}
1022
1023func TestWalk(t *testing.T) {
1024 testCases := []struct {
1025 value string
1026 out string
1027 }{{
1028 value: `""`,
1029 out: `""`,
1030 }, {
1031 value: `null`,
1032 out: `null`,
1033 }, {
1034 value: `_|_`,
1035 out: "_|_(from source)",
1036 }, {
1037 value: `(a.b)
1038 a: {}`,
1039 out: `_|_(<0>.a.b:undefined field "b")`,
1040 }, {
1041 value: `true`,
1042 out: `true`,
1043 }, {
1044 value: `false`,
1045 out: `false`,
1046 }, {
1047 value: `bool`,
1048 out: "bool",
1049 }, {
1050 value: `"str"`,
1051 out: `"str"`,
1052 }, {
1053 value: `12_000`,
1054 out: `12000`,
1055 }, {
1056 value: `12.000`,
1057 out: `12.000`,
1058 }, {
1059 value: `12M`,
1060 out: `12000000`,
1061 }, {
1062 value: `3.0e100`,
1063 out: `3.0e+100`,
1064 }, {
1065 value: `[]`,
1066 out: `[]`,
1067 }, {
1068 value: `[1, 2, 3]`,
1069 out: `[1,2,3]`,
1070 }, {
1071 value: `[int]`,
1072 out: `[int]`,
1073 }, {
1074 value: `((0..3) * [1, 2])`,
1075 out: `[1,2]`,
1076 }, {
1077 value: `{}`,
1078 out: `{}`,
1079 }, {
1080 value: `{a: 2, b: 3, c: ["A", "B"]}`,
1081 out: `{a:2,b:3,c:["A","B"]}`,
1082 }}
1083 for i, tc := range testCases {
1084 t.Run(fmt.Sprintf("%d/%v", i, tc.value), func(t *testing.T) {
1085 inst := getInstance(t, tc.value)
1086 buf := []byte{}
1087 stripComma := func() {
1088 if n := len(buf) - 1; buf[n] == ',' {
1089 buf = buf[:n]
1090 }
1091 }
1092 inst.Value().Walk(func(v Value) bool {
1093 if k, ok := v.Label(); ok {
1094 buf = append(buf, k+":"...)
1095 }
1096 switch v.Kind() {
1097 case StructKind:
1098 buf = append(buf, '{')
1099 case ListKind:
1100 buf = append(buf, '[')
1101 default:
1102 buf = append(buf, fmt.Sprint(v, ",")...)
1103 }
1104 return true
1105 }, func(v Value) {
1106 switch v.Kind() {
1107 case StructKind:
1108 stripComma()
1109 buf = append(buf, "},"...)
1110 case ListKind:
1111 stripComma()
1112 buf = append(buf, "],"...)
1113 }
1114 })
1115 stripComma()
1116 if got := string(buf); got != tc.out {
1117 t.Errorf("\n got %v;\nwant %v", got, tc.out)
1118 }
1119 })
1120 }
1121}
1122
1123func TestTrimZeros(t *testing.T) {
1124 testCases := []struct {
1125 in string
1126 out string
1127 }{
1128 {"", ""},
1129 {"2", "2"},
1130 {"2.0", "2.0"},
1131 {"2.000000000000", "2.0"},
1132 {"2000000000000", "2e+12"},
1133 {"2000000", "2e+6"},
1134 }
1135 for _, tc := range testCases {
1136 t.Run(tc.in, func(t *testing.T) {
1137 if got := trimZeros(tc.in); got != tc.out {
1138 t.Errorf("got %q; want %q", got, tc.out)
1139 }
1140 })
1141 }
1142}
1143
1144func TestReferences(t *testing.T) {
1145 config1 := `
1146 a: {
1147 b: 3
1148 }
1149 c: {
1150 d: a.b
1151 e: c.d
1152 f: a
1153 }
1154 `
1155 config2 := `
1156 a: { c: 3 }
1157 b: { c: int, d: 4 }
1158 r: (a & b).c
1159 `
1160 testCases := []struct {
1161 config string
1162 in string
1163 out string
1164 }{
1165 {config1, "c.d", "a.b"},
1166 {config1, "c.e", "c.d"},
1167 {config1, "c.f", "a"},
1168
1169 {config2, "r", "a.c b.c"},
1170 }
1171 for _, tc := range testCases {
1172 t.Run(tc.in, func(t *testing.T) {
1173 ctx, st := compileFile(t, tc.config)
1174 v := newValueRoot(ctx, st)
1175 for _, k := range strings.Split(tc.in, ".") {
1176 obj, err := v.structVal(ctx)
1177 if err != nil {
1178 t.Fatal(err)
1179 }
1180 v = obj.Lookup(k)
1181 }
1182 got := []string{}
1183 for _, r := range v.References() {
1184 got = append(got, strings.Join(r, "."))
1185 }
1186 want := strings.Split(tc.out, " ")
1187 if !reflect.DeepEqual(got, want) {
1188 t.Errorf("got %v; want %v", got, want)
1189 }
1190 })
1191 }
1192}
1193
1194func checkErr(t *testing.T, err error, str, name string) bool {
1195 t.Helper()
1196 if err == nil {
1197 if str != "" {
1198 t.Errorf(`err:%s: got ""; want %q`, name, str)
1199 }
1200 return true
1201 }
1202 return checkFailed(t, err, str, name)
1203}
1204
1205func checkFatal(t *testing.T, err error, str, name string) {
1206 t.Helper()
1207 if !checkFailed(t, err, str, name) {
1208 t.SkipNow()
1209 }
1210}
1211
1212func checkFailed(t *testing.T, err error, str, name string) bool {
1213 t.Helper()
1214 if err != nil {
1215 got := err.Error()
1216 if str == "" {
1217 t.Fatalf(`err:%s: got %q; want ""`, name, got)
1218 }
1219 if !strings.Contains(got, str) {
1220 t.Errorf(`err:%s: got %q; want %q`, name, got, str)
1221 }
1222 return false
1223 }
1224 return true
1225}