this repo has no description
at master 1025 lines 21 kB view raw
1// Copyright 2024 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 toml_test 16 17import ( 18 "bytes" 19 "encoding/json" 20 "io" 21 "path" 22 "reflect" 23 "strings" 24 "testing" 25 26 "github.com/go-quicktest/qt" 27 gotoml "github.com/pelletier/go-toml/v2" 28 29 "cuelang.org/go/cue/ast" 30 "cuelang.org/go/cue/ast/astutil" 31 "cuelang.org/go/cue/cuecontext" 32 "cuelang.org/go/cue/errors" 33 "cuelang.org/go/cue/format" 34 "cuelang.org/go/cue/token" 35 "cuelang.org/go/encoding/toml" 36 "cuelang.org/go/internal/astinternal" 37 "cuelang.org/go/internal/cuetxtar" 38) 39 40func TestDecoder(t *testing.T) { 41 t.Parallel() 42 // Note that we use backquoted Go string literals with indentation for readability. 43 // The whitespace doesn't affect the input TOML, and we cue/format on the "want" CUE source, 44 // so the added newlines and tabs don't change the test behavior. 45 tests := []struct { 46 name string 47 input string 48 wantCUE string 49 wantErr string 50 }{{ 51 name: "Empty", 52 input: "", 53 wantCUE: "", 54 }, { 55 name: "LoneComment", 56 input: ` 57 # Just a comment 58 `, 59 wantCUE: "", 60 }, { 61 name: "RootKeyMissing", 62 input: ` 63 # A comment to verify that parser positions work. 64 = "no key name" 65 `, 66 wantErr: ` 67 invalid character at start of key: =: 68 test.toml:2:1 69 `, 70 }, { 71 name: "RootKeysOne", 72 input: ` 73 key = "value" 74 `, 75 wantCUE: ` 76 key: "value" 77 `, 78 }, { 79 name: "RootMultiple", 80 input: ` 81 key1 = "value1" 82 key2 = "value2" 83 key3 = "value3" 84 `, 85 wantCUE: ` 86 key1: "value1" 87 key2: "value2" 88 key3: "value3" 89 `, 90 }, { 91 name: "RootKeysDots", 92 input: ` 93 a1 = "A" 94 b1.b2 = "B" 95 c1.c2.c3 = "C" 96 `, 97 wantCUE: ` 98 a1: "A" 99 b1: b2: "B" 100 c1: c2: c3: "C" 101 `, 102 }, { 103 name: "RootKeysCharacters", 104 input: ` 105 a-b = "dashes" 106 a_b = "underscore unquoted" 107 _ = "underscore quoted" 108 _ab = "underscore prefix quoted" 109 123 = "numbers" 110 x._.y._ = "underscores quoted" 111 `, 112 wantCUE: ` 113 "a-b": "dashes" 114 a_b: "underscore unquoted" 115 "_": "underscore quoted" 116 "_ab": "underscore prefix quoted" 117 "123": "numbers" 118 x: "_": y: "_": "underscores quoted" 119 `, 120 }, { 121 name: "RootKeysQuoted", 122 input: ` 123 "1.2.3" = "quoted dots" 124 "foo bar" = "quoted space" 125 'foo "bar"' = "nested quotes" 126 `, 127 wantCUE: ` 128 "1.2.3": "quoted dots" 129 "foo bar": "quoted space" 130 "foo \"bar\"": "nested quotes" 131 `, 132 }, { 133 name: "RootKeysMixed", 134 input: ` 135 site."foo.com".title = "foo bar" 136 `, 137 wantCUE: ` 138 site: "foo.com": title: "foo bar" 139 `, 140 }, { 141 name: "KeysDuplicateSimple", 142 input: ` 143 foo = "same key" 144 foo = "same key" 145 `, 146 wantErr: ` 147 duplicate key: foo: 148 test.toml:2:1 149 `, 150 }, { 151 name: "KeysDuplicateQuoted", 152 input: ` 153 "foo" = "same key" 154 foo = "same key" 155 `, 156 wantErr: ` 157 duplicate key: foo: 158 test.toml:2:1 159 `, 160 }, { 161 name: "KeysDuplicateWhitespace", 162 input: ` 163 foo . bar = "same key" 164 foo.bar = "same key" 165 `, 166 wantErr: ` 167 duplicate key: foo.bar: 168 test.toml:2:1 169 `, 170 }, { 171 name: "KeysDuplicateDots", 172 input: ` 173 foo."bar.baz".zzz = "same key" 174 foo."bar.baz".zzz = "same key" 175 `, 176 wantErr: ` 177 duplicate key: foo."bar.baz".zzz: 178 test.toml:2:1 179 `, 180 }, { 181 name: "KeysNotDuplicateDots", 182 input: ` 183 foo."bar.baz" = "different key" 184 "foo.bar".baz = "different key" 185 `, 186 wantCUE: ` 187 foo: "bar.baz": "different key" 188 "foo.bar": baz: "different key" 189 `, 190 }, { 191 name: "BasicStrings", 192 input: ` 193 escapes = "foo \"bar\" \n\t\\ baz" 194 unicode = "foo \u00E9" 195 `, 196 wantCUE: ` 197 escapes: "foo \"bar\" \n\t\\ baz" 198 unicode: "foo é" 199 `, 200 }, { 201 // Leading tabs do matter in this test. 202 // TODO: use our own multiline strings where it gives better results. 203 name: "MultilineBasicStrings", 204 input: ` 205nested = """ can contain "" quotes """ 206four = """"four"""" 207double = """ 208line one 209line two""" 210double_indented = """ 211 line one 212 line two 213 """ 214escaped = """\ 215line one \ 216line two.\ 217""" 218 `, 219 wantCUE: ` 220 nested: " can contain \"\" quotes " 221 four: "\"four\"" 222 double: "line one\nline two" 223 double_indented: "\tline one\n\tline two\n\t" 224 escaped: "line one line two." 225 `, 226 }, { 227 // TODO: we can probably do better in many cases, e.g. #"" 228 name: "LiteralStrings", 229 input: ` 230 winpath = 'C:\Users\nodejs\templates' 231 winpath2 = '\\ServerX\admin$\system32\' 232 quoted = 'Tom "Dubs" Preston-Werner' 233 regex = '<\i\c*\s*>' 234 `, 235 wantCUE: ` 236 winpath: "C:\\Users\\nodejs\\templates" 237 winpath2: "\\\\ServerX\\admin$\\system32\\" 238 quoted: "Tom \"Dubs\" Preston-Werner" 239 regex: "<\\i\\c*\\s*>" 240 `, 241 }, { 242 // Leading tabs do matter in this test. 243 // TODO: use our own multiline strings where it gives better results. 244 name: "MultilineLiteralStrings", 245 input: ` 246nested = ''' can contain '' quotes ''' 247four = ''''four'''' 248double = ''' 249line one 250line two''' 251double_indented = ''' 252 line one 253 line two 254 ''' 255escaped = '''\ 256line one \ 257line two.\ 258''' 259 `, 260 wantCUE: ` 261 nested: " can contain '' quotes " 262 four: "'four'" 263 double: "line one\nline two" 264 double_indented: "\tline one\n\tline two\n\t" 265 escaped: "\\\nline one \\\nline two.\\\n" 266 `, 267 }, { 268 name: "Integers", 269 input: ` 270 zero = 0 271 positive = 123 272 plus = +40 273 minus = -40 274 underscores = 1_002_003 275 hexadecimal = 0xdeadBEEF 276 octal = 0o755 277 binary = 0b11010110 278 `, 279 wantCUE: ` 280 zero: 0 281 positive: 123 282 plus: +40 283 minus: -40 284 underscores: 1_002_003 285 hexadecimal: 0xdeadBEEF 286 octal: 0o755 287 binary: 0b11010110 288 `, 289 }, { 290 name: "Floats", 291 input: ` 292 pi = 3.1415 293 plus = +1.23 294 minus = -4.56 295 exponent = 1e067 296 exponent_plus = 5e+20 297 exponent_minus = -2E-4 298 exponent_dot = 6.789e-30 299 `, 300 wantCUE: ` 301 pi: 3.1415 302 plus: +1.23 303 minus: -4.56 304 exponent: 1e067 305 exponent_plus: 5e+20 306 exponent_minus: -2E-4 307 exponent_dot: 6.789e-30 308 `, 309 }, { 310 name: "Bools", 311 input: ` 312 positive = true 313 negative = false 314 `, 315 wantCUE: ` 316 positive: true 317 negative: false 318 `, 319 }, { 320 name: "DateTimes", 321 input: ` 322 offsetDateTime1 = 1979-05-27T07:32:00Z 323 offsetDateTime2 = 1979-05-27T00:32:00-07:00 324 offsetDateTime3 = 1979-05-27T00:32:00.999999-07:00 325 localDateTime1 = 1979-05-27T07:32:00 326 localDateTime2 = 1979-05-27T00:32:00.999999 327 localDate1 = 1979-05-27 328 localTime1 = 07:32:00 329 localTime2 = 00:32:00.999999 330 331 inlineArray = [1979-05-27, 07:32:00] 332 333 notActuallyDate = "1979-05-27" 334 notActuallyTime = "07:32:00" 335 inlineArrayNotActually = ["1979-05-27", "07:32:00"] 336 `, 337 wantCUE: ` 338 import "time" 339 340 offsetDateTime1: "1979-05-27T07:32:00Z" & time.Format(time.RFC3339) 341 offsetDateTime2: "1979-05-27T00:32:00-07:00" & time.Format(time.RFC3339) 342 offsetDateTime3: "1979-05-27T00:32:00.999999-07:00" & time.Format(time.RFC3339) 343 localDateTime1: "1979-05-27T07:32:00" & time.Format("2006-01-02T15:04:05") 344 localDateTime2: "1979-05-27T00:32:00.999999" & time.Format("2006-01-02T15:04:05") 345 localDate1: "1979-05-27" & time.Format(time.RFC3339Date) 346 localTime1: "07:32:00" & time.Format("15:04:05") 347 localTime2: "00:32:00.999999" & time.Format("15:04:05") 348 inlineArray: ["1979-05-27" & time.Format(time.RFC3339Date), "07:32:00" & time.Format("15:04:05")] 349 notActuallyDate: "1979-05-27" 350 notActuallyTime: "07:32:00" 351 inlineArrayNotActually: ["1979-05-27", "07:32:00"] 352 `, 353 }, { 354 name: "Arrays", 355 input: ` 356 integers = [1, 2, 3] 357 colors = ["red", "yellow", "green"] 358 nested_ints = [[1, 2], [3, 4, 5]] 359 nested_mixed = [[1, 2], ["a", "b", "c"], {extra = "keys"}] 360 strings = ["all", 'strings', """are the same""", '''type'''] 361 mixed_numbers = [0.1, 0.2, 0.5, 1, 2, 5] 362 `, 363 wantCUE: ` 364 integers: [1, 2, 3] 365 colors: ["red", "yellow", "green"] 366 nested_ints: [[1, 2], [3, 4, 5]] 367 nested_mixed: [[1, 2], ["a", "b", "c"], {extra: "keys"}] 368 strings: ["all", "strings", "are the same", "type"] 369 mixed_numbers: [0.1, 0.2, 0.5, 1, 2, 5] 370 `, 371 }, { 372 name: "InlineTables", 373 input: ` 374 empty = {} 375 point = {x = 1, y = 2} 376 animal = {type.name = "pug"} 377 deep = {l1 = {l2 = {l3 = "leaf"}}} 378 `, 379 wantCUE: ` 380 empty: {} 381 point: {x: 1, y: 2} 382 animal: {type: name: "pug"} 383 deep: {l1: {l2: {l3: "leaf"}}} 384 `, 385 }, { 386 name: "InlineTablesDuplicate", 387 input: ` 388 point = {x = "same key", x = "same key"} 389 `, 390 wantErr: ` 391 duplicate key: point.x: 392 test.toml:1:26 393 `, 394 }, { 395 name: "ArrayInlineTablesDuplicate", 396 input: ` 397 point = [{}, {}, {x = "same key", x = "same key"}] 398 `, 399 wantErr: ` 400 duplicate key: point.2.x: 401 test.toml:1:35 402 `, 403 }, { 404 name: "InlineTablesNotDuplicateScoping", 405 input: ` 406 repeat = {repeat = {repeat = "leaf"}} 407 struct1 = {sibling = "leaf"} 408 struct2 = {sibling = "leaf"} 409 arrays = [{sibling = "leaf"}, {sibling = "leaf"}] 410 `, 411 wantCUE: ` 412 repeat: {repeat: {repeat: "leaf"}} 413 struct1: {sibling: "leaf"} 414 struct2: {sibling: "leaf"} 415 arrays: [{sibling: "leaf"}, {sibling: "leaf"}] 416 `, 417 }, { 418 name: "TablesEmpty", 419 input: ` 420 [foo] 421 [bar] 422 `, 423 wantCUE: ` 424 foo: {} 425 bar: {} 426 `, 427 }, { 428 name: "TablesOne", 429 input: ` 430 [foo] 431 single = "single" 432 `, 433 wantCUE: ` 434 foo: { 435 single: "single" 436 } 437 `, 438 }, { 439 name: "TablesMultiple", 440 input: ` 441 root1 = "root1 value" 442 root2 = "root2 value" 443 [foo] 444 foo1 = "foo1 value" 445 foo2 = "foo2 value" 446 [bar] 447 bar1 = "bar1 value" 448 bar2 = "bar2 value" 449 `, 450 wantCUE: ` 451 root1: "root1 value" 452 root2: "root2 value" 453 foo: { 454 foo1: "foo1 value" 455 foo2: "foo2 value" 456 } 457 bar: { 458 bar1: "bar1 value" 459 bar2: "bar2 value" 460 } 461 `, 462 }, { 463 // A lot of these edge cases are covered by RootKeys tests already. 464 name: "TablesKeysComplex", 465 input: ` 466 [foo.bar . "baz.zzz zzz"] 467 one = "1" 468 [123-456] 469 two = "2" 470 `, 471 wantCUE: ` 472 foo: bar: "baz.zzz zzz": { 473 one: "1" 474 } 475 "123-456": { 476 two: "2" 477 } 478 `, 479 }, { 480 name: "TableKeysDuplicateSimple", 481 input: ` 482 [foo] 483 [foo] 484 `, 485 wantErr: ` 486 duplicate key: foo: 487 test.toml:2:2 488 `, 489 }, { 490 name: "TableKeysDuplicateOverlap", 491 input: ` 492 [foo] 493 bar = "leaf" 494 [foo.bar] 495 baz = "second leaf" 496 `, 497 wantErr: ` 498 duplicate key: foo.bar: 499 test.toml:3:2 500 `, 501 }, { 502 name: "TableInnerKeysDuplicateSimple", 503 input: ` 504 [foo] 505 bar = "same key" 506 bar = "same key" 507 `, 508 wantErr: ` 509 duplicate key: foo.bar: 510 test.toml:3:1 511 `, 512 }, { 513 name: "TablesNotDuplicateScoping", 514 input: ` 515 [repeat] 516 repeat.repeat = "leaf" 517 [struct1] 518 sibling = "leaf" 519 [struct2] 520 sibling = "leaf" 521 `, 522 wantCUE: ` 523 repeat: { 524 repeat: repeat: "leaf" 525 } 526 struct1: { 527 sibling: "leaf" 528 } 529 struct2: { 530 sibling: "leaf" 531 } 532 `, 533 }, { 534 name: "ArrayTablesEmpty", 535 input: ` 536 [[foo]] 537 `, 538 wantCUE: ` 539 foo: [ 540 {}, 541 ] 542 `, 543 }, { 544 name: "ArrayTablesOne", 545 input: ` 546 [[foo]] 547 single = "single" 548 `, 549 wantCUE: ` 550 foo: [ 551 { 552 single: "single" 553 }, 554 ] 555 `, 556 }, { 557 name: "ArrayTablesMultiple", 558 input: ` 559 root = "root value" 560 [[foo]] 561 foo1 = "foo1 value" 562 foo2 = "foo2 value" 563 [[foo]] 564 foo3 = "foo3 value" 565 foo4 = "foo4 value" 566 [[foo]] 567 [[foo]] 568 single = "single" 569 `, 570 wantCUE: ` 571 root: "root value" 572 foo: [ 573 { 574 foo1: "foo1 value" 575 foo2: "foo2 value" 576 }, 577 { 578 foo3: "foo3 value" 579 foo4: "foo4 value" 580 }, 581 {}, 582 { 583 single: "single" 584 }, 585 ] 586 `, 587 }, { 588 name: "ArrayTablesSeparate", 589 input: ` 590 root = "root value" 591 [[foo]] 592 foo1 = "foo1 value" 593 [[bar]] 594 bar1 = "bar1 value" 595 [[baz]] 596 `, 597 wantCUE: ` 598 root: "root value" 599 foo: [ 600 { 601 foo1: "foo1 value" 602 }, 603 ] 604 bar: [ 605 { 606 bar1: "bar1 value" 607 }, 608 ] 609 baz: [ 610 {}, 611 ] 612 `, 613 }, { 614 name: "ArrayTablesSubtable", 615 input: ` 616 [[foo]] 617 foo1 = "foo1 value" 618 [foo.subtable1] 619 sub1 = "sub1 value" 620 [foo.subtable2] 621 sub2 = "sub2 value" 622 [foo.subtable2.deeper] 623 sub2d = "sub2d value" 624 [[foo]] 625 foo2 = "foo2 value" 626 `, 627 wantCUE: ` 628 foo: [ 629 { 630 foo1: "foo1 value" 631 subtable1: { 632 sub1: "sub1 value" 633 } 634 subtable2: { 635 sub2: "sub2 value" 636 } 637 subtable2: deeper: { 638 sub2d: "sub2d value" 639 } 640 }, 641 { 642 foo2: "foo2 value" 643 }, 644 ] 645 `, 646 }, { 647 name: "ArrayTablesSubtableDuplicateKey", 648 input: ` 649 [[foo]] 650 [foo.subtable] 651 name = "bar" 652 [[foo]] 653 [foo.subtable] 654 name = "bar" 655 `, 656 wantCUE: ` 657 foo: [ 658 { 659 subtable: { 660 name: "bar" 661 } 662 }, 663 { 664 subtable: { 665 name: "bar" 666 } 667 } 668 ] 669 `, 670 }, { 671 name: "ArrayTablesSubtableActualDuplicate", 672 input: ` 673 [[foo]] 674 [foo.subtable] 675 name = "bar" 676 [foo.subtable] 677 `, 678 wantErr: ` 679 duplicate key: foo.subtable: 680 test.toml:4:2 681 `, 682 }, { 683 name: "ArrayTablesNested", 684 input: ` 685 [[foo]] 686 foo1 = "foo1 value" 687 [[foo.nested1]] 688 nest1a = "nest1a value" 689 [[foo.nested1]] 690 nest1b = "nest1b value" 691 [[foo.nested2]] 692 nest2 = "nest2 value" 693 [[foo.nested2.deeper]] 694 nest2d = "nest2d value" 695 [[foo.nested3.directly.deeper]] 696 nest3d = "nest3d value" 697 [[foo]] 698 foo2 = "foo2 value" 699 `, 700 wantCUE: ` 701 foo: [ 702 { 703 foo1: "foo1 value" 704 nested1: [ 705 { 706 nest1a: "nest1a value" 707 }, 708 { 709 nest1b: "nest1b value" 710 }, 711 ] 712 nested2: [ 713 { 714 nest2: "nest2 value" 715 deeper: [ 716 { 717 nest2d: "nest2d value" 718 } 719 ] 720 }, 721 ] 722 nested3: directly: deeper: [ 723 { 724 nest3d: "nest3d value" 725 }, 726 ] 727 }, 728 { 729 foo2: "foo2 value" 730 }, 731 ] 732 `, 733 }, { 734 name: "ArrayTablesNestedSiblings", 735 input: ` 736 [[foo]] 737 foo1 = "foo1 value" 738 [[foo.nested1]] 739 foo1_n1 = "foo1_n1 value" 740 not_duplicate = "not a duplicate" 741 [[foo.nested2.deeper]] 742 foo1_n2 = "foo1_n2 value" 743 [[foo]] 744 foo2 = "foo2 value" 745 [[foo.nested1]] 746 foo2_n1 = "foo2_n1 value" 747 not_duplicate = "not a duplicate" 748 [[foo.nested2.deeper]] 749 foo2_n2 = "foo2_n2 value" 750 `, 751 wantCUE: ` 752 foo: [ 753 { 754 foo1: "foo1 value" 755 nested1: [ 756 { 757 foo1_n1: "foo1_n1 value" 758 not_duplicate: "not a duplicate" 759 }, 760 ] 761 nested2: deeper: [ 762 { 763 foo1_n2: "foo1_n2 value" 764 }, 765 ] 766 }, 767 { 768 foo2: "foo2 value" 769 nested1: [ 770 { 771 foo2_n1: "foo2_n1 value" 772 not_duplicate: "not a duplicate" 773 }, 774 ] 775 nested2: deeper: [ 776 { 777 foo2_n2: "foo2_n2 value" 778 }, 779 ] 780 }, 781 ] 782 `, 783 }, { 784 name: "RedeclareKeyAsTableArray", 785 input: ` 786 foo = "foo value" 787 [middle] 788 middle = "to ensure we don't rely on the last key" 789 [[foo]] 790 baz = "baz value" 791 `, 792 wantErr: ` 793 cannot redeclare key "foo" as a table array: 794 test.toml:4:3 795 `, 796 }, { 797 name: "RedeclareTableAsTableArray", 798 input: ` 799 [foo] 800 bar = "bar value" 801 [middle] 802 middle = "to ensure we don't rely on the last key" 803 [[foo]] 804 baz = "baz value" 805 `, 806 wantErr: ` 807 cannot redeclare key "foo" as a table array: 808 test.toml:5:3 809 `, 810 }, { 811 name: "RedeclareArrayAsTableArray", 812 input: ` 813 foo = ["inline array"] 814 [middle] 815 middle = "to ensure we don't rely on the last key" 816 [[foo]] 817 baz = "baz value" 818 `, 819 wantErr: ` 820 cannot redeclare key "foo" as a table array: 821 test.toml:4:3 822 `, 823 }, { 824 name: "RedeclareTableArrayAsKey", 825 input: ` 826 [[foo.foo2]] 827 bar = "bar value" 828 [middle] 829 middle = "to ensure we don't rely on the last key" 830 [foo] 831 foo2 = "redeclaring" 832 `, 833 wantErr: ` 834 cannot redeclare table array "foo.foo2" as a table: 835 test.toml:6:1 836 `, 837 }, { 838 name: "RedeclareTableArrayAsTable", 839 input: ` 840 [[foo]] 841 bar = "bar value" 842 [middle] 843 middle = "to ensure we don't rely on the last key" 844 [foo] 845 baz = "baz value" 846 `, 847 wantErr: ` 848 cannot redeclare table array "foo" as a table: 849 test.toml:5:2 850 `, 851 }, { 852 name: "KeysNotDuplicateTableArrays", 853 input: ` 854 [[foo]] 855 bar = "foo.0.bar" 856 [[foo]] 857 bar = "foo.1.bar" 858 [[foo]] 859 bar = "foo.2.bar" 860 [[foo.nested]] 861 bar = "foo.2.nested.0.bar" 862 [[foo.nested]] 863 bar = "foo.2.nested.1.bar" 864 [[foo.nested]] 865 bar = "foo.2.nested.2.bar" 866 `, 867 wantCUE: ` 868 foo: [ 869 { 870 bar: "foo.0.bar" 871 }, 872 { 873 bar: "foo.1.bar" 874 }, 875 { 876 bar: "foo.2.bar" 877 nested: [ 878 { 879 bar: "foo.2.nested.0.bar" 880 }, 881 { 882 bar: "foo.2.nested.1.bar" 883 }, 884 { 885 bar: "foo.2.nested.2.bar" 886 }, 887 ] 888 }, 889 ] 890 `, 891 }} 892 for _, test := range tests { 893 t.Run(test.name, func(t *testing.T) { 894 t.Parallel() 895 896 input := unindentMultiline(test.input) 897 dec := toml.NewDecoder("test.toml", strings.NewReader(input)) 898 899 node, err := dec.Decode() 900 if test.wantErr != "" { 901 gotErr := strings.TrimSuffix(errors.Details(err, nil), "\n") 902 wantErr := unindentMultiline(test.wantErr) 903 904 qt.Assert(t, qt.Equals(gotErr, wantErr)) 905 qt.Assert(t, qt.IsNil(node)) 906 // We don't continue, so we can't expect any decoded CUE. 907 qt.Assert(t, qt.Equals(test.wantCUE, "")) 908 909 // Validate that go-toml's Unmarshal also rejects this input. 910 err = gotoml.Unmarshal([]byte(input), new(any)) 911 qt.Assert(t, qt.IsNotNil(err)) 912 return 913 } 914 qt.Assert(t, qt.IsNil(err)) 915 916 file, err := astutil.ToFile(node) 917 qt.Assert(t, qt.IsNil(err)) 918 919 node2, err := dec.Decode() 920 qt.Assert(t, qt.IsNil(node2)) 921 qt.Assert(t, qt.Equals(err, io.EOF)) 922 923 wantFormatted, err := format.Source([]byte(test.wantCUE)) 924 qt.Assert(t, qt.IsNil(err), qt.Commentf("wantCUE:\n%s", test.wantCUE)) 925 926 formatted, err := format.Node(file) 927 qt.Assert(t, qt.IsNil(err)) 928 t.Logf("CUE:\n%s", formatted) 929 qt.Assert(t, qt.Equals(string(formatted), string(wantFormatted))) 930 931 // Ensure that the CUE node can be compiled into a cue.Value and validated. 932 ctx := cuecontext.New() 933 val := ctx.BuildFile(file) 934 qt.Assert(t, qt.IsNil(val.Err())) 935 qt.Assert(t, qt.IsNil(val.Validate())) 936 937 // Validate that the decoded CUE value is equivalent 938 // to the Go value that go-toml's Unmarshal produces. 939 // We use JSON equality as some details such as which integer types are used 940 // are not actually relevant to an "equal data" check. 941 var unmarshalTOML any 942 err = gotoml.Unmarshal([]byte(input), &unmarshalTOML) 943 qt.Assert(t, qt.IsNil(err)) 944 jsonTOML, err := json.Marshal(unmarshalTOML) 945 qt.Assert(t, qt.IsNil(err)) 946 t.Logf("json.Marshal via go-toml:\t%s\n", jsonTOML) 947 948 jsonCUE, err := json.Marshal(val) 949 qt.Assert(t, qt.IsNil(err)) 950 t.Logf("json.Marshal via CUE:\t%s\n", jsonCUE) 951 qt.Assert(t, qt.JSONEquals(jsonCUE, unmarshalTOML)) 952 953 // Ensure that the decoded CUE can be re-encoded as TOML, 954 // and the resulting TOML is still JSON-equivalent. 955 t.Run("reencode", func(t *testing.T) { 956 switch test.name { 957 case "DateTimes": 958 t.Skip("TODO(mvdan): dates and times always encode as TOML strings today") 959 } 960 sb := new(strings.Builder) 961 enc := toml.NewEncoder(sb) 962 963 err := enc.Encode(val) 964 qt.Assert(t, qt.IsNil(err)) 965 cueTOML := sb.String() 966 t.Logf("reencoded TOML:\n%s", cueTOML) 967 968 var unmarshalCueTOML any 969 err = gotoml.Unmarshal([]byte(cueTOML), &unmarshalCueTOML) 970 qt.Assert(t, qt.IsNil(err)) 971 972 qt.Assert(t, qt.CmpEquals(unmarshalCueTOML, unmarshalTOML)) 973 }) 974 }) 975 } 976} 977 978// unindentMultiline mimics CUE's behavior with `"""` multi-line strings, 979// where a leading newline is omitted, and any whitespace preceding the trailing newline 980// is removed from the start of all lines. 981func unindentMultiline(s string) string { 982 i := strings.LastIndexByte(s, '\n') 983 if i < 0 { 984 // Not a multi-line string. 985 return s 986 } 987 trim := s[i:] 988 s = strings.ReplaceAll(s, trim, "\n") 989 s = strings.TrimPrefix(s, "\n") 990 s = strings.TrimSuffix(s, "\n") 991 return s 992} 993 994var ( 995 typNode = reflect.TypeFor[ast.Node]() 996 typPos = reflect.TypeFor[token.Pos]() 997) 998 999func TestDecoderTxtar(t *testing.T) { 1000 test := cuetxtar.TxTarTest{ 1001 Root: "testdata", 1002 Name: "decode", 1003 } 1004 1005 test.Run(t, func(t *cuetxtar.Test) { 1006 for _, file := range t.Archive.Files { 1007 if strings.HasPrefix(file.Name, "out/") { 1008 continue 1009 } 1010 dec := toml.NewDecoder(file.Name, bytes.NewReader(file.Data)) 1011 node, err := dec.Decode() 1012 qt.Assert(t, qt.IsNil(err)) 1013 1014 // Show all valid node positions. 1015 out := astinternal.AppendDebug(nil, node, astinternal.DebugConfig{ 1016 OmitEmpty: true, 1017 Filter: func(v reflect.Value) bool { 1018 t := v.Type() 1019 return t.Implements(typNode) || t.Kind() == reflect.Slice || t == typPos 1020 }, 1021 }) 1022 t.Writer(path.Join(file.Name, "positions")).Write(out) 1023 } 1024 }) 1025}