...

Source file src/github.com/protocolbuffers/txtpbfmt/parser/parser_test.go

Documentation: github.com/protocolbuffers/txtpbfmt/parser

     1  // Open source parser tests.
     2  // N.b.: take care when editing this file, as it contains significant trailing whitespace.
     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  		//   012
    64  		in: `p {}`,
    65  		want: map[int]bool{
    66  			2: true,
    67  		},
    68  	}, {
    69  		name: "multiple nested children in the same line",
    70  		//   0123456
    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  		//   0123
    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  			// (5 lines) * (10 chars) - 2
   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  		// ensure capacity is equal to length to catch slice index out of bounds errors
   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  		// In this test multiple subfields of `test` are given. The expected behavior is: first sort by
  1166  		// test.id; in case of a tie, sort by test.type; in case of a tie again, sort by test.name.
  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  		// Nodes are sorted by the specified order, else left untouched.
  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  		// Nodes are sorted by name/value first, then by the specified order. Hence the specified
  1786  		// repeated fields (z_first) is also sorted by value rather than in original order.
  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  		// Total length >15, but each existing line <15, so don't re-wrap 1st line to "I am ".
  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  	// Test FormatWithConfig with inputs.
  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  	// Test ParseWithConfig with inputs.
  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 /* depth */)
  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