1
2
3 package parser
4
5 import (
6 "fmt"
7 "strings"
8 "testing"
9
10 "github.com/kylelemons/godebug/diff"
11 "github.com/kylelemons/godebug/pretty"
12 )
13
14 func TestError(t *testing.T) {
15 inputs := []struct {
16 in string
17 err string
18 }{{
19 in: "list_wrong_end: [one, two}",
20 }, {
21 in: "multy_string_list: ['a' 'b', 'c']",
22 err: "multiple-string value",
23 }, {
24 in: `mapping {{
25 my_proto_field: "foo"
26 }}`, err: "Failed to find a FieldName",
27 }, {
28 in: `name: "string with literal new line
29 "`, err: "new line",
30 }}
31 for _, input := range inputs {
32 out, err := Format([]byte(input.in))
33 if err == nil {
34 nodes, err := Parse([]byte(input.in))
35 if err != nil {
36 t.Errorf("Parse %v returned err %v", input.in, err)
37 continue
38 }
39 t.Errorf("Expected a formatting error but none was raised while formatting:\n%v\nout\n%s\ntree\n%s\n", input.in, out, DebugFormat(nodes, 0))
40 continue
41 }
42 if !strings.Contains(err.Error(), input.err) {
43 t.Errorf(
44 "Expected a formatting error containing \"%v\",\n error was: \"%v\"\nwhile formatting:\n%v", input.err, err, input.in)
45 }
46 }
47 }
48
49 func TestPreprocess(t *testing.T) {
50 type testType int
51 const (
52 nonTripleQuotedTest testType = 1
53 tripleQuotedTest testType = 2
54 )
55 inputs := []struct {
56 name string
57 in string
58 want map[int]bool
59 err bool
60 testType testType
61 }{{
62 name: "simple example",
63
64 in: `p {}`,
65 want: map[int]bool{
66 2: true,
67 },
68 }, {
69 name: "multiple nested children in the same line",
70
71 in: `p { b { n: v } }`,
72 want: map[int]bool{
73 2: true,
74 6: true,
75 },
76 }, {
77 name: "only second line",
78
79 in: `p {
80 b { n: v } }`,
81 want: map[int]bool{
82 6: true,
83 },
84 }, {
85 name: "empty output",
86 in: `p {
87 b {
88 n: v } }`,
89 want: map[int]bool{},
90 }, {
91 name: "comments and strings",
92 in: `
93 # p {}
94 s: "p {}"
95 # s: "p {}"
96 s: "#p {}"
97 p {}`,
98 want: map[int]bool{
99
100 58: true,
101 },
102 }, {
103 name: "escaped char",
104 in: `p { s="\"}"
105 }`,
106 want: map[int]bool{},
107 }, {
108 name: "missing '}'",
109 in: `p {`,
110 want: map[int]bool{},
111 }, {
112 name: "too many '}'",
113 in: `p {}}`,
114 err: true,
115 }, {
116 name: "single quote",
117 in: `"`,
118 err: true,
119 }, {
120 name: "double quote",
121 in: `""`,
122 }, {
123 name: "two single quotes",
124 in: `''`,
125 }, {
126 name: "single single quote",
127 in: `'`,
128 err: true,
129 }, {
130 name: "naked single quote in double quotes",
131 in: `"'"`,
132 }, {
133 name: "escaped single quote in double quotes",
134 in: `"\'"`,
135 }, {
136 name: "invalid naked single quote in single quotes",
137 in: `'''`,
138 err: true,
139 }, {
140 name: "invalid standalone angled bracket",
141 in: `>`,
142 err: true,
143 }, {
144 name: "invalid angled bracket outside template",
145 in: `foo > bar`,
146 err: true,
147 }, {
148 name: "valid angled bracket inside string",
149 in: `"foo > bar"`,
150 }, {
151 name: "valid angled bracket inside template",
152 in: `% foo >= bar %`,
153 }, {
154 name: "valid angled bracket inside comment",
155 in: `# foo >= bar`,
156 }, {
157 name: "valid angled bracket inside if condition in template",
158 in: `%if (value > 0)%`,
159 }, {
160 name: "valid templated arg inside comment",
161 in: `# foo: %bar%`,
162 }, {
163 name: "valid templated arg inside string",
164 in: `foo: "%bar%"`,
165 }, {
166 name: "% delimiter inside commented lines",
167 in: `
168 # comment %
169 {
170 # comment %
171 }
172 `,
173 }, {
174 name: "% delimiter inside strings",
175 in: `
176 foo: "%"
177 {
178 bar: "%"
179 }
180 `,
181 }, {
182 name: "escaped single quote in single quotes",
183 in: `'\''`,
184 }, {
185 name: "two single quotes",
186 in: `''`,
187 }, {
188 name: "triple quoted backlash",
189 in: `"""\"""`,
190 err: false,
191 testType: tripleQuotedTest,
192 }, {
193 name: "triple quoted backlash invalid",
194 in: `"""\"""`,
195 err: true,
196 testType: nonTripleQuotedTest,
197 }, {
198 name: "triple quoted and regular quotes backslash handling",
199 in: `"""text""" "\""`,
200 err: false,
201 testType: tripleQuotedTest,
202 }}
203 for _, input := range inputs {
204 bytes := []byte(input.in)
205
206 bytes = bytes[0:len(bytes):len(bytes)]
207 if input.testType != tripleQuotedTest {
208 have, err := sameLineBrackets(bytes, false)
209 if (err != nil) != input.err {
210 t.Errorf("sameLineBrackets[%s] allowTripleQuotedStrings=false %v returned err %v", input.name, input.in, err)
211 continue
212 }
213 if diff := pretty.Compare(input.want, have); diff != "" {
214 t.Errorf("sameLineBrackets[%s] allowTripleQuotedStrings=false %v returned diff (-want, +have):\n%s", input.name, input.in, diff)
215 }
216 }
217
218 if input.testType != nonTripleQuotedTest {
219 have, err := sameLineBrackets(bytes, true)
220 if (err != nil) != input.err {
221 t.Errorf("sameLineBrackets[%s] allowTripleQuotedStrings=true %v returned err %v", input.name, input.in, err)
222 continue
223 }
224 if diff := pretty.Compare(input.want, have); diff != "" {
225 t.Errorf("sameLineBrackets[%s] allowTripleQuotedStrings=true %v returned diff (-want, +have):\n%s", input.name, input.in, diff)
226 }
227 }
228 }
229 }
230
231 func TestDisable(t *testing.T) {
232 inputs := []string{
233 `#txtpbfmt:disable
234 some{random:field}`,
235 `#a
236 #b
237
238 # txtpbfmt: disable
239 some{random:field}`,
240 }
241 for _, input := range inputs {
242 output, err := Format([]byte(input))
243 if err != nil {
244 t.Errorf("%v: %v", err, input)
245 continue
246 }
247 if diff := pretty.Compare(input, string(output)); diff != "" {
248 t.Errorf("disable ineffective (-want, +have):\n%s", diff)
249 }
250 }
251 }
252
253 func TestFormat(t *testing.T) {
254 inputs := []struct {
255 name string
256 in string
257 out string
258 }{{
259 name: "file comment + block comment",
260 in: `# file comment 1
261 # file comment 2
262
263 # presubmit comment 1
264 # presubmit comment 2
265 presubmit: {
266 # review comment 1
267 # review comment 2
268 review_notify: "address" # review same line comment 1
269
270 # extra comment block 1 line 1
271 # extra comment block 1 line 2
272
273 # extra comment block 2 line 1
274 # extra comment block 2 line 2
275 }
276 `,
277 out: `# file comment 1
278 # file comment 2
279
280 # presubmit comment 1
281 # presubmit comment 2
282 presubmit: {
283 # review comment 1
284 # review comment 2
285 review_notify: "address" # review same line comment 1
286
287 # extra comment block 1 line 1
288 # extra comment block 1 line 2
289
290 # extra comment block 2 line 1
291 # extra comment block 2 line 2
292 }
293 `}, {
294 name: "2x file comment",
295 in: `# file comment block 1 line 1
296 # file comment block 1 line 2
297
298 # file comment block 2 line 1
299 # file comment block 2 line 2
300
301 presubmit: {}
302 `,
303 out: `# file comment block 1 line 1
304 # file comment block 1 line 2
305
306 # file comment block 2 line 1
307 # file comment block 2 line 2
308
309 presubmit: {}
310 `}, {
311 name: "much blank space",
312 in: `
313
314
315 # presubmit comment 1
316 # presubmit comment 2
317 presubmit : {
318
319
320 # review comment 1
321 # review comment 2
322 review_notify : "address" # review same line comment 2
323
324
325 }
326
327
328 `,
329 out: `# presubmit comment 1
330 # presubmit comment 2
331 presubmit: {
332
333 # review comment 1
334 # review comment 2
335 review_notify: "address" # review same line comment 2
336
337 }
338
339 `}, {
340 name: "empty children",
341 in: `
342
343
344 # file comment 1
345 # file comment 2
346
347
348 # presubmit comment 1
349 # presubmit comment 2
350 presubmit: {
351 }
352
353
354 `,
355 out: `# file comment 1
356 # file comment 2
357
358 # presubmit comment 1
359 # presubmit comment 2
360 presubmit: {
361 }
362
363 `}, {
364 name: "list notation with []",
365 in: `
366 presubmit: {
367 check_tests: {
368 action: [ MAIL, REVIEW, SUBMIT ]
369 }
370 }
371 `,
372 out: `presubmit: {
373 check_tests: {
374 action: [MAIL, REVIEW, SUBMIT]
375 }
376 }
377 `}, {
378 name: "list notation with [{}] with lots of comments",
379 in: `# list comment
380 list: [
381 # first comment
382 {}, # first inline comment
383 # second comment
384 {} # second inline comment
385 # last comment
386 ] # list inline comment
387 # other comment`,
388 out: `# list comment
389 list: [
390 # first comment
391 {}, # first inline comment
392 # second comment
393 {} # second inline comment
394 # last comment
395 ] # list inline comment
396 # other comment
397 `}, {
398 name: "list notation with [{}] with inline children",
399 in: `children: [ { name: "node_2.1" } , { name: "node_2.2" },{name:"node_2.3"}]
400 `,
401 out: `children: [ { name: "node_2.1" }, { name: "node_2.2" }, { name: "node_2.3" } ]
402 `}, {
403 name: "list notation with [{}] without comma separators between multiline children",
404 in: `children: [
405 {
406 name: "node_2.1"
407 }
408 {
409 name: "node_2.2"
410 }
411 {
412 name: "node_2.3"
413 }
414 ]
415 `,
416 out: `children: [
417 {
418 name: "node_2.1"
419 },
420 {
421 name: "node_2.2"
422 },
423 {
424 name: "node_2.3"
425 }
426 ]
427 `}, {
428 name: "list notation with [{}]",
429 in: `children: [
430
431
432 # Line 1
433
434
435
436 # Line 2
437
438
439
440 # Line 3
441
442
443
444 {
445 name: "node_1"
446 },
447 {
448
449
450 name: "node_2"
451 children: [ { name: "node_2.1" }, {name:"node_2.2"},{name:"node_2.3" }]
452
453
454 },
455 {
456 name: "node_3"
457 children : [
458
459 {
460 name: "node_3.1"
461 }, # after-node comment.
462
463
464
465 # Line 1
466
467
468
469 # Line 2
470
471
472
473 {
474 name: "node_3.2",
475 },
476
477
478
479
480 {
481 name: "node_3.3"
482 }
483 ]
484 }
485 ]
486 `,
487 out: `children: [
488 # Line 1
489
490 # Line 2
491
492 # Line 3
493 {
494 name: "node_1"
495 },
496 {
497
498 name: "node_2"
499 children: [ { name: "node_2.1" }, { name: "node_2.2" }, { name: "node_2.3" } ]
500
501 },
502 {
503 name: "node_3"
504 children: [
505 {
506 name: "node_3.1"
507 }, # after-node comment.
508
509 # Line 1
510
511 # Line 2
512
513 {
514 name: "node_3.2"
515 },
516
517 {
518 name: "node_3.3"
519 }
520 ]
521 }
522 ]
523 `}, {
524 name: "multiline string",
525 in: `
526 name: "Foo"
527 description:
528 "Foo is an open-source, scalable, and efficient storage solution "
529 "for the web. It is based on MySQL—so it supports major MySQL features "
530 "like transactions, indexes, and joins—but it also provides the scalability "
531 "of NoSQL. As such, Foo offers the best of both the RDBMS and NoSQL "
532 "worlds."
533 `,
534 out: `name: "Foo"
535 description:
536 "Foo is an open-source, scalable, and efficient storage solution "
537 "for the web. It is based on MySQL—so it supports major MySQL features "
538 "like transactions, indexes, and joins—but it also provides the scalability "
539 "of NoSQL. As such, Foo offers the best of both the RDBMS and NoSQL "
540 "worlds."
541 `}, {
542 name: "escaped \"",
543 in: `
544 check_contents: {
545 required_regexp: "\\s*syntax\\s*=\\s*\".*\""
546 }
547 `,
548 out: `check_contents: {
549 required_regexp: "\\s*syntax\\s*=\\s*\".*\""
550 }
551 `}, {
552 name: "single-quote inside a double-quote-delimited string (and vice-versa)",
553 in: `
554 description:
555 "foo's fork of mod_python's Cookie submodule. "
556 "as well as \"marshalled\" cookies -- cookies that contain marshalled python "
557 'double quote " inside single-quote-delimited string'
558 `,
559 out: `description:
560 "foo's fork of mod_python's Cookie submodule. "
561 "as well as \"marshalled\" cookies -- cookies that contain marshalled python "
562 "double quote \" inside single-quote-delimited string"
563 `}, {
564 name: "list with inline comments; comments after list and after last child",
565 in: `
566 tricorder: {
567 options: {
568 build_args: [
569 # Other build_args comment.
570 # LT.IfChange
571 "first line",
572 # LT.ThenChange(//foo)
573 "--config=android_x86", # Inline comment for android_x86.
574 "--config=android_release" # Inline comment for last child.
575 # Comment after list.
576 ]
577 }
578 }
579 `,
580 out: `tricorder: {
581 options: {
582 build_args: [
583 # Other build_args comment.
584 # LT.IfChange
585 "first line",
586 # LT.ThenChange(//foo)
587 "--config=android_x86", # Inline comment for android_x86.
588 "--config=android_release" # Inline comment for last child.
589 # Comment after list.
590 ]
591 }
592 }
593 `}, {
594 name: "';' at end of value",
595 in: `name: "value";`,
596 out: `name: "value"
597 `}, {
598 name: "multi-line string with inline comments",
599 in: `# cm
600 options:
601 "first line" # first comment
602 "second line" # second comment
603 `,
604 out: `# cm
605 options:
606 "first line" # first comment
607 "second line" # second comment
608
609 `}, {
610 name: "all kinds of inline comments",
611 in: `# presubmit pre comment 1
612 # presubmit pre comment 2
613 presubmit: {
614 # review pre comment 1
615 # review pre comment 2
616 review_notify: "review_notify_value" # review inline comment
617 # comment for project
618 project: [
619 # project1 pre comment 1
620 # project1 pre comment 2
621 "project1", # project1 inline comment
622 # project2 pre comment 1
623 # project2 pre comment 2
624 "project2" # project2 inline comment
625 # after comment 1
626 # after comment 2
627 ]
628 # description pre comment 1
629 # description pre comment 2
630 description: "line1" # line1 inline comment
631 # line2 pre comment 1
632 # line2 pre comment 2
633 "line2" # line2 inline comment
634 # after comment 1
635 # after comment 2
636 name { name: value } # inline comment
637 } # inline comment
638 `,
639 out: `# presubmit pre comment 1
640 # presubmit pre comment 2
641 presubmit: {
642 # review pre comment 1
643 # review pre comment 2
644 review_notify: "review_notify_value" # review inline comment
645 # comment for project
646 project: [
647 # project1 pre comment 1
648 # project1 pre comment 2
649 "project1", # project1 inline comment
650 # project2 pre comment 1
651 # project2 pre comment 2
652 "project2" # project2 inline comment
653 # after comment 1
654 # after comment 2
655 ]
656 # description pre comment 1
657 # description pre comment 2
658 description:
659 "line1" # line1 inline comment
660 # line2 pre comment 1
661 # line2 pre comment 2
662 "line2" # line2 inline comment
663 # after comment 1
664 # after comment 2
665 name { name: value } # inline comment
666 } # inline comment
667 `}, {
668 name: "more ';'",
669 in: `list_with_semicolon: [one, two];
670 string_with_semicolon: "str one";
671 multi_line_with_semicolon: "line 1"
672 "line 2";
673 other_name: other_value`,
674 out: `list_with_semicolon: [one, two]
675 string_with_semicolon: "str one"
676 multi_line_with_semicolon:
677 "line 1"
678 "line 2"
679 other_name: other_value
680 `}, {
681 name: "keep lists",
682 in: `list_two_items_inline: [one, two];
683 list_two_items: [one,
684 two];
685 list_one_item: [one]
686 list_one_item_multiline: [
687 one]
688 list_one_item_inline_comment: [one # with inline comment
689 ]
690 list_one_item_pre_comment: [
691 # one item comment
692 one
693 ]
694 list_one_item_post_comment: [
695 one
696 # post comment
697 ]
698 list_no_item: []
699 list_no_item: [
700 ]
701 # comment
702 list_no_item_comment: [
703 # as you can see there are no items
704 ]
705 list_no_item_inline_comment: [] # Nothing here`,
706 out: `list_two_items_inline: [one, two]
707 list_two_items: [
708 one,
709 two
710 ]
711 list_one_item: [one]
712 list_one_item_multiline: [
713 one
714 ]
715 list_one_item_inline_comment: [
716 one # with inline comment
717 ]
718 list_one_item_pre_comment: [
719 # one item comment
720 one
721 ]
722 list_one_item_post_comment: [
723 one
724 # post comment
725 ]
726 list_no_item: []
727 list_no_item: [
728 ]
729 # comment
730 list_no_item_comment: [
731 # as you can see there are no items
732 ]
733 list_no_item_inline_comment: [] # Nothing here
734 `}, {
735 name: "',' as field separator",
736 in: `# cm
737
738 presubmit: {
739 path_expression: "...",
740 other: "other"
741 }`,
742 out: `# cm
743
744 presubmit: {
745 path_expression: "..."
746 other: "other"
747 }
748 `}, {
749 name: "comment between name and value",
750 in: `
751 address: "address"
752 options:
753 # LT.IfChange
754 "first line"
755 # LT.ThenChange(//foo)
756 other: OTHER
757 `,
758 out: `address: "address"
759 options:
760 # LT.IfChange
761 "first line"
762 # LT.ThenChange(//foo)
763 other: OTHER
764 `}, {
765 name: "another example of comment between name and value",
766 in: `
767 address: # comment
768 "value"
769 options: # comment
770 "line 1"
771 "line 2"
772 `,
773 out: `address:
774 # comment
775 "value"
776 options:
777 # comment
778 "line 1"
779 "line 2"
780 `}, {
781 name: "new line with spaces between comment and value",
782 in: `
783 # comment
784
785 check_tests: {
786 }
787 `,
788 out: `# comment
789 check_tests: {
790 }
791 `}, {
792 name: "proto extension",
793 in: `[foo.bar.Baz] {
794 }`,
795 out: `[foo.bar.Baz] {
796 }
797 `}, {
798 name: "multiple nested in the same line",
799 in: `
800 expr {
801 union { userset { ref { relation: "_this" } } }
802 }
803 `,
804 out: `expr {
805 union { userset { ref { relation: "_this" } } }
806 }
807 `}, {
808 name: "comment on the last line without new line at the end of the file",
809 in: `name: "value" # comment`,
810 out: `name: "value" # comment
811 `}, {
812 name: "white space inside extension name",
813 in: `[foo.Bar.
814 Baz] {
815 name: "value"
816 }
817 `,
818 out: `[foo.Bar.Baz] {
819 name: "value"
820 }
821 `}, {
822 name: "one blank line at the end",
823 in: `presubmit {
824 }
825
826 `,
827 out: `presubmit {
828 }
829
830 `}, {
831 name: "template directive",
832 in: `[ext]: {
833 offset: %offset%
834 %if (offset < 0)% %for i : offset_count%
835 # directive comment
836 %if enabled%
837
838 # innermost comment
839 # innermost comment
840
841 offset_type: PACKETS
842 %end%
843 %end% %end%
844
845 # my comment
846 # my comment
847
848 %for (leading_timestamps : leading_timestamps_array)%
849 leading_timestamps: %leading_timestamps.timestamp%
850 %end%
851 }
852 `,
853 out: `[ext]: {
854 offset: %offset%
855 %if (offset < 0)%
856 %for i : offset_count%
857 # directive comment
858 %if enabled%
859
860 # innermost comment
861 # innermost comment
862
863 offset_type: PACKETS
864 %end%
865 %end%
866 %end%
867
868 # my comment
869 # my comment
870
871 %for (leading_timestamps : leading_timestamps_array)%
872 leading_timestamps: %leading_timestamps.timestamp%
873 %end%
874 }
875 `}, {
876 name: "template directive with >",
877 in: `[ext]: {
878 offset: %offset%
879 %if (offset > 0)% %for i : offset_count%
880 # directive comment
881 %if enabled%
882
883 # innermost comment
884 # innermost comment
885
886 offset_type: PACKETS
887 %end%
888 %end% %end%
889
890 # my comment
891 # my comment
892
893 %for (leading_timestamps : leading_timestamps_array)%
894 leading_timestamps: %leading_timestamps.timestamp%
895 %end%
896 }
897 `,
898 out: `[ext]: {
899 offset: %offset%
900 %if (offset > 0)%
901 %for i : offset_count%
902 # directive comment
903 %if enabled%
904
905 # innermost comment
906 # innermost comment
907
908 offset_type: PACKETS
909 %end%
910 %end%
911 %end%
912
913 # my comment
914 # my comment
915
916 %for (leading_timestamps : leading_timestamps_array)%
917 leading_timestamps: %leading_timestamps.timestamp%
918 %end%
919 }
920 `}, {
921 name: "template directive with >=",
922 in: `[ext]: {
923 offset: %offset%
924 %if (offset >= 0)% %for i : offset_count%
925 # directive comment
926 %if enabled%
927
928 # innermost comment
929 # innermost comment
930
931 offset_type: PACKETS
932 %end%
933 %end% %end%
934
935 # my comment
936 # my comment
937
938 %for (leading_timestamps : leading_timestamps_array)%
939 leading_timestamps: %leading_timestamps.timestamp%
940 %end%
941 }
942 `,
943 out: `[ext]: {
944 offset: %offset%
945 %if (offset >= 0)%
946 %for i : offset_count%
947 # directive comment
948 %if enabled%
949
950 # innermost comment
951 # innermost comment
952
953 offset_type: PACKETS
954 %end%
955 %end%
956 %end%
957
958 # my comment
959 # my comment
960
961 %for (leading_timestamps : leading_timestamps_array)%
962 leading_timestamps: %leading_timestamps.timestamp%
963 %end%
964 }
965 `}, {
966 name: "template directive escaped",
967 in: `node {
968 name: %"value \"% value"%
969 }
970 `,
971 out: `node {
972 name: %"value \"% value"%
973 }
974 `}, {
975 name: "no_directives (as opposed to next tests)",
976 in: `presubmit: { check_presubmit_service: { address: "address" failure_status: WARNING options: "options" } }
977 `,
978 out: `presubmit: { check_presubmit_service: { address: "address" failure_status: WARNING options: "options" } }
979 `}, {
980 name: "expand_all_children",
981 in: `# txtpbfmt: expand_all_children
982 presubmit: { check_presubmit_service: { address: "address" failure_status: WARNING options: "options" } }
983 `,
984 out: `# txtpbfmt: expand_all_children
985 presubmit: {
986 check_presubmit_service: {
987 address: "address"
988 failure_status: WARNING
989 options: "options"
990 }
991 }
992 `}, {
993 name: "skip_all_colons",
994 in: `# txtpbfmt: skip_all_colons
995 presubmit: { check_presubmit_service: { address: "address" failure_status: WARNING options: "options" } }
996 `,
997 out: `# txtpbfmt: skip_all_colons
998 presubmit { check_presubmit_service { address: "address" failure_status: WARNING options: "options" } }
999 `}, {
1000 name: "separate_directives",
1001 in: `# txtpbfmt: expand_all_children
1002 # txtpbfmt: skip_all_colons
1003 presubmit: { check_presubmit_service: { address: "address" failure_status: WARNING options: "options" } }
1004 `,
1005 out: `# txtpbfmt: expand_all_children
1006 # txtpbfmt: skip_all_colons
1007 presubmit {
1008 check_presubmit_service {
1009 address: "address"
1010 failure_status: WARNING
1011 options: "options"
1012 }
1013 }
1014 `}, {
1015 name: "combined_directives",
1016 in: `# txtpbfmt: expand_all_children, skip_all_colons
1017 presubmit: { check_presubmit_service: { address: "address" failure_status: WARNING options: "options" } }
1018 `,
1019 out: `# txtpbfmt: expand_all_children, skip_all_colons
1020 presubmit {
1021 check_presubmit_service {
1022 address: "address"
1023 failure_status: WARNING
1024 options: "options"
1025 }
1026 }
1027 `}, {
1028 name: "preserve angle brackets",
1029 in: `# txtpbfmt: preserve_angle_brackets
1030 foo <
1031 a: 1
1032 >
1033 foo {
1034 b: 2
1035 }
1036 `,
1037 out: `# txtpbfmt: preserve_angle_brackets
1038 foo <
1039 a: 1
1040 >
1041 foo {
1042 b: 2
1043 }
1044 `,
1045 }, {
1046 name: "repeated proto format",
1047 in: `{
1048 a: 1;
1049 b: 2}
1050 { a: 1;
1051 b: 4
1052 nested: {
1053 # nested_string is optional
1054 nested_string: "foo"
1055 }
1056 }
1057 { a: 1;
1058 b:4,
1059 c: 5}`,
1060 out: `{
1061 a: 1
1062 b: 2
1063 }
1064 {
1065 a: 1
1066 b: 4
1067 nested: {
1068 # nested_string is optional
1069 nested_string: "foo"
1070 }
1071 }
1072 {
1073 a: 1
1074 b: 4
1075 c: 5
1076 }
1077 `}, {
1078 name: "repeated proto format with short messages",
1079 in: `{ a: 1}
1080 { a: 2 }
1081 { a: 3}`,
1082 out: `{ a: 1 }
1083 { a: 2 }
1084 { a: 3 }
1085 `}, {
1086 name: "allow unnamed nodes everywhere",
1087 in: `
1088 # txtpbfmt: allow_unnamed_nodes_everywhere
1089 mapping {{
1090 my_proto_field: "foo"
1091 }}`,
1092 out: `# txtpbfmt: allow_unnamed_nodes_everywhere
1093 mapping {
1094 {
1095 my_proto_field: "foo"
1096 }
1097 }
1098 `}, {
1099 name: "sort fields and values",
1100 in: `# txtpbfmt: sort_fields_by_field_name
1101 # txtpbfmt: sort_repeated_fields_by_content
1102 presubmit: {
1103 auto_reviewers: "reviewerB"
1104 check_contents: {
1105 # Should go after ADD
1106 operation: EDIT
1107 operation: ADD
1108 prohibited_regexp: "UnsafeFunction"
1109 check_delta_only: true
1110 }
1111 # Should go before reviewerB
1112 auto_reviewers: "reviewerA"
1113 }
1114 `,
1115 out: `# txtpbfmt: sort_fields_by_field_name
1116 # txtpbfmt: sort_repeated_fields_by_content
1117 presubmit: {
1118 # Should go before reviewerB
1119 auto_reviewers: "reviewerA"
1120 auto_reviewers: "reviewerB"
1121 check_contents: {
1122 check_delta_only: true
1123 operation: ADD
1124 # Should go after ADD
1125 operation: EDIT
1126 prohibited_regexp: "UnsafeFunction"
1127 }
1128 }
1129 `}, {
1130 name: "sort by subfield values",
1131 in: `# txtpbfmt: sort_repeated_fields_by_subfield=operation.name
1132 # txtpbfmt: sort_repeated_fields_by_subfield=test.id
1133 presubmit: {
1134 operation {
1135 name: EDIT
1136 }
1137 operation {
1138 name: ADD
1139 }
1140 test {
1141 id: 4
1142 }
1143 test {
1144 id: 2
1145 }
1146 }
1147 `,
1148 out: `# txtpbfmt: sort_repeated_fields_by_subfield=operation.name
1149 # txtpbfmt: sort_repeated_fields_by_subfield=test.id
1150 presubmit: {
1151 operation {
1152 name: ADD
1153 }
1154 operation {
1155 name: EDIT
1156 }
1157 test {
1158 id: 2
1159 }
1160 test {
1161 id: 4
1162 }
1163 }
1164 `}, {
1165
1166
1167 name: "sort by multiple subfield values",
1168 in: `# txtpbfmt: sort_repeated_fields_by_subfield=operation.name
1169 # txtpbfmt: sort_repeated_fields_by_subfield=test.id
1170 # txtpbfmt: sort_repeated_fields_by_subfield=test.type
1171 # txtpbfmt: sort_repeated_fields_by_subfield=test.name
1172 presubmit: {
1173 operation {
1174 name: EDIT
1175 }
1176 operation {
1177 name: ADD
1178 }
1179 test {
1180 id: 4
1181 name: bar
1182 unrelated_field: 1
1183 type: type_1
1184 }
1185 test {
1186 id: 2
1187 name: foo
1188 unrelated_field: 3
1189 type: type_2
1190 }
1191 test {
1192 id: 2
1193 name: baz
1194 unrelated_field: 2
1195 type: type_1
1196 }
1197 test {
1198 id: 2
1199 name: bar
1200 unrelated_field: 1
1201 type: type_2
1202 }
1203 }
1204 `,
1205 out: `# txtpbfmt: sort_repeated_fields_by_subfield=operation.name
1206 # txtpbfmt: sort_repeated_fields_by_subfield=test.id
1207 # txtpbfmt: sort_repeated_fields_by_subfield=test.type
1208 # txtpbfmt: sort_repeated_fields_by_subfield=test.name
1209 presubmit: {
1210 operation {
1211 name: ADD
1212 }
1213 operation {
1214 name: EDIT
1215 }
1216 test {
1217 id: 2
1218 name: baz
1219 unrelated_field: 2
1220 type: type_1
1221 }
1222 test {
1223 id: 2
1224 name: bar
1225 unrelated_field: 1
1226 type: type_2
1227 }
1228 test {
1229 id: 2
1230 name: foo
1231 unrelated_field: 3
1232 type: type_2
1233 }
1234 test {
1235 id: 4
1236 name: bar
1237 unrelated_field: 1
1238 type: type_1
1239 }
1240 }
1241 `}, {
1242 name: "sort and remove duplicates",
1243 in: `# txtpbfmt: sort_fields_by_field_name
1244 # txtpbfmt: sort_repeated_fields_by_content
1245 # txtpbfmt: remove_duplicate_values_for_repeated_fields
1246 presubmit: {
1247 auto_reviewers: "reviewerB"
1248 # Should go before reviewerB
1249 auto_reviewers: "reviewerA"
1250 check_contents: {
1251 operation: EDIT
1252 operation: ADD
1253 # Should be removed
1254 operation: EDIT
1255 prohibited_regexp: "UnsafeFunction"
1256 # Should go before operation: ADD
1257 check_delta_only: true
1258 }
1259 # Should be removed
1260 auto_reviewers: "reviewerA"
1261 }
1262 `,
1263 out: `# txtpbfmt: sort_fields_by_field_name
1264 # txtpbfmt: sort_repeated_fields_by_content
1265 # txtpbfmt: remove_duplicate_values_for_repeated_fields
1266 presubmit: {
1267 # Should go before reviewerB
1268 auto_reviewers: "reviewerA"
1269 auto_reviewers: "reviewerB"
1270 check_contents: {
1271 # Should go before operation: ADD
1272 check_delta_only: true
1273 operation: ADD
1274 operation: EDIT
1275 prohibited_regexp: "UnsafeFunction"
1276 }
1277 }
1278 `}, {
1279 name: "multiple groups of repeated fields",
1280 in: `# txtpbfmt: sort_repeated_fields_by_content
1281 # txtpbfmt: sort_repeated_fields_by_subfield=id
1282
1283 # field b
1284 field: "b"
1285
1286 # field a
1287 field: "a"
1288 message: { id: "b" }
1289 message: { id: "a" }
1290
1291 # new group
1292
1293 # field b
1294 field: "b"
1295
1296 # field a
1297 field: "a"
1298 message: { id: "b" }
1299 message: { id: "a" }
1300 `,
1301 out: `# txtpbfmt: sort_repeated_fields_by_content
1302 # txtpbfmt: sort_repeated_fields_by_subfield=id
1303
1304 # field a
1305 field: "a"
1306
1307 # field b
1308 field: "b"
1309 message: { id: "a" }
1310 message: { id: "b" }
1311
1312 # new group
1313
1314 # field a
1315 field: "a"
1316
1317 # field b
1318 field: "b"
1319 message: { id: "a" }
1320 message: { id: "b" }
1321 `}, {
1322 name: "trailing comma / semicolon",
1323 in: `dict: {
1324 arg: {
1325 key: "first_value"
1326 value: { num: 0 }
1327 },
1328 arg: {
1329 key: "second_value"
1330 value: { num: 1 }
1331 };
1332 }
1333 `,
1334 out: `dict: {
1335 arg: {
1336 key: "first_value"
1337 value: { num: 0 }
1338 }
1339 arg: {
1340 key: "second_value"
1341 value: { num: 1 }
1342 }
1343 }
1344 `}}
1345 for _, input := range inputs {
1346 out, err := Format([]byte(input.in))
1347 if err != nil {
1348 t.Errorf("Format[%s] %v returned err %v", input.name, input.in, err)
1349 continue
1350 }
1351 if diff := diff.Diff(input.out, string(out)); diff != "" {
1352 nodes, err := Parse([]byte(input.in))
1353 if err != nil {
1354 t.Errorf("Parse[%s] %v returned err %v", input.name, input.in, err)
1355 continue
1356 }
1357 t.Errorf("Format[%s](\n%s\n)\nparsed tree\n%s\n\nreturned diff (-want, +got):\n%s", input.name, input.in, DebugFormat(nodes, 0), diff)
1358 }
1359 }
1360 }
1361
1362 func TestParserConfigs(t *testing.T) {
1363 inputs := []struct {
1364 name string
1365 in string
1366 config Config
1367 out string
1368 wantErr string
1369 }{{
1370 name: "AlreadyExpandedConfigOff",
1371 in: `presubmit: {
1372 check_presubmit_service: {
1373 address: "address"
1374 failure_status: WARNING
1375 options: "options"
1376 }
1377 }
1378 `,
1379 config: Config{ExpandAllChildren: false},
1380 out: `presubmit: {
1381 check_presubmit_service: {
1382 address: "address"
1383 failure_status: WARNING
1384 options: "options"
1385 }
1386 }
1387 `,
1388 }, {
1389 name: "AlreadyExpandedConfigOn",
1390 in: `presubmit: {
1391 check_presubmit_service: {
1392 address: "address"
1393 failure_status: WARNING
1394 options: "options"
1395 }
1396 }
1397 `,
1398 config: Config{ExpandAllChildren: true},
1399 out: `presubmit: {
1400 check_presubmit_service: {
1401 address: "address"
1402 failure_status: WARNING
1403 options: "options"
1404 }
1405 }
1406 `,
1407 }, {
1408 name: "NotExpandedOnFix",
1409 in: `presubmit: { check_presubmit_service: { address: "address" failure_status: WARNING options: "options" } }
1410 `,
1411 config: Config{ExpandAllChildren: false},
1412 out: `presubmit: { check_presubmit_service: { address: "address" failure_status: WARNING options: "options" } }
1413 `,
1414 }, {
1415 name: "ExpandedOnFix",
1416 in: `presubmit: { check_presubmit_service: { address: "address" failure_status: WARNING options: "options" } }
1417 `,
1418 config: Config{ExpandAllChildren: true},
1419 out: `presubmit: {
1420 check_presubmit_service: {
1421 address: "address"
1422 failure_status: WARNING
1423 options: "options"
1424 }
1425 }
1426 `,
1427 }, {
1428 name: "SortFieldNames",
1429 in: `presubmit: {
1430 auto_reviewers: "reviewerB"
1431 check_contents: {
1432 operation: EDIT
1433 operation: ADD
1434 prohibited_regexp: "UnsafeFunction"
1435 check_delta_only: true
1436 }
1437 # Should remain below reviewerB
1438 auto_reviewers: "reviewerA"
1439 }
1440 `,
1441 config: Config{SortFieldsByFieldName: true},
1442 out: `presubmit: {
1443 auto_reviewers: "reviewerB"
1444 # Should remain below reviewerB
1445 auto_reviewers: "reviewerA"
1446 check_contents: {
1447 check_delta_only: true
1448 operation: EDIT
1449 operation: ADD
1450 prohibited_regexp: "UnsafeFunction"
1451 }
1452 }
1453 `,
1454 }, {
1455 name: "SortFieldContents",
1456 in: `presubmit: {
1457 auto_reviewers: "reviewerB"
1458 check_contents: {
1459 # Should go after ADD
1460 operation: EDIT
1461 operation: ADD
1462 prohibited_regexp: "UnsafeFunction"
1463 check_delta_only: true
1464 }
1465 # Should remain below
1466 auto_reviewers: "reviewerA"
1467 }
1468 `,
1469 config: Config{SortRepeatedFieldsByContent: true},
1470 out: `presubmit: {
1471 auto_reviewers: "reviewerB"
1472 check_contents: {
1473 operation: ADD
1474 # Should go after ADD
1475 operation: EDIT
1476 prohibited_regexp: "UnsafeFunction"
1477 check_delta_only: true
1478 }
1479 # Should remain below
1480 auto_reviewers: "reviewerA"
1481 }
1482 `,
1483 }, {
1484 name: "SortNamedFieldBySubfieldContents",
1485 in: `presubmit: {
1486 auto_reviewers: "reviewerB"
1487 check_contents: {
1488 # Should go after ADD
1489 operation: {
1490 name: EDIT
1491 }
1492 operation: {
1493 name: ADD
1494 }
1495 prohibited_regexp: "UnsafeFunction"
1496 check_delta_only: true
1497 }
1498 # Should remain below
1499 auto_reviewers: "reviewerA"
1500 }
1501 `,
1502 config: Config{SortRepeatedFieldsBySubfield: []string{"operation.name"}},
1503 out: `presubmit: {
1504 auto_reviewers: "reviewerB"
1505 check_contents: {
1506 operation: {
1507 name: ADD
1508 }
1509 # Should go after ADD
1510 operation: {
1511 name: EDIT
1512 }
1513 prohibited_regexp: "UnsafeFunction"
1514 check_delta_only: true
1515 }
1516 # Should remain below
1517 auto_reviewers: "reviewerA"
1518 }
1519 `,
1520 }, {
1521 name: "SortNamedFieldByMultipleSubfieldContents",
1522 in: `presubmit: {
1523 operation {
1524 name: EDIT
1525 }
1526 operation {
1527 name: ADD
1528 }
1529 test {
1530 id: 4
1531 }
1532 test {
1533 id: 2
1534 }
1535 }
1536 `,
1537 config: Config{SortRepeatedFieldsBySubfield: []string{"operation.name", "test.id"}},
1538 out: `presubmit: {
1539 operation {
1540 name: ADD
1541 }
1542 operation {
1543 name: EDIT
1544 }
1545 test {
1546 id: 2
1547 }
1548 test {
1549 id: 4
1550 }
1551 }
1552 `,
1553 }, {
1554 name: "SortAnyFieldBySubfieldContents",
1555 in: `presubmit: {
1556 auto_reviewers: "reviewerB"
1557 check_contents: {
1558 # Should go after ADD
1559 operation: {
1560 name: EDIT
1561 }
1562 operation: {
1563 name: ADD
1564 }
1565 prohibited_regexp: "UnsafeFunction"
1566 check_delta_only: true
1567 }
1568 # Should remain below
1569 auto_reviewers: "reviewerA"
1570 }
1571 `,
1572 config: Config{SortRepeatedFieldsBySubfield: []string{"name"}},
1573 out: `presubmit: {
1574 auto_reviewers: "reviewerB"
1575 check_contents: {
1576 operation: {
1577 name: ADD
1578 }
1579 # Should go after ADD
1580 operation: {
1581 name: EDIT
1582 }
1583 prohibited_regexp: "UnsafeFunction"
1584 check_delta_only: true
1585 }
1586 # Should remain below
1587 auto_reviewers: "reviewerA"
1588 }
1589 `,
1590 }, {
1591 name: "SortBySubfieldsDontSortFieldsWithDifferentNames",
1592 in: `presubmit: {
1593 check_contents: {
1594 operation1: {
1595 name: EDIT
1596 }
1597 operation2: {
1598 name: ADD
1599 }
1600 }
1601 }
1602 `,
1603 config: Config{SortRepeatedFieldsBySubfield: []string{"name"}},
1604 out: `presubmit: {
1605 check_contents: {
1606 operation1: {
1607 name: EDIT
1608 }
1609 operation2: {
1610 name: ADD
1611 }
1612 }
1613 }
1614 `,
1615 }, {
1616 name: "SortSeparatedFieldBySubfieldContents",
1617 in: `presubmit: {
1618 auto_reviewers: "reviewerB"
1619 check_contents: {
1620 # Should go after ADD
1621 operation: {
1622 name: EDIT
1623 }
1624 split: 1
1625 operation: {
1626 name: ADD
1627 }
1628 }
1629 # Should move above
1630 auto_reviewers: "reviewerA"
1631 }
1632 `,
1633 config: Config{SortFieldsByFieldName: true, SortRepeatedFieldsBySubfield: []string{"name"}},
1634 out: `presubmit: {
1635 auto_reviewers: "reviewerB"
1636 # Should move above
1637 auto_reviewers: "reviewerA"
1638 check_contents: {
1639 operation: {
1640 name: ADD
1641 }
1642 # Should go after ADD
1643 operation: {
1644 name: EDIT
1645 }
1646 split: 1
1647 }
1648 }
1649 `,
1650 }, {
1651 name: "SortSubfieldsIgnoreEmptySubfieldName",
1652 in: `presubmit: {
1653 auto_reviewers: "reviewerB"
1654 check_contents: {
1655 operation: {
1656 name: EDIT
1657 }
1658 operation: {
1659 name: ADD
1660 }
1661 }
1662 auto_reviewers: "reviewerA"
1663 }
1664 `,
1665 config: Config{SortRepeatedFieldsBySubfield: []string{"operation."}},
1666 out: `presubmit: {
1667 auto_reviewers: "reviewerB"
1668 check_contents: {
1669 operation: {
1670 name: EDIT
1671 }
1672 operation: {
1673 name: ADD
1674 }
1675 }
1676 auto_reviewers: "reviewerA"
1677 }
1678 `,
1679 }, {
1680 name: "SortFieldNamesAndContents",
1681 in: `presubmit: {
1682 auto_reviewers: "reviewerB"
1683 check_contents: {
1684 # Should go after ADD
1685 operation: EDIT
1686 operation: ADD
1687 prohibited_regexp: "UnsafeFunction"
1688 check_delta_only: true
1689 }
1690 # Should go before reviewerB
1691 auto_reviewers: "reviewerA"
1692 }
1693 `,
1694 config: Config{SortFieldsByFieldName: true, SortRepeatedFieldsByContent: true},
1695 out: `presubmit: {
1696 # Should go before reviewerB
1697 auto_reviewers: "reviewerA"
1698 auto_reviewers: "reviewerB"
1699 check_contents: {
1700 check_delta_only: true
1701 operation: ADD
1702 # Should go after ADD
1703 operation: EDIT
1704 prohibited_regexp: "UnsafeFunction"
1705 }
1706 }
1707 `,
1708 }, {
1709 name: "SortBySpecifiedFieldOrder",
1710 in: `
1711 below_wrapper: true
1712 wrapper: {
1713 unmoved_not_in_config: "foo"
1714 check_contents: {
1715 # Not really at the top; attached to x_third
1716 x_third: "3"
1717 # attached to EDIT
1718 z_first: EDIT
1719 unknown_bubbles_to_top: true
1720 x_second: true
1721 z_first: ADD
1722 also_unknown_bubbles_to_top: true
1723 # Trailing comment is on different node; should not confuse ordering logic.
1724 # These always sort below fields in the sorting config, and thus stay at bottom.
1725 }
1726 # Should also not move
1727 unmoved_not_in_config: "bar"
1728 }
1729 `,
1730 config: Config{
1731 fieldSortOrder: map[string][]string{
1732 RootName: {"wrapper", "below_wrapper"},
1733 "check_contents": {"z_first", "x_second", "x_third"},
1734 },
1735 },
1736
1737 out: `wrapper: {
1738 unmoved_not_in_config: "foo"
1739 check_contents: {
1740 unknown_bubbles_to_top: true
1741 also_unknown_bubbles_to_top: true
1742 # attached to EDIT
1743 z_first: EDIT
1744 z_first: ADD
1745 x_second: true
1746 # Not really at the top; attached to x_third
1747 x_third: "3"
1748 # Trailing comment is on different node; should not confuse ordering logic.
1749 # These always sort below fields in the sorting config, and thus stay at bottom.
1750 }
1751 # Should also not move
1752 unmoved_not_in_config: "bar"
1753 }
1754 below_wrapper: true
1755 `,
1756 }, {
1757 name: "SortBySpecifiedFieldOrderAndNameAndValue",
1758 in: `presubmit: {
1759 # attached to bar
1760 sort_by_name_and_value: "bar"
1761 check_contents: {
1762 x_third: "3"
1763 # attached to EDIT
1764 z_first: EDIT
1765 unknown_bubbles_to_top: true
1766 x_second: true
1767 z_first: ADD
1768 also_unknown_bubbles_to_top: true
1769 # The nested check_contents bubbles to the top, since it's not in the fieldSortOrder.
1770 check_contents: {
1771 x_second: true
1772 z_first: ADD
1773 }
1774 }
1775 sort_by_name_and_value: "foo"
1776 }
1777 `,
1778 config: Config{
1779 fieldSortOrder: map[string][]string{
1780 "check_contents": {"z_first", "x_second", "x_third", "not_required"},
1781 },
1782 SortFieldsByFieldName: true,
1783 SortRepeatedFieldsByContent: true,
1784 },
1785
1786
1787 out: `presubmit: {
1788 check_contents: {
1789 also_unknown_bubbles_to_top: true
1790 # The nested check_contents bubbles to the top, since it's not in the fieldSortOrder.
1791 check_contents: {
1792 z_first: ADD
1793 x_second: true
1794 }
1795 unknown_bubbles_to_top: true
1796 z_first: ADD
1797 # attached to EDIT
1798 z_first: EDIT
1799 x_second: true
1800 x_third: "3"
1801 }
1802 # attached to bar
1803 sort_by_name_and_value: "bar"
1804 sort_by_name_and_value: "foo"
1805 }
1806 `,
1807 }, {
1808 name: "SortBySpecifiedFieldOrderErrorHandling",
1809 in: `presubmit: {
1810 node_not_in_config_will_not_trigger_error: true
1811 check_contents: {
1812 x_third: "3"
1813 z_first: EDIT
1814 unknown_field_triggers_error: true
1815 x_second: true
1816 z_first: ADD
1817 }
1818 }
1819 `,
1820 config: Config{
1821 fieldSortOrder: map[string][]string{
1822 "check_contents": {"z_first", "x_second", "x_third"},
1823 },
1824 RequireFieldSortOrderToMatchAllFieldsInNode: true,
1825 },
1826 wantErr: `parent field: "check_contents", unsorted field: "unknown_field_triggers_error"`,
1827 }, {
1828 name: "RemoveRepeats",
1829 in: `presubmit: {
1830 auto_reviewers: "reviewerB"
1831 auto_reviewers: "reviewerA"
1832 check_contents: {
1833 operation: EDIT
1834 operation: ADD
1835 # Should be removed
1836 operation: EDIT
1837 prohibited_regexp: "UnsafeFunction"
1838 check_delta_only: true
1839 }
1840 # Should be removed
1841 auto_reviewers: "reviewerA"
1842 }
1843 `,
1844 config: Config{RemoveDuplicateValuesForRepeatedFields: true},
1845 out: `presubmit: {
1846 auto_reviewers: "reviewerB"
1847 auto_reviewers: "reviewerA"
1848 check_contents: {
1849 operation: EDIT
1850 operation: ADD
1851 prohibited_regexp: "UnsafeFunction"
1852 check_delta_only: true
1853 }
1854 }
1855 `,
1856 }, {
1857 name: "SortEverythingAndRemoveRepeats",
1858 in: `presubmit: {
1859 auto_reviewers: "reviewerB"
1860 # Should go before reviewerB
1861 auto_reviewers: "reviewerA"
1862 check_contents: {
1863 operation: EDIT
1864 operation: ADD
1865 # Should be removed
1866 operation: EDIT
1867 prohibited_regexp: "UnsafeFunction"
1868 # Should go before operation: ADD
1869 check_delta_only: true
1870 }
1871 # Should be removed
1872 auto_reviewers: "reviewerA"
1873 }
1874 `,
1875 config: Config{
1876 SortFieldsByFieldName: true,
1877 SortRepeatedFieldsByContent: true,
1878 RemoveDuplicateValuesForRepeatedFields: true},
1879 out: `presubmit: {
1880 # Should go before reviewerB
1881 auto_reviewers: "reviewerA"
1882 auto_reviewers: "reviewerB"
1883 check_contents: {
1884 # Should go before operation: ADD
1885 check_delta_only: true
1886 operation: ADD
1887 operation: EDIT
1888 prohibited_regexp: "UnsafeFunction"
1889 }
1890 }
1891 `,
1892 }, {
1893 name: "TripleQuotedStrings",
1894 config: Config{
1895 AllowTripleQuotedStrings: true,
1896 },
1897 in: `foo: """bar"""
1898 `,
1899 out: `foo: """bar"""
1900 `,
1901 }, {
1902 name: "TripleQuotedStrings_multiLine",
1903 config: Config{
1904 AllowTripleQuotedStrings: true,
1905 },
1906 in: `foo: """
1907 bar
1908 """
1909 `,
1910 out: `foo: """
1911 bar
1912 """
1913 `,
1914 }, {
1915 name: "TripleQuotedStrings_singleQuotes",
1916 config: Config{
1917 AllowTripleQuotedStrings: true,
1918 },
1919 in: `foo: '''
1920 bar
1921 '''
1922 `,
1923 out: `foo: '''
1924 bar
1925 '''
1926 `,
1927 }, {
1928 name: "TripleQuotedStrings_brackets",
1929 config: Config{
1930 AllowTripleQuotedStrings: true,
1931 },
1932 in: `s: """ "}" """
1933 `,
1934 out: `s: """ "}" """
1935 `,
1936 }, {
1937 name: "TripleQuotedStrings_metaComment",
1938 in: `# txtpbfmt: allow_triple_quoted_strings
1939 foo: """
1940 bar
1941 """
1942 `,
1943 out: `# txtpbfmt: allow_triple_quoted_strings
1944 foo: """
1945 bar
1946 """
1947 `,
1948 }, {
1949 name: "WrapStringsAtColumn",
1950 config: Config{
1951 WrapStringsAtColumn: 15,
1952 },
1953 in: `# Comments are not wrapped
1954 s: "one two three four five"
1955 `,
1956 out: `# Comments are not wrapped
1957 s:
1958 "one two "
1959 "three four "
1960 "five"
1961 `,
1962 }, {
1963 name: "WrapStringsAtColumn_inlineChildren",
1964 config: Config{
1965 WrapStringsAtColumn: 14,
1966 },
1967 in: `root {
1968 # inline children don't wrap
1969 inner { inline: "89 1234" }
1970 # Verify that skipping an inline string doesn't skip the rest of the file.
1971 wrappable {
1972 s: "will wrap"
1973 }
1974 }
1975 `,
1976 out: `root {
1977 # inline children don't wrap
1978 inner { inline: "89 1234" }
1979 # Verify that skipping an inline string doesn't skip the rest of the file.
1980 wrappable {
1981 s:
1982 "will "
1983 "wrap"
1984 }
1985 }
1986 `,
1987 }, {
1988 name: "WrapStringsAtColumn_exactlyNumColumnsDoesNotWrap",
1989 config: Config{
1990 WrapStringsAtColumn: 14,
1991 },
1992 in: `root {
1993 inner {
1994 s: "89 123"
1995 }
1996 }
1997 `,
1998 out: `root {
1999 inner {
2000 s: "89 123"
2001 }
2002 }
2003 `,
2004 }, {
2005 name: "WrapStringsAtColumn_numColumnsPlus1Wraps",
2006 config: Config{
2007 WrapStringsAtColumn: 14,
2008 },
2009 in: `root {
2010 inner {
2011 s:
2012 "89 123 "
2013 "123 56"
2014 }
2015 }
2016 `,
2017 out: `root {
2018 inner {
2019 s:
2020 "89 "
2021 "123 "
2022 "123 "
2023 "56"
2024 }
2025 }
2026 `,
2027 }, {
2028 name: "WrapStringsAtColumn_commentKeptWhenLinesReduced",
2029 config: Config{
2030 WrapStringsAtColumn: 15,
2031 },
2032 in: `root {
2033 label:
2034 # before
2035 "56789 next-line" # trailing
2036 label:
2037 # inside top
2038 "56789 next-line " # trailing line1
2039 "v "
2040 "v "
2041 "v "
2042 "v "
2043 # inside in-between
2044 "straggler" # trailing line2
2045 # next-node comment
2046 }
2047 `,
2048 out: `root {
2049 label:
2050 # before
2051 "56789 " # trailing
2052 "next-line"
2053 label:
2054 # inside top
2055 "56789 " # trailing line1
2056 "next-line "
2057 "v v v v "
2058 "straggler"
2059 # inside in-between
2060 # trailing line2
2061 # next-node comment
2062 }
2063 `,
2064 }, {
2065 name: "WrapStringsAtColumn_doNotBreakLongWords",
2066 config: Config{
2067 WrapStringsAtColumn: 15,
2068 },
2069 in: `s: "one@two_three-four&five"
2070 `,
2071 out: `s: "one@two_three-four&five"
2072 `,
2073 }, {
2074 name: "WrapStringsAtColumn_wrapHtml",
2075 config: Config{
2076 WrapStringsAtColumn: 15,
2077 WrapHTMLStrings: true,
2078 },
2079 in: `s: "one two three <four>"
2080 `,
2081 out: `s:
2082 "one two "
2083 "three "
2084 "<four>"
2085 `,
2086 }, {
2087 name: "WrapStringsAtColumn_empty",
2088 config: Config{
2089 WrapStringsAtColumn: 15,
2090 },
2091 in: `s:
2092 `,
2093 out: `s:
2094 `,
2095 }, {
2096 name: "WrapStringsAtColumn_doNoWrapHtmlByDefault",
2097 config: Config{
2098 WrapStringsAtColumn: 15,
2099 },
2100 in: `s: "one two three <four>"
2101 `,
2102 out: `s: "one two three <four>"
2103 `,
2104 }, {
2105 name: "WrapStringsAtColumn_metaComment",
2106 in: `# txtpbfmt: wrap_strings_at_column=15
2107 # txtpbfmt: wrap_html_strings
2108 s: "one two three <four>"
2109 `,
2110 out: `# txtpbfmt: wrap_strings_at_column=15
2111 # txtpbfmt: wrap_html_strings
2112 s:
2113 "one two "
2114 "three "
2115 "<four>"
2116 `,
2117 }, {
2118 name: "WrapStringsAtColumn_doNotWrapNonStrings",
2119 config: Config{
2120 WrapStringsAtColumn: 15,
2121 },
2122 in: `e: VERY_LONG_ENUM_VALUE
2123 i: 12345678901234567890
2124 r: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
2125 `,
2126 out: `e: VERY_LONG_ENUM_VALUE
2127 i: 12345678901234567890
2128 r: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
2129 `,
2130 }, {
2131 name: "WrapStringsAtColumn_alreadyWrappedStringsAreNotRewrapped",
2132 config: Config{
2133 WrapStringsAtColumn: 15,
2134 },
2135
2136 in: `s:
2137 "I "
2138 "am already "
2139 "wrapped"
2140 `,
2141 out: `s:
2142 "I "
2143 "am already "
2144 "wrapped"
2145 `,
2146 }, {
2147 name: "WrapStringsAtColumn_alreadyWrappedStringsAreNotRewrappedUnlessSomeAreLonger",
2148 config: Config{
2149 WrapStringsAtColumn: 15,
2150 },
2151 in: `s:
2152 "I "
2153 "am already "
2154 "wrapped"
2155 " but I am not!"
2156 `,
2157 out: `s:
2158 "I am "
2159 "already "
2160 "wrapped "
2161 "but I am "
2162 "not!"
2163 `,
2164 }, {
2165 name: "WrapStringsAtColumn_tripleQuotedStringsAreNotWrapped",
2166 config: Config{
2167 WrapStringsAtColumn: 15,
2168 AllowTripleQuotedStrings: true,
2169 },
2170 in: `s1: """one two three four five"""
2171 s2: '''six seven eight nine'''
2172 `,
2173 out: `s1: """one two three four five"""
2174 s2: '''six seven eight nine'''
2175 `,
2176 }, {
2177 name: "WrapStringsAfterNewlines",
2178 config: Config{
2179 WrapStringsAfterNewlines: true,
2180 },
2181 in: `# Comments are not \n wrapped
2182 s: "one two \nthree four\nfive"
2183 `,
2184 out: `# Comments are not \n wrapped
2185 s:
2186 "one two \n"
2187 "three four\n"
2188 "five"
2189 `,
2190 }, {
2191 name: "WrapStringsAfterNewlines_motivatingExampleWithMarkup",
2192 config: Config{
2193 WrapStringsAfterNewlines: true,
2194 },
2195 in: `root {
2196 doc: "<body>\n <p>\n Hello\n </p>\n</body>\n"
2197 }
2198 `,
2199 out: `root {
2200 doc:
2201 "<body>\n"
2202 " <p>\n"
2203 " Hello\n"
2204 " </p>\n"
2205 "</body>\n"
2206 }
2207 `,
2208 }, {
2209 name: "WrapStringsAfterNewlines_inlineChildren",
2210 config: Config{
2211 WrapStringsAfterNewlines: true,
2212 },
2213 in: `root {
2214 # inline children don't wrap
2215 inner { inline: "89 1234" }
2216 # Verify that skipping an inline string doesn't skip the rest of the file.
2217 wrappable {
2218 s: "will \nwrap"
2219 }
2220 }
2221 `,
2222 out: `root {
2223 # inline children don't wrap
2224 inner { inline: "89 1234" }
2225 # Verify that skipping an inline string doesn't skip the rest of the file.
2226 wrappable {
2227 s:
2228 "will \n"
2229 "wrap"
2230 }
2231 }
2232 `,
2233 }, {
2234 name: "WrapStringsAfterNewlines_noNewlineDoesNotWrap",
2235 config: Config{
2236 WrapStringsAfterNewlines: true,
2237 },
2238 in: `root {
2239 inner {
2240 s: "89 123"
2241 }
2242 }
2243 `,
2244 out: `root {
2245 inner {
2246 s: "89 123"
2247 }
2248 }
2249 `,
2250 }, {
2251 name: "WrapStringsAfterNewlines_trailingNewlineDoesNotWrap",
2252 config: Config{
2253 WrapStringsAfterNewlines: true,
2254 },
2255 in: `root {
2256 s: "89 123\n"
2257 }
2258 `,
2259 out: `root {
2260 s: "89 123\n"
2261 }
2262 `,
2263 }, {
2264 name: "WrapStringsAfterNewlines_trailingNewlineDoesNotLeaveSuperfluousEmptyString",
2265 config: Config{
2266 WrapStringsAfterNewlines: true,
2267 },
2268 in: `root {
2269 s: "89\n123\n"
2270 }
2271 `,
2272 out: `root {
2273 s:
2274 "89\n"
2275 "123\n"
2276 }
2277 `,
2278 }, {
2279 name: "WrapStringsAfterNewlines_empty",
2280 config: Config{
2281 WrapStringsAfterNewlines: true,
2282 },
2283 in: `s:
2284 `,
2285 out: `s:
2286 `,
2287 }, {
2288 name: "WrapStringsAfterNewlines_metaComment",
2289 in: `# txtpbfmt: wrap_strings_after_newlines
2290 s: "one two \nthree\n four"
2291 `,
2292 out: `# txtpbfmt: wrap_strings_after_newlines
2293 s:
2294 "one two \n"
2295 "three\n"
2296 " four"
2297 `,
2298 }, {
2299 name: "WrapStringsAfterNewlines_alreadyWrappedStringsAreRewrapped",
2300 config: Config{
2301 WrapStringsAfterNewlines: true,
2302 },
2303 in: `s:
2304 "I "
2305 "am already\n"
2306 "wrapped. \nBut this was not."
2307 `,
2308 out: `s:
2309 "I am already\n"
2310 "wrapped. \n"
2311 "But this was not."
2312 `,
2313 }, {
2314 name: "WrapStringsAfterNewlines_tripleQuotedStringsAreNotWrapped",
2315 config: Config{
2316 WrapStringsAfterNewlines: true,
2317 AllowTripleQuotedStrings: true,
2318 },
2319 in: `s1: """one two three four five"""
2320 s2: '''six seven \neight nine'''
2321 `,
2322 out: `s1: """one two three four five"""
2323 s2: '''six seven \neight nine'''
2324 `,
2325 }, {
2326 name: "WrapStringsAfterNewlines_tooManyEscapesDoesNotWrap",
2327 config: Config{
2328 WrapStringsAfterNewlines: true,
2329 },
2330 in: `s: "7\nsev\xADen\x00"
2331 `,
2332 out: `s: "7\nsev\xADen\x00"
2333 `,
2334 }, {
2335 name: "WrapStringsAfterNewlines_wayTooManyEscapesDoesNotWrap",
2336 config: Config{
2337 WrapStringsAfterNewlines: true,
2338 },
2339 in: `s: "ᆳ\xde\x00\x00\x00\x08\n(\x02\n\x0b\x00\x07\x01h\x0c\x14\x01"
2340 `,
2341 out: `s: "ᆳ\xde\x00\x00\x00\x08\n(\x02\n\x0b\x00\x07\x01h\x0c\x14\x01"
2342 `,
2343 }, {
2344 name: "WrapStringsAfterNewlines_aFewEscapesStillWrap",
2345 config: Config{
2346 WrapStringsAfterNewlines: true,
2347 },
2348 in: `s: "aaaaaaaaaa \n bbbbbbbbbb \n cccccccccc \n dddddddddd \n eeeeeeeeee\x00 \n"
2349 `,
2350 out: `s:
2351 "aaaaaaaaaa \n"
2352 " bbbbbbbbbb \n"
2353 " cccccccccc \n"
2354 " dddddddddd \n"
2355 " eeeeeeeeee\x00 \n"
2356 `,
2357 }, {
2358 name: "PreserveAngleBrackets",
2359 config: Config{
2360 PreserveAngleBrackets: true,
2361 },
2362 in: `foo <
2363 a: 1
2364 >
2365 foo {
2366 b: 2
2367 }
2368 `,
2369 out: `foo <
2370 a: 1
2371 >
2372 foo {
2373 b: 2
2374 }
2375 `,
2376 }, {
2377 name: "legacy quote behavior",
2378 config: Config{
2379 SmartQuotes: false,
2380 },
2381 in: `foo: "\"bar\""`,
2382 out: `foo: "\"bar\""
2383 `,
2384 }, {
2385 name: "smart quotes",
2386 config: Config{
2387 SmartQuotes: true,
2388 },
2389 in: `foo: "\"bar\""`,
2390 out: `foo: '"bar"'
2391 `,
2392 }, {
2393 name: "smart quotes via meta comment",
2394 config: Config{
2395 SmartQuotes: false,
2396 },
2397 in: `# txtpbfmt: smartquotes
2398 foo: "\"bar\""`,
2399 out: `# txtpbfmt: smartquotes
2400 foo: '"bar"'
2401 `,
2402 },
2403 }
2404
2405 for _, input := range inputs {
2406 got, err := FormatWithConfig([]byte(input.in), input.config)
2407 if input.wantErr != "" {
2408 if err == nil {
2409 t.Errorf("FormatWithConfig[%s] got err=nil, want err=%v", input.name, input.wantErr)
2410 continue
2411 }
2412 if !strings.Contains(err.Error(), input.wantErr) {
2413 t.Errorf("FormatWithConfig[%s] got err=%v, want err=%v", input.name, err, input.wantErr)
2414 }
2415 continue
2416 }
2417 if err != nil {
2418 t.Errorf("FormatWithConfig[%s] %v with config %v returned err %v", input.name, input.in, input.config, err)
2419 continue
2420 }
2421 if diff := diff.Diff(input.out, string(got)); diff != "" {
2422 t.Errorf("FormatWithConfig[%s](\n%s\n)\nreturned different output from expected (-want, +got):\n%s", input.name, input.in, diff)
2423 }
2424 }
2425
2426 for _, input := range inputs {
2427 nodes, err := ParseWithConfig([]byte(input.in), input.config)
2428 if input.wantErr != "" {
2429 if err == nil {
2430 t.Errorf("ParseWithConfig[%s] got err=nil, want err=%v", input.name, input.wantErr)
2431 continue
2432 }
2433 if !strings.Contains(err.Error(), input.wantErr) {
2434 t.Errorf("ParseWithConfig[%s] got err=%v, want err=%v", input.name, err, input.wantErr)
2435 }
2436 continue
2437 }
2438 if err != nil {
2439 t.Errorf("ParseWithConfig[%s] %v with config %v returned err %v", input.name, input.in, input.config, err)
2440 continue
2441 }
2442 got := Pretty(nodes, 0)
2443 if diff := diff.Diff(input.out, got); diff != "" {
2444 t.Errorf("ParseWithConfig[%s](\n%s\n)\nreturned different Pretty output from expected (-want, +got):\n%s", input.name, input.in, diff)
2445 }
2446 }
2447 }
2448
2449 func TestDebugFormat(t *testing.T) {
2450 inputs := []struct {
2451 in string
2452 want string
2453 }{{
2454 in: `name: { name: "value" }`,
2455 want: `
2456 name: "name"
2457 PreComments: "" (len 0)
2458 children:
2459 . name: "name"
2460 . PreComments: "" (len 0)
2461 . values: [{Value: "\"value\"", PreComments: "", InlineComment: ""}]
2462 `,
2463 }}
2464 for _, input := range inputs {
2465 nodes, err := Parse([]byte(input.in))
2466 if err != nil {
2467 t.Errorf("Parse %v returned err %v", input.in, err)
2468 continue
2469 }
2470 if len(nodes) == 0 {
2471 t.Errorf("Parse %v returned no nodes", input.in)
2472 continue
2473 }
2474 got := DebugFormat(nodes, 0 )
2475 if diff := diff.Diff(input.want, got); diff != "" {
2476 t.Errorf("DebugFormat %v returned diff (-want, +got):\n%s", input.in, diff)
2477 }
2478 }
2479 }
2480
2481 func TestUnescapeQuotes(t *testing.T) {
2482 inputs := []struct {
2483 in string
2484 want string
2485 }{
2486 {in: ``, want: ``},
2487 {in: `"`, want: `"`},
2488 {in: `\`, want: `\`},
2489 {in: `\\`, want: `\\`},
2490 {in: `\\\`, want: `\\\`},
2491 {in: `"\"`, want: `""`},
2492 {in: `"\\"`, want: `"\\"`},
2493 {in: `"\\\"`, want: `"\\"`},
2494 {in: `'\'`, want: `''`},
2495 {in: `'\\'`, want: `'\\'`},
2496 {in: `'\\\'`, want: `'\\'`},
2497 {in: `'\n'`, want: `'\n'`},
2498 {in: `\'\"\\\n\"\'`, want: `'"\\\n"'`},
2499 }
2500 for _, input := range inputs {
2501 got := unescapeQuotes(input.in)
2502 if got != input.want {
2503 t.Errorf("unescapeQuotes(`%s`): got `%s`, want `%s`", input.in, got, input.want)
2504 }
2505 }
2506 }
2507
2508 func TestSmartQuotes(t *testing.T) {
2509 inputs := []struct {
2510 in string
2511 wantLegacy string
2512 wantSmart string
2513 }{
2514 {`1`, `1`, `1`},
2515 {`""`, `""`, `""`},
2516 {`''`, `""`, `""`},
2517 {`"a"`, `"a"`, `"a"`},
2518 {`'a'`, `"a"`, `"a"`},
2519 {`'a"b'`, `"a\"b"`, `'a"b'`},
2520 {`"a\"b"`, `"a\"b"`, `'a"b'`},
2521 {`'a\'b'`, `"a\'b"`, `"a'b"`},
2522 {`'a"b\'c'`, `"a\"b\'c"`, `"a\"b'c"`},
2523 {`"a\"b'c"`, `"a\"b'c"`, `"a\"b'c"`},
2524 {`'a\"b\'c'`, `"a\"b\'c"`, `"a\"b'c"`},
2525 {`"a\"b\'c"`, `"a\"b\'c"`, `"a\"b'c"`},
2526 {`"'\\\'"`, `"'\\\'"`, `"'\\'"`},
2527 }
2528 for _, tc := range inputs {
2529 in := `foo: ` + tc.in
2530 name := fmt.Sprintf("Format [ %s ] with legacy quote behavior", tc.in)
2531 want := `foo: ` + tc.wantLegacy
2532 gotRaw, err := FormatWithConfig([]byte(in), Config{SmartQuotes: false})
2533 got := strings.TrimSpace(string(gotRaw))
2534 if err != nil {
2535 t.Errorf("%s: got error: %s, want no error and [ %s ]", name, err, want)
2536 } else if got != want {
2537 t.Errorf("%s: got [ %s ], want [ %s ]", name, got, want)
2538 }
2539
2540 name = fmt.Sprintf("Format [ %s ] with smart quote behavior", tc.in)
2541 want = `foo: ` + tc.wantSmart
2542 gotRaw, err = FormatWithConfig([]byte(`foo: `+tc.in), Config{SmartQuotes: true})
2543 got = strings.TrimSpace(string(gotRaw))
2544 if err != nil {
2545 t.Errorf("%s: got error: %s, want no error and [ %s ]", name, err, want)
2546 } else if got != want {
2547 t.Errorf("%s: got [ %s ], want [ %s ]", name, got, want)
2548 }
2549 }
2550 }
2551
2552 func FuzzParse(f *testing.F) {
2553 testcases := []string{"", "a: 123", "input { dimension: [2, 4, 6, 8] }", "]", "\":%\"",
2554 "%''#''0'0''0''0''0''0\""}
2555 for _, tc := range testcases {
2556 f.Add([]byte(tc))
2557 }
2558 f.Fuzz(func(t *testing.T, in []byte) {
2559 Parse(in)
2560 })
2561 }
2562
View as plain text