...

Source file src/gopkg.in/ini.v1/ini_test.go

Documentation: gopkg.in/ini.v1

     1  // Copyright 2014 Unknwon
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"): you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    11  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    12  // License for the specific language governing permissions and limitations
    13  // under the License.
    14  
    15  package ini
    16  
    17  import (
    18  	"bytes"
    19  	"flag"
    20  	"io/ioutil"
    21  	"path/filepath"
    22  	"runtime"
    23  	"testing"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/require"
    27  )
    28  
    29  const (
    30  	confData = `
    31  	; Package name
    32  	NAME        = ini
    33  	; Package version
    34  	VERSION     = v1
    35  	; Package import path
    36  	IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
    37  	
    38  	# Information about package author
    39  	# Bio can be written in multiple lines.
    40  	[author]
    41  	NAME   = Unknwon  ; Succeeding comment
    42  	E-MAIL = fake@localhost
    43  	GITHUB = https://github.com/%(NAME)s
    44  	BIO    = """Gopher.
    45  	Coding addict.
    46  	Good man.
    47  	"""  # Succeeding comment`
    48  	minimalConf  = "testdata/minimal.ini"
    49  	fullConf     = "testdata/full.ini"
    50  	notFoundConf = "testdata/404.ini"
    51  )
    52  
    53  var update = flag.Bool("update", false, "Update .golden files")
    54  
    55  func TestLoad(t *testing.T) {
    56  	t.Run("load from good data sources", func(t *testing.T) {
    57  		f, err := Load(
    58  			"testdata/minimal.ini",
    59  			[]byte("NAME = ini\nIMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s"),
    60  			bytes.NewReader([]byte(`VERSION = v1`)),
    61  			ioutil.NopCloser(bytes.NewReader([]byte("[author]\nNAME = Unknwon"))),
    62  		)
    63  		require.NoError(t, err)
    64  		require.NotNil(t, f)
    65  
    66  		// Validate values make sure all sources are loaded correctly
    67  		sec := f.Section("")
    68  		assert.Equal(t, "ini", sec.Key("NAME").String())
    69  		assert.Equal(t, "v1", sec.Key("VERSION").String())
    70  		assert.Equal(t, "gopkg.in/ini.v1", sec.Key("IMPORT_PATH").String())
    71  
    72  		sec = f.Section("author")
    73  		assert.Equal(t, "Unknwon", sec.Key("NAME").String())
    74  		assert.Equal(t, "u@gogs.io", sec.Key("E-MAIL").String())
    75  	})
    76  
    77  	t.Run("load from bad data sources", func(t *testing.T) {
    78  		t.Run("invalid input", func(t *testing.T) {
    79  			_, err := Load(notFoundConf)
    80  			require.Error(t, err)
    81  		})
    82  
    83  		t.Run("unsupported type", func(t *testing.T) {
    84  			_, err := Load(123)
    85  			require.Error(t, err)
    86  		})
    87  	})
    88  
    89  	t.Run("cannot properly parse INI files containing `#` or `;` in value", func(t *testing.T) {
    90  		f, err := Load([]byte(`
    91  	[author]
    92  	NAME = U#n#k#n#w#o#n
    93  	GITHUB = U;n;k;n;w;o;n
    94  	`))
    95  		require.NoError(t, err)
    96  		require.NotNil(t, f)
    97  
    98  		sec := f.Section("author")
    99  		nameValue := sec.Key("NAME").String()
   100  		githubValue := sec.Key("GITHUB").String()
   101  		assert.Equal(t, "U", nameValue)
   102  		assert.Equal(t, "U", githubValue)
   103  	})
   104  
   105  	t.Run("cannot parse small python-compatible INI files", func(t *testing.T) {
   106  		f, err := Load([]byte(`
   107  [long]
   108  long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
   109     foo
   110     bar
   111     foobar
   112     barfoo
   113     -----END RSA PRIVATE KEY-----
   114  `))
   115  		require.Error(t, err)
   116  		assert.Nil(t, f)
   117  		assert.Equal(t, "key-value delimiter not found: foo\n", err.Error())
   118  	})
   119  
   120  	t.Run("cannot parse big python-compatible INI files", func(t *testing.T) {
   121  		f, err := Load([]byte(`
   122  [long]
   123  long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
   124     1foo
   125     2bar
   126     3foobar
   127     4barfoo
   128     5foo
   129     6bar
   130     7foobar
   131     8barfoo
   132     9foo
   133     10bar
   134     11foobar
   135     12barfoo
   136     13foo
   137     14bar
   138     15foobar
   139     16barfoo
   140     17foo
   141     18bar
   142     19foobar
   143     20barfoo
   144     21foo
   145     22bar
   146     23foobar
   147     24barfoo
   148     25foo
   149     26bar
   150     27foobar
   151     28barfoo
   152     29foo
   153     30bar
   154     31foobar
   155     32barfoo
   156     33foo
   157     34bar
   158     35foobar
   159     36barfoo
   160     37foo
   161     38bar
   162     39foobar
   163     40barfoo
   164     41foo
   165     42bar
   166     43foobar
   167     44barfoo
   168     45foo
   169     46bar
   170     47foobar
   171     48barfoo
   172     49foo
   173     50bar
   174     51foobar
   175     52barfoo
   176     53foo
   177     54bar
   178     55foobar
   179     56barfoo
   180     57foo
   181     58bar
   182     59foobar
   183     60barfoo
   184     61foo
   185     62bar
   186     63foobar
   187     64barfoo
   188     65foo
   189     66bar
   190     67foobar
   191     68barfoo
   192     69foo
   193     70bar
   194     71foobar
   195     72barfoo
   196     73foo
   197     74bar
   198     75foobar
   199     76barfoo
   200     77foo
   201     78bar
   202     79foobar
   203     80barfoo
   204     81foo
   205     82bar
   206     83foobar
   207     84barfoo
   208     85foo
   209     86bar
   210     87foobar
   211     88barfoo
   212     89foo
   213     90bar
   214     91foobar
   215     92barfoo
   216     93foo
   217     94bar
   218     95foobar
   219     96barfoo
   220     -----END RSA PRIVATE KEY-----
   221  `))
   222  		require.Error(t, err)
   223  		assert.Nil(t, f)
   224  		assert.Equal(t, "key-value delimiter not found: 1foo\n", err.Error())
   225  	})
   226  }
   227  
   228  func TestLooseLoad(t *testing.T) {
   229  	f, err := LoadSources(LoadOptions{Loose: true}, notFoundConf, minimalConf)
   230  	require.NoError(t, err)
   231  	require.NotNil(t, f)
   232  
   233  	t.Run("inverse case", func(t *testing.T) {
   234  		_, err = Load(notFoundConf)
   235  		require.Error(t, err)
   236  	})
   237  }
   238  
   239  func TestInsensitiveLoad(t *testing.T) {
   240  	t.Run("insensitive to section and key names", func(t *testing.T) {
   241  		f, err := InsensitiveLoad(minimalConf)
   242  		require.NoError(t, err)
   243  		require.NotNil(t, f)
   244  
   245  		assert.Equal(t, "u@gogs.io", f.Section("Author").Key("e-mail").String())
   246  
   247  		t.Run("write out", func(t *testing.T) {
   248  			var buf bytes.Buffer
   249  			_, err := f.WriteTo(&buf)
   250  			require.NoError(t, err)
   251  			assert.Equal(t, `[author]
   252  e-mail = u@gogs.io
   253  `,
   254  				buf.String(),
   255  			)
   256  		})
   257  
   258  		t.Run("inverse case", func(t *testing.T) {
   259  			f, err := Load(minimalConf)
   260  			require.NoError(t, err)
   261  			require.NotNil(t, f)
   262  
   263  			assert.Empty(t, f.Section("Author").Key("e-mail").String())
   264  		})
   265  	})
   266  
   267  	// Ref: https://github.com/go-ini/ini/issues/198
   268  	t.Run("insensitive load with default section", func(t *testing.T) {
   269  		f, err := InsensitiveLoad([]byte(`
   270  user = unknwon
   271  [profile]
   272  email = unknwon@local
   273  `))
   274  		require.NoError(t, err)
   275  		require.NotNil(t, f)
   276  
   277  		assert.Equal(t, "unknwon", f.Section(DefaultSection).Key("user").String())
   278  	})
   279  }
   280  
   281  func TestLoadSources(t *testing.T) {
   282  	t.Run("with true `AllowPythonMultilineValues`", func(t *testing.T) {
   283  		t.Run("ignore nonexistent files", func(t *testing.T) {
   284  			f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true, Loose: true}, notFoundConf, minimalConf)
   285  			require.NoError(t, err)
   286  			require.NotNil(t, f)
   287  
   288  			t.Run("inverse case", func(t *testing.T) {
   289  				_, err = LoadSources(LoadOptions{AllowPythonMultilineValues: true}, notFoundConf)
   290  				require.Error(t, err)
   291  			})
   292  		})
   293  
   294  		t.Run("insensitive to section and key names", func(t *testing.T) {
   295  			f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true, Insensitive: true}, minimalConf)
   296  			require.NoError(t, err)
   297  			require.NotNil(t, f)
   298  
   299  			assert.Equal(t, "u@gogs.io", f.Section("Author").Key("e-mail").String())
   300  
   301  			t.Run("write out", func(t *testing.T) {
   302  				var buf bytes.Buffer
   303  				_, err := f.WriteTo(&buf)
   304  				require.NoError(t, err)
   305  				assert.Equal(t, `[author]
   306  e-mail = u@gogs.io
   307  `,
   308  					buf.String(),
   309  				)
   310  			})
   311  
   312  			t.Run("inverse case", func(t *testing.T) {
   313  				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, minimalConf)
   314  				require.NoError(t, err)
   315  				require.NotNil(t, f)
   316  
   317  				assert.Empty(t, f.Section("Author").Key("e-mail").String())
   318  			})
   319  		})
   320  
   321  		t.Run("insensitive to sections and sensitive to key names", func(t *testing.T) {
   322  			f, err := LoadSources(LoadOptions{InsensitiveSections: true}, minimalConf)
   323  			require.NoError(t, err)
   324  			require.NotNil(t, f)
   325  
   326  			assert.Equal(t, "u@gogs.io", f.Section("Author").Key("E-MAIL").String())
   327  
   328  			t.Run("write out", func(t *testing.T) {
   329  				var buf bytes.Buffer
   330  				_, err := f.WriteTo(&buf)
   331  				require.NoError(t, err)
   332  				assert.Equal(t, `[author]
   333  E-MAIL = u@gogs.io
   334  `,
   335  					buf.String(),
   336  				)
   337  			})
   338  
   339  			t.Run("inverse case", func(t *testing.T) {
   340  				f, err := LoadSources(LoadOptions{}, minimalConf)
   341  				require.NoError(t, err)
   342  				require.NotNil(t, f)
   343  
   344  				assert.Empty(t, f.Section("Author").Key("e-mail").String())
   345  			})
   346  		})
   347  
   348  		t.Run("sensitive to sections and insensitive to key names", func(t *testing.T) {
   349  			f, err := LoadSources(LoadOptions{InsensitiveKeys: true}, minimalConf)
   350  			require.NoError(t, err)
   351  			require.NotNil(t, f)
   352  
   353  			assert.Equal(t, "u@gogs.io", f.Section("author").Key("e-mail").String())
   354  
   355  			t.Run("write out", func(t *testing.T) {
   356  				var buf bytes.Buffer
   357  				_, err := f.WriteTo(&buf)
   358  				require.NoError(t, err)
   359  				assert.Equal(t, `[author]
   360  e-mail = u@gogs.io
   361  `,
   362  					buf.String(),
   363  				)
   364  			})
   365  
   366  			t.Run("inverse case", func(t *testing.T) {
   367  				f, err := LoadSources(LoadOptions{}, minimalConf)
   368  				require.NoError(t, err)
   369  				require.NotNil(t, f)
   370  
   371  				assert.Empty(t, f.Section("Author").Key("e-mail").String())
   372  			})
   373  		})
   374  
   375  		t.Run("ignore continuation lines", func(t *testing.T) {
   376  			f, err := LoadSources(LoadOptions{
   377  				AllowPythonMultilineValues: true,
   378  				IgnoreContinuation:         true,
   379  			}, []byte(`
   380  key1=a\b\
   381  key2=c\d\
   382  key3=value`))
   383  			require.NoError(t, err)
   384  			require.NotNil(t, f)
   385  
   386  			assert.Equal(t, `a\b\`, f.Section("").Key("key1").String())
   387  			assert.Equal(t, `c\d\`, f.Section("").Key("key2").String())
   388  			assert.Equal(t, "value", f.Section("").Key("key3").String())
   389  
   390  			t.Run("inverse case", func(t *testing.T) {
   391  				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
   392  key1=a\b\
   393  key2=c\d\`))
   394  				require.NoError(t, err)
   395  				require.NotNil(t, f)
   396  
   397  				assert.Equal(t, `a\bkey2=c\d`, f.Section("").Key("key1").String())
   398  			})
   399  		})
   400  
   401  		t.Run("ignore inline comments", func(t *testing.T) {
   402  			f, err := LoadSources(LoadOptions{
   403  				AllowPythonMultilineValues: true,
   404  				IgnoreInlineComment:        true,
   405  			}, []byte(`
   406  key1=value ;comment
   407  key2=value2 #comment2
   408  key3=val#ue #comment3`))
   409  			require.NoError(t, err)
   410  			require.NotNil(t, f)
   411  
   412  			assert.Equal(t, `value ;comment`, f.Section("").Key("key1").String())
   413  			assert.Equal(t, `value2 #comment2`, f.Section("").Key("key2").String())
   414  			assert.Equal(t, `val#ue #comment3`, f.Section("").Key("key3").String())
   415  
   416  			t.Run("inverse case", func(t *testing.T) {
   417  				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
   418  key1=value ;comment
   419  key2=value2 #comment2`))
   420  				require.NoError(t, err)
   421  				require.NotNil(t, f)
   422  
   423  				assert.Equal(t, `value`, f.Section("").Key("key1").String())
   424  				assert.Equal(t, `;comment`, f.Section("").Key("key1").Comment)
   425  				assert.Equal(t, `value2`, f.Section("").Key("key2").String())
   426  				assert.Equal(t, `#comment2`, f.Section("").Key("key2").Comment)
   427  			})
   428  		})
   429  
   430  		t.Run("skip unrecognizable lines", func(t *testing.T) {
   431  			f, err := LoadSources(LoadOptions{
   432  				SkipUnrecognizableLines: true,
   433  			}, []byte(`
   434  GenerationDepth: 13
   435  
   436  BiomeRarityScale: 100
   437  
   438  ################
   439  # Biome Groups #
   440  ################
   441  
   442  BiomeGroup(NormalBiomes, 3, 99, RoofedForestEnchanted, ForestSakura, FloatingJungle
   443  BiomeGroup(IceBiomes, 4, 85, Ice Plains)
   444  
   445  = RainForest
   446  `))
   447  			require.NoError(t, err)
   448  			require.NotNil(t, f)
   449  
   450  			assert.Equal(t, "13", f.Section("").Key("GenerationDepth").String())
   451  			assert.Equal(t, "100", f.Section("").Key("BiomeRarityScale").String())
   452  			assert.False(t, f.Section("").HasKey("BiomeGroup"))
   453  		})
   454  
   455  		t.Run("allow boolean type keys", func(t *testing.T) {
   456  			f, err := LoadSources(LoadOptions{
   457  				AllowPythonMultilineValues: true,
   458  				AllowBooleanKeys:           true,
   459  			}, []byte(`
   460  key1=hello
   461  #key2
   462  key3`))
   463  			require.NoError(t, err)
   464  			require.NotNil(t, f)
   465  
   466  			assert.Equal(t, []string{"key1", "key3"}, f.Section("").KeyStrings())
   467  			assert.True(t, f.Section("").Key("key3").MustBool(false))
   468  
   469  			t.Run("write out", func(t *testing.T) {
   470  				var buf bytes.Buffer
   471  				_, err := f.WriteTo(&buf)
   472  				require.NoError(t, err)
   473  				assert.Equal(t, `key1 = hello
   474  # key2
   475  key3
   476  `,
   477  					buf.String(),
   478  				)
   479  			})
   480  
   481  			t.Run("inverse case", func(t *testing.T) {
   482  				_, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
   483  key1=hello
   484  #key2
   485  key3`))
   486  				require.Error(t, err)
   487  			})
   488  		})
   489  
   490  		t.Run("allow shadow keys", func(t *testing.T) {
   491  			f, err := LoadSources(LoadOptions{AllowShadows: true, AllowPythonMultilineValues: true}, []byte(`
   492  [remote "origin"]
   493  url = https://github.com/Antergone/test1.git
   494  url = https://github.com/Antergone/test2.git
   495  fetch = +refs/heads/*:refs/remotes/origin/*`))
   496  			require.NoError(t, err)
   497  			require.NotNil(t, f)
   498  
   499  			assert.Equal(t, "https://github.com/Antergone/test1.git", f.Section(`remote "origin"`).Key("url").String())
   500  			assert.Equal(
   501  				t,
   502  				[]string{
   503  					"https://github.com/Antergone/test1.git",
   504  					"https://github.com/Antergone/test2.git",
   505  				},
   506  				f.Section(`remote "origin"`).Key("url").ValueWithShadows(),
   507  			)
   508  			assert.Equal(t, "+refs/heads/*:refs/remotes/origin/*", f.Section(`remote "origin"`).Key("fetch").String())
   509  
   510  			t.Run("write out", func(t *testing.T) {
   511  				var buf bytes.Buffer
   512  				_, err := f.WriteTo(&buf)
   513  				require.NoError(t, err)
   514  				assert.Equal(t, `[remote "origin"]
   515  url   = https://github.com/Antergone/test1.git
   516  url   = https://github.com/Antergone/test2.git
   517  fetch = +refs/heads/*:refs/remotes/origin/*
   518  `,
   519  					buf.String(),
   520  				)
   521  			})
   522  
   523  			t.Run("inverse case", func(t *testing.T) {
   524  				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
   525  [remote "origin"]
   526  url = https://github.com/Antergone/test1.git
   527  url = https://github.com/Antergone/test2.git`))
   528  				require.NoError(t, err)
   529  				require.NotNil(t, f)
   530  
   531  				assert.Equal(t, "https://github.com/Antergone/test2.git", f.Section(`remote "origin"`).Key("url").String())
   532  			})
   533  		})
   534  
   535  		t.Run("unescape double quotes inside value", func(t *testing.T) {
   536  			f, err := LoadSources(LoadOptions{
   537  				AllowPythonMultilineValues: true,
   538  				UnescapeValueDoubleQuotes:  true,
   539  			}, []byte(`
   540  create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
   541  			require.NoError(t, err)
   542  			require.NotNil(t, f)
   543  
   544  			assert.Equal(t, `创建了仓库 <a href="%s">%s</a>`, f.Section("").Key("create_repo").String())
   545  
   546  			t.Run("inverse case", func(t *testing.T) {
   547  				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
   548  create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
   549  				require.NoError(t, err)
   550  				require.NotNil(t, f)
   551  
   552  				assert.Equal(t, `"创建了仓库 <a href=\"%s\">%s</a>"`, f.Section("").Key("create_repo").String())
   553  			})
   554  		})
   555  
   556  		t.Run("unescape comment symbols inside value", func(t *testing.T) {
   557  			f, err := LoadSources(LoadOptions{
   558  				AllowPythonMultilineValues:  true,
   559  				IgnoreInlineComment:         true,
   560  				UnescapeValueCommentSymbols: true,
   561  			}, []byte(`
   562  key = test value <span style="color: %s\; background: %s">more text</span>
   563  `))
   564  			require.NoError(t, err)
   565  			require.NotNil(t, f)
   566  
   567  			assert.Equal(t, `test value <span style="color: %s; background: %s">more text</span>`, f.Section("").Key("key").String())
   568  		})
   569  
   570  		t.Run("can parse small python-compatible INI files", func(t *testing.T) {
   571  			f, err := LoadSources(LoadOptions{
   572  				AllowPythonMultilineValues: true,
   573  				Insensitive:                true,
   574  				UnparseableSections:        []string{"core_lesson", "comments"},
   575  			}, []byte(`
   576  [long]
   577  long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
   578    foo
   579    bar
   580    foobar
   581    barfoo
   582    -----END RSA PRIVATE KEY-----
   583  multiline_list =
   584    first
   585    second
   586    third
   587  `))
   588  			require.NoError(t, err)
   589  			require.NotNil(t, f)
   590  
   591  			assert.Equal(t, "-----BEGIN RSA PRIVATE KEY-----\n  foo\n  bar\n  foobar\n  barfoo\n  -----END RSA PRIVATE KEY-----", f.Section("long").Key("long_rsa_private_key").String())
   592  			assert.Equal(t, "\n  first\n  second\n  third", f.Section("long").Key("multiline_list").String())
   593  		})
   594  
   595  		t.Run("can parse big python-compatible INI files", func(t *testing.T) {
   596  			f, err := LoadSources(LoadOptions{
   597  				AllowPythonMultilineValues: true,
   598  				Insensitive:                true,
   599  				UnparseableSections:        []string{"core_lesson", "comments"},
   600  			}, []byte(`
   601  [long]
   602  long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
   603     1foo
   604     2bar
   605     3foobar
   606     4barfoo
   607     5foo
   608     6bar
   609     7foobar
   610     8barfoo
   611     9foo
   612     10bar
   613     11foobar
   614     12barfoo
   615     13foo
   616     14bar
   617     15foobar
   618     16barfoo
   619     17foo
   620     18bar
   621     19foobar
   622     20barfoo
   623     21foo
   624     22bar
   625     23foobar
   626     24barfoo
   627     25foo
   628     26bar
   629     27foobar
   630     28barfoo
   631     29foo
   632     30bar
   633     31foobar
   634     32barfoo
   635     33foo
   636     34bar
   637     35foobar
   638     36barfoo
   639     37foo
   640     38bar
   641     39foobar
   642     40barfoo
   643     41foo
   644     42bar
   645     43foobar
   646     44barfoo
   647     45foo
   648     46bar
   649     47foobar
   650     48barfoo
   651     49foo
   652     50bar
   653     51foobar
   654     52barfoo
   655     53foo
   656     54bar
   657     55foobar
   658     56barfoo
   659     57foo
   660     58bar
   661     59foobar
   662     60barfoo
   663     61foo
   664     62bar
   665     63foobar
   666     64barfoo
   667     65foo
   668     66bar
   669     67foobar
   670     68barfoo
   671     69foo
   672     70bar
   673     71foobar
   674     72barfoo
   675     73foo
   676     74bar
   677     75foobar
   678     76barfoo
   679     77foo
   680     78bar
   681     79foobar
   682     80barfoo
   683     81foo
   684     82bar
   685     83foobar
   686     84barfoo
   687     85foo
   688     86bar
   689     87foobar
   690     88barfoo
   691     89foo
   692     90bar
   693     91foobar
   694     92barfoo
   695     93foo
   696     94bar
   697     95foobar
   698     96barfoo
   699     -----END RSA PRIVATE KEY-----
   700  `))
   701  			require.NoError(t, err)
   702  			require.NotNil(t, f)
   703  
   704  			assert.Equal(t, `-----BEGIN RSA PRIVATE KEY-----
   705     1foo
   706     2bar
   707     3foobar
   708     4barfoo
   709     5foo
   710     6bar
   711     7foobar
   712     8barfoo
   713     9foo
   714     10bar
   715     11foobar
   716     12barfoo
   717     13foo
   718     14bar
   719     15foobar
   720     16barfoo
   721     17foo
   722     18bar
   723     19foobar
   724     20barfoo
   725     21foo
   726     22bar
   727     23foobar
   728     24barfoo
   729     25foo
   730     26bar
   731     27foobar
   732     28barfoo
   733     29foo
   734     30bar
   735     31foobar
   736     32barfoo
   737     33foo
   738     34bar
   739     35foobar
   740     36barfoo
   741     37foo
   742     38bar
   743     39foobar
   744     40barfoo
   745     41foo
   746     42bar
   747     43foobar
   748     44barfoo
   749     45foo
   750     46bar
   751     47foobar
   752     48barfoo
   753     49foo
   754     50bar
   755     51foobar
   756     52barfoo
   757     53foo
   758     54bar
   759     55foobar
   760     56barfoo
   761     57foo
   762     58bar
   763     59foobar
   764     60barfoo
   765     61foo
   766     62bar
   767     63foobar
   768     64barfoo
   769     65foo
   770     66bar
   771     67foobar
   772     68barfoo
   773     69foo
   774     70bar
   775     71foobar
   776     72barfoo
   777     73foo
   778     74bar
   779     75foobar
   780     76barfoo
   781     77foo
   782     78bar
   783     79foobar
   784     80barfoo
   785     81foo
   786     82bar
   787     83foobar
   788     84barfoo
   789     85foo
   790     86bar
   791     87foobar
   792     88barfoo
   793     89foo
   794     90bar
   795     91foobar
   796     92barfoo
   797     93foo
   798     94bar
   799     95foobar
   800     96barfoo
   801     -----END RSA PRIVATE KEY-----`,
   802  				f.Section("long").Key("long_rsa_private_key").String(),
   803  			)
   804  		})
   805  
   806  		t.Run("allow unparsable sections", func(t *testing.T) {
   807  			f, err := LoadSources(LoadOptions{
   808  				AllowPythonMultilineValues: true,
   809  				Insensitive:                true,
   810  				UnparseableSections:        []string{"core_lesson", "comments"},
   811  			}, []byte(`
   812  Lesson_Location = 87
   813  Lesson_Status = C
   814  Score = 3
   815  Time = 00:02:30
   816  
   817  [CORE_LESSON]
   818  my lesson state data – 1111111111111111111000000000000000001110000
   819  111111111111111111100000000000111000000000 – end my lesson state data
   820  
   821  [COMMENTS]
   822  <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
   823  `))
   824  			require.NoError(t, err)
   825  			require.NotNil(t, f)
   826  
   827  			assert.Equal(t, "3", f.Section("").Key("score").String())
   828  			assert.Empty(t, f.Section("").Body())
   829  			assert.Equal(t, `my lesson state data – 1111111111111111111000000000000000001110000
   830  111111111111111111100000000000111000000000 – end my lesson state data`,
   831  				f.Section("core_lesson").Body(),
   832  			)
   833  			assert.Equal(t, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`, f.Section("comments").Body())
   834  
   835  			t.Run("write out", func(t *testing.T) {
   836  				var buf bytes.Buffer
   837  				_, err := f.WriteTo(&buf)
   838  				require.NoError(t, err)
   839  				assert.Equal(t, `lesson_location = 87
   840  lesson_status   = C
   841  score           = 3
   842  time            = 00:02:30
   843  
   844  [core_lesson]
   845  my lesson state data – 1111111111111111111000000000000000001110000
   846  111111111111111111100000000000111000000000 – end my lesson state data
   847  
   848  [comments]
   849  <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
   850  `,
   851  					buf.String(),
   852  				)
   853  			})
   854  
   855  			t.Run("inverse case", func(t *testing.T) {
   856  				_, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
   857  [CORE_LESSON]
   858  my lesson state data – 1111111111111111111000000000000000001110000
   859  111111111111111111100000000000111000000000 – end my lesson state data`))
   860  				require.Error(t, err)
   861  			})
   862  		})
   863  
   864  		t.Run("and false `SpaceBeforeInlineComment`", func(t *testing.T) {
   865  			t.Run("cannot parse INI files containing `#` or `;` in value", func(t *testing.T) {
   866  				f, err := LoadSources(
   867  					LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: false},
   868  					[]byte(`
   869  [author]
   870  NAME = U#n#k#n#w#o#n
   871  GITHUB = U;n;k;n;w;o;n
   872  `))
   873  				require.NoError(t, err)
   874  				require.NotNil(t, f)
   875  				sec := f.Section("author")
   876  				nameValue := sec.Key("NAME").String()
   877  				githubValue := sec.Key("GITHUB").String()
   878  				assert.Equal(t, "U", nameValue)
   879  				assert.Equal(t, "U", githubValue)
   880  			})
   881  		})
   882  
   883  		t.Run("and true `SpaceBeforeInlineComment`", func(t *testing.T) {
   884  			t.Run("can parse INI files containing `#` or `;` in value", func(t *testing.T) {
   885  				f, err := LoadSources(
   886  					LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: true},
   887  					[]byte(`
   888  [author]
   889  NAME = U#n#k#n#w#o#n
   890  GITHUB = U;n;k;n;w;o;n
   891  `))
   892  				require.NoError(t, err)
   893  				require.NotNil(t, f)
   894  				sec := f.Section("author")
   895  				nameValue := sec.Key("NAME").String()
   896  				githubValue := sec.Key("GITHUB").String()
   897  				assert.Equal(t, "U#n#k#n#w#o#n", nameValue)
   898  				assert.Equal(t, "U;n;k;n;w;o;n", githubValue)
   899  			})
   900  		})
   901  	})
   902  
   903  	t.Run("with false `AllowPythonMultilineValues`", func(t *testing.T) {
   904  		t.Run("ignore nonexistent files", func(t *testing.T) {
   905  			f, err := LoadSources(LoadOptions{
   906  				AllowPythonMultilineValues: false,
   907  				Loose:                      true,
   908  			}, notFoundConf, minimalConf)
   909  			require.NoError(t, err)
   910  			require.NotNil(t, f)
   911  
   912  			t.Run("inverse case", func(t *testing.T) {
   913  				_, err = LoadSources(LoadOptions{
   914  					AllowPythonMultilineValues: false,
   915  				}, notFoundConf)
   916  				require.Error(t, err)
   917  			})
   918  		})
   919  
   920  		t.Run("insensitive to section and key names", func(t *testing.T) {
   921  			f, err := LoadSources(LoadOptions{
   922  				AllowPythonMultilineValues: false,
   923  				Insensitive:                true,
   924  			}, minimalConf)
   925  			require.NoError(t, err)
   926  			require.NotNil(t, f)
   927  
   928  			assert.Equal(t, "u@gogs.io", f.Section("Author").Key("e-mail").String())
   929  
   930  			t.Run("write out", func(t *testing.T) {
   931  				var buf bytes.Buffer
   932  				_, err := f.WriteTo(&buf)
   933  				require.NoError(t, err)
   934  				assert.Equal(t, `[author]
   935  e-mail = u@gogs.io
   936  `,
   937  					buf.String(),
   938  				)
   939  			})
   940  
   941  			t.Run("inverse case", func(t *testing.T) {
   942  				f, err := LoadSources(LoadOptions{
   943  					AllowPythonMultilineValues: false,
   944  				}, minimalConf)
   945  				require.NoError(t, err)
   946  				require.NotNil(t, f)
   947  
   948  				assert.Empty(t, f.Section("Author").Key("e-mail").String())
   949  			})
   950  		})
   951  
   952  		t.Run("ignore continuation lines", func(t *testing.T) {
   953  			f, err := LoadSources(LoadOptions{
   954  				AllowPythonMultilineValues: false,
   955  				IgnoreContinuation:         true,
   956  			}, []byte(`
   957  key1=a\b\
   958  key2=c\d\
   959  key3=value`))
   960  			require.NoError(t, err)
   961  			require.NotNil(t, f)
   962  
   963  			assert.Equal(t, `a\b\`, f.Section("").Key("key1").String())
   964  			assert.Equal(t, `c\d\`, f.Section("").Key("key2").String())
   965  			assert.Equal(t, "value", f.Section("").Key("key3").String())
   966  
   967  			t.Run("inverse case", func(t *testing.T) {
   968  				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
   969  key1=a\b\
   970  key2=c\d\`))
   971  				require.NoError(t, err)
   972  				require.NotNil(t, f)
   973  
   974  				assert.Equal(t, `a\bkey2=c\d`, f.Section("").Key("key1").String())
   975  			})
   976  		})
   977  
   978  		t.Run("ignore inline comments", func(t *testing.T) {
   979  			f, err := LoadSources(LoadOptions{
   980  				AllowPythonMultilineValues: false,
   981  				IgnoreInlineComment:        true,
   982  			}, []byte(`
   983  key1=value ;comment
   984  key2=value2 #comment2
   985  key3=val#ue #comment3`))
   986  			require.NoError(t, err)
   987  			require.NotNil(t, f)
   988  
   989  			assert.Equal(t, `value ;comment`, f.Section("").Key("key1").String())
   990  			assert.Equal(t, `value2 #comment2`, f.Section("").Key("key2").String())
   991  			assert.Equal(t, `val#ue #comment3`, f.Section("").Key("key3").String())
   992  
   993  			t.Run("inverse case", func(t *testing.T) {
   994  				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
   995  key1=value ;comment
   996  key2=value2 #comment2`))
   997  				require.NoError(t, err)
   998  				require.NotNil(t, f)
   999  
  1000  				assert.Equal(t, `value`, f.Section("").Key("key1").String())
  1001  				assert.Equal(t, `;comment`, f.Section("").Key("key1").Comment)
  1002  				assert.Equal(t, `value2`, f.Section("").Key("key2").String())
  1003  				assert.Equal(t, `#comment2`, f.Section("").Key("key2").Comment)
  1004  			})
  1005  		})
  1006  
  1007  		t.Run("allow boolean type keys", func(t *testing.T) {
  1008  			f, err := LoadSources(LoadOptions{
  1009  				AllowPythonMultilineValues: false,
  1010  				AllowBooleanKeys:           true,
  1011  			}, []byte(`
  1012  key1=hello
  1013  #key2
  1014  key3`))
  1015  			require.NoError(t, err)
  1016  			require.NotNil(t, f)
  1017  
  1018  			assert.Equal(t, []string{"key1", "key3"}, f.Section("").KeyStrings())
  1019  			assert.True(t, f.Section("").Key("key3").MustBool(false))
  1020  
  1021  			t.Run("write out", func(t *testing.T) {
  1022  				var buf bytes.Buffer
  1023  				_, err := f.WriteTo(&buf)
  1024  				require.NoError(t, err)
  1025  				assert.Equal(t, `key1 = hello
  1026  # key2
  1027  key3
  1028  `,
  1029  					buf.String(),
  1030  				)
  1031  			})
  1032  
  1033  			t.Run("inverse case", func(t *testing.T) {
  1034  				_, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
  1035  key1=hello
  1036  #key2
  1037  key3`))
  1038  				require.Error(t, err)
  1039  			})
  1040  		})
  1041  
  1042  		t.Run("allow shadow keys", func(t *testing.T) {
  1043  			f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false, AllowShadows: true}, []byte(`
  1044  [remote "origin"]
  1045  url = https://github.com/Antergone/test1.git
  1046  url = https://github.com/Antergone/test2.git
  1047  fetch = +refs/heads/*:refs/remotes/origin/*`))
  1048  			require.NoError(t, err)
  1049  			require.NotNil(t, f)
  1050  
  1051  			assert.Equal(t, "https://github.com/Antergone/test1.git", f.Section(`remote "origin"`).Key("url").String())
  1052  			assert.Equal(
  1053  				t,
  1054  				[]string{
  1055  					"https://github.com/Antergone/test1.git",
  1056  					"https://github.com/Antergone/test2.git",
  1057  				},
  1058  				f.Section(`remote "origin"`).Key("url").ValueWithShadows(),
  1059  			)
  1060  			assert.Equal(t, "+refs/heads/*:refs/remotes/origin/*", f.Section(`remote "origin"`).Key("fetch").String())
  1061  
  1062  			t.Run("write out", func(t *testing.T) {
  1063  				var buf bytes.Buffer
  1064  				_, err := f.WriteTo(&buf)
  1065  				require.NoError(t, err)
  1066  				assert.Equal(t, `[remote "origin"]
  1067  url   = https://github.com/Antergone/test1.git
  1068  url   = https://github.com/Antergone/test2.git
  1069  fetch = +refs/heads/*:refs/remotes/origin/*
  1070  `,
  1071  					buf.String(),
  1072  				)
  1073  			})
  1074  
  1075  			t.Run("inverse case", func(t *testing.T) {
  1076  				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
  1077  [remote "origin"]
  1078  url = https://github.com/Antergone/test1.git
  1079  url = https://github.com/Antergone/test2.git`))
  1080  				require.NoError(t, err)
  1081  				require.NotNil(t, f)
  1082  
  1083  				assert.Equal(t, "https://github.com/Antergone/test2.git", f.Section(`remote "origin"`).Key("url").String())
  1084  			})
  1085  		})
  1086  
  1087  		t.Run("unescape double quotes inside value", func(t *testing.T) {
  1088  			f, err := LoadSources(LoadOptions{
  1089  				AllowPythonMultilineValues: false,
  1090  				UnescapeValueDoubleQuotes:  true,
  1091  			}, []byte(`
  1092  create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
  1093  			require.NoError(t, err)
  1094  			require.NotNil(t, f)
  1095  
  1096  			assert.Equal(t, `创建了仓库 <a href="%s">%s</a>`, f.Section("").Key("create_repo").String())
  1097  
  1098  			t.Run("inverse case", func(t *testing.T) {
  1099  				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
  1100  create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
  1101  				require.NoError(t, err)
  1102  				require.NotNil(t, f)
  1103  
  1104  				assert.Equal(t, `"创建了仓库 <a href=\"%s\">%s</a>"`, f.Section("").Key("create_repo").String())
  1105  			})
  1106  		})
  1107  
  1108  		t.Run("unescape comment symbols inside value", func(t *testing.T) {
  1109  			f, err := LoadSources(LoadOptions{
  1110  				AllowPythonMultilineValues:  false,
  1111  				IgnoreInlineComment:         true,
  1112  				UnescapeValueCommentSymbols: true,
  1113  			}, []byte(`
  1114  key = test value <span style="color: %s\; background: %s">more text</span>
  1115  `))
  1116  			require.NoError(t, err)
  1117  			require.NotNil(t, f)
  1118  
  1119  			assert.Equal(t, `test value <span style="color: %s; background: %s">more text</span>`, f.Section("").Key("key").String())
  1120  		})
  1121  
  1122  		t.Run("cannot parse small python-compatible INI files", func(t *testing.T) {
  1123  			f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
  1124  [long]
  1125  long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
  1126    foo
  1127    bar
  1128    foobar
  1129    barfoo
  1130    -----END RSA PRIVATE KEY-----
  1131  `))
  1132  			require.Error(t, err)
  1133  			assert.Nil(t, f)
  1134  			assert.Equal(t, "key-value delimiter not found: foo\n", err.Error())
  1135  		})
  1136  
  1137  		t.Run("cannot parse big python-compatible INI files", func(t *testing.T) {
  1138  			f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
  1139  [long]
  1140  long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
  1141    1foo
  1142    2bar
  1143    3foobar
  1144    4barfoo
  1145    5foo
  1146    6bar
  1147    7foobar
  1148    8barfoo
  1149    9foo
  1150    10bar
  1151    11foobar
  1152    12barfoo
  1153    13foo
  1154    14bar
  1155    15foobar
  1156    16barfoo
  1157    17foo
  1158    18bar
  1159    19foobar
  1160    20barfoo
  1161    21foo
  1162    22bar
  1163    23foobar
  1164    24barfoo
  1165    25foo
  1166    26bar
  1167    27foobar
  1168    28barfoo
  1169    29foo
  1170    30bar
  1171    31foobar
  1172    32barfoo
  1173    33foo
  1174    34bar
  1175    35foobar
  1176    36barfoo
  1177    37foo
  1178    38bar
  1179    39foobar
  1180    40barfoo
  1181    41foo
  1182    42bar
  1183    43foobar
  1184    44barfoo
  1185    45foo
  1186    46bar
  1187    47foobar
  1188    48barfoo
  1189    49foo
  1190    50bar
  1191    51foobar
  1192    52barfoo
  1193    53foo
  1194    54bar
  1195    55foobar
  1196    56barfoo
  1197    57foo
  1198    58bar
  1199    59foobar
  1200    60barfoo
  1201    61foo
  1202    62bar
  1203    63foobar
  1204    64barfoo
  1205    65foo
  1206    66bar
  1207    67foobar
  1208    68barfoo
  1209    69foo
  1210    70bar
  1211    71foobar
  1212    72barfoo
  1213    73foo
  1214    74bar
  1215    75foobar
  1216    76barfoo
  1217    77foo
  1218    78bar
  1219    79foobar
  1220    80barfoo
  1221    81foo
  1222    82bar
  1223    83foobar
  1224    84barfoo
  1225    85foo
  1226    86bar
  1227    87foobar
  1228    88barfoo
  1229    89foo
  1230    90bar
  1231    91foobar
  1232    92barfoo
  1233    93foo
  1234    94bar
  1235    95foobar
  1236    96barfoo
  1237    -----END RSA PRIVATE KEY-----
  1238  `))
  1239  			require.Error(t, err)
  1240  			assert.Nil(t, f)
  1241  			assert.Equal(t, "key-value delimiter not found: 1foo\n", err.Error())
  1242  		})
  1243  
  1244  		t.Run("allow unparsable sections", func(t *testing.T) {
  1245  			f, err := LoadSources(LoadOptions{
  1246  				AllowPythonMultilineValues: false,
  1247  				Insensitive:                true,
  1248  				UnparseableSections:        []string{"core_lesson", "comments"},
  1249  			}, []byte(`
  1250  Lesson_Location = 87
  1251  Lesson_Status = C
  1252  Score = 3
  1253  Time = 00:02:30
  1254  
  1255  [CORE_LESSON]
  1256  my lesson state data – 1111111111111111111000000000000000001110000
  1257  111111111111111111100000000000111000000000 – end my lesson state data
  1258  
  1259  [COMMENTS]
  1260  <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
  1261  `))
  1262  			require.NoError(t, err)
  1263  			require.NotNil(t, f)
  1264  
  1265  			assert.Equal(t, "3", f.Section("").Key("score").String())
  1266  			assert.Empty(t, f.Section("").Body())
  1267  			assert.Equal(t, `my lesson state data – 1111111111111111111000000000000000001110000
  1268  111111111111111111100000000000111000000000 – end my lesson state data`,
  1269  				f.Section("core_lesson").Body(),
  1270  			)
  1271  			assert.Equal(t, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`, f.Section("comments").Body())
  1272  
  1273  			t.Run("write out", func(t *testing.T) {
  1274  				var buf bytes.Buffer
  1275  				_, err := f.WriteTo(&buf)
  1276  				require.NoError(t, err)
  1277  				assert.Equal(t, `lesson_location = 87
  1278  lesson_status   = C
  1279  score           = 3
  1280  time            = 00:02:30
  1281  
  1282  [core_lesson]
  1283  my lesson state data – 1111111111111111111000000000000000001110000
  1284  111111111111111111100000000000111000000000 – end my lesson state data
  1285  
  1286  [comments]
  1287  <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
  1288  `,
  1289  					buf.String(),
  1290  				)
  1291  			})
  1292  
  1293  			t.Run("inverse case", func(t *testing.T) {
  1294  				_, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
  1295  [CORE_LESSON]
  1296  my lesson state data – 1111111111111111111000000000000000001110000
  1297  111111111111111111100000000000111000000000 – end my lesson state data`))
  1298  				require.Error(t, err)
  1299  			})
  1300  		})
  1301  
  1302  		t.Run("and false `SpaceBeforeInlineComment`", func(t *testing.T) {
  1303  			t.Run("cannot parse INI files containing `#` or `;` in value", func(t *testing.T) {
  1304  				f, err := LoadSources(
  1305  					LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: false},
  1306  					[]byte(`
  1307  [author]
  1308  NAME = U#n#k#n#w#o#n
  1309  GITHUB = U;n;k;n;w;o;n
  1310  `))
  1311  				require.NoError(t, err)
  1312  				require.NotNil(t, f)
  1313  				sec := f.Section("author")
  1314  				nameValue := sec.Key("NAME").String()
  1315  				githubValue := sec.Key("GITHUB").String()
  1316  				assert.Equal(t, "U", nameValue)
  1317  				assert.Equal(t, "U", githubValue)
  1318  			})
  1319  		})
  1320  
  1321  		t.Run("and true `SpaceBeforeInlineComment`", func(t *testing.T) {
  1322  			t.Run("can parse INI files containing `#` or `;` in value", func(t *testing.T) {
  1323  				f, err := LoadSources(
  1324  					LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: true},
  1325  					[]byte(`
  1326  [author]
  1327  NAME = U#n#k#n#w#o#n
  1328  GITHUB = U;n;k;n;w;o;n
  1329  `))
  1330  				require.NoError(t, err)
  1331  				require.NotNil(t, f)
  1332  				sec := f.Section("author")
  1333  				nameValue := sec.Key("NAME").String()
  1334  				githubValue := sec.Key("GITHUB").String()
  1335  				assert.Equal(t, "U#n#k#n#w#o#n", nameValue)
  1336  				assert.Equal(t, "U;n;k;n;w;o;n", githubValue)
  1337  			})
  1338  		})
  1339  	})
  1340  
  1341  	t.Run("with `ChildSectionDelimiter` ':'", func(t *testing.T) {
  1342  		t.Run("get all keys of parent sections", func(t *testing.T) {
  1343  			f := Empty(LoadOptions{ChildSectionDelimiter: ":"})
  1344  			require.NotNil(t, f)
  1345  
  1346  			k, err := f.Section("package").NewKey("NAME", "ini")
  1347  			require.NoError(t, err)
  1348  			assert.NotNil(t, k)
  1349  			k, err = f.Section("package").NewKey("VERSION", "v1")
  1350  			require.NoError(t, err)
  1351  			assert.NotNil(t, k)
  1352  			k, err = f.Section("package").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
  1353  			require.NoError(t, err)
  1354  			assert.NotNil(t, k)
  1355  
  1356  			keys := f.Section("package:sub:sub2").ParentKeys()
  1357  			names := []string{"NAME", "VERSION", "IMPORT_PATH"}
  1358  			assert.Equal(t, len(names), len(keys))
  1359  			for i, name := range names {
  1360  				assert.Equal(t, name, keys[i].Name())
  1361  			}
  1362  		})
  1363  
  1364  		t.Run("getting and setting values", func(t *testing.T) {
  1365  			f, err := LoadSources(LoadOptions{ChildSectionDelimiter: ":"}, fullConf)
  1366  			require.NoError(t, err)
  1367  			require.NotNil(t, f)
  1368  
  1369  			t.Run("get parent-keys that are available to the child section", func(t *testing.T) {
  1370  				parentKeys := f.Section("package:sub").ParentKeys()
  1371  				assert.NotNil(t, parentKeys)
  1372  				for _, k := range parentKeys {
  1373  					assert.Equal(t, "CLONE_URL", k.Name())
  1374  				}
  1375  			})
  1376  
  1377  			t.Run("get parent section value", func(t *testing.T) {
  1378  				assert.Equal(t, "https://gopkg.in/ini.v1", f.Section("package:sub").Key("CLONE_URL").String())
  1379  				assert.Equal(t, "https://gopkg.in/ini.v1", f.Section("package:fake:sub").Key("CLONE_URL").String())
  1380  			})
  1381  		})
  1382  
  1383  		t.Run("get child sections by parent name", func(t *testing.T) {
  1384  			f, err := LoadSources(LoadOptions{ChildSectionDelimiter: ":"}, []byte(`
  1385  [node]
  1386  [node:biz1]
  1387  [node:biz2]
  1388  [node.biz3]
  1389  [node.bizN]
  1390  `))
  1391  			require.NoError(t, err)
  1392  			require.NotNil(t, f)
  1393  
  1394  			children := f.ChildSections("node")
  1395  			names := []string{"node:biz1", "node:biz2"}
  1396  			assert.Equal(t, len(names), len(children))
  1397  			for i, name := range names {
  1398  				assert.Equal(t, name, children[i].Name())
  1399  			}
  1400  		})
  1401  	})
  1402  
  1403  	t.Run("ShortCircuit", func(t *testing.T) {
  1404  		t.Run("load the first available configuration, ignore other configuration", func(t *testing.T) {
  1405  			f, err := LoadSources(LoadOptions{ShortCircuit: true}, minimalConf, []byte(`key1 = value1`))
  1406  			require.NotNil(t, f)
  1407  			require.NoError(t, err)
  1408  			var buf bytes.Buffer
  1409  			_, err = f.WriteTo(&buf)
  1410  			require.NoError(t, err)
  1411  			assert.Equal(t, `[author]
  1412  E-MAIL = u@gogs.io
  1413  `,
  1414  				buf.String(),
  1415  			)
  1416  		})
  1417  
  1418  		t.Run("return an error when fail to load", func(t *testing.T) {
  1419  			f, err := LoadSources(LoadOptions{ShortCircuit: true}, notFoundConf, minimalConf)
  1420  			assert.Nil(t, f)
  1421  			require.Error(t, err)
  1422  		})
  1423  
  1424  		t.Run("used with Loose to ignore errors that the file does not exist", func(t *testing.T) {
  1425  			f, err := LoadSources(LoadOptions{ShortCircuit: true, Loose: true}, notFoundConf, minimalConf)
  1426  			require.NotNil(t, f)
  1427  			require.NoError(t, err)
  1428  			var buf bytes.Buffer
  1429  			_, err = f.WriteTo(&buf)
  1430  			require.NoError(t, err)
  1431  			assert.Equal(t, `[author]
  1432  E-MAIL = u@gogs.io
  1433  `,
  1434  				buf.String(),
  1435  			)
  1436  		})
  1437  
  1438  		t.Run("ensure all sources are loaded without ShortCircuit", func(t *testing.T) {
  1439  			f, err := LoadSources(LoadOptions{ShortCircuit: false}, minimalConf, []byte(`key1 = value1`))
  1440  			require.NotNil(t, f)
  1441  			require.NoError(t, err)
  1442  			var buf bytes.Buffer
  1443  			_, err = f.WriteTo(&buf)
  1444  			require.NoError(t, err)
  1445  			assert.Equal(t, `key1 = value1
  1446  
  1447  [author]
  1448  E-MAIL = u@gogs.io
  1449  `,
  1450  				buf.String(),
  1451  			)
  1452  		})
  1453  	})
  1454  }
  1455  
  1456  func Test_KeyValueDelimiters(t *testing.T) {
  1457  	t.Run("custom key-value delimiters", func(t *testing.T) {
  1458  		f, err := LoadSources(LoadOptions{
  1459  			KeyValueDelimiters: "?!",
  1460  		}, []byte(`
  1461  [section]
  1462  key1?value1
  1463  key2!value2
  1464  `))
  1465  		require.NoError(t, err)
  1466  		require.NotNil(t, f)
  1467  
  1468  		assert.Equal(t, "value1", f.Section("section").Key("key1").String())
  1469  		assert.Equal(t, "value2", f.Section("section").Key("key2").String())
  1470  	})
  1471  }
  1472  
  1473  func Test_PreserveSurroundedQuote(t *testing.T) {
  1474  	t.Run("preserve surrounded quote test", func(t *testing.T) {
  1475  		f, err := LoadSources(LoadOptions{
  1476  			PreserveSurroundedQuote: true,
  1477  		}, []byte(`
  1478  [section]
  1479  key1 = "value1"
  1480  key2 = value2
  1481  `))
  1482  		require.NoError(t, err)
  1483  		require.NotNil(t, f)
  1484  
  1485  		assert.Equal(t, "\"value1\"", f.Section("section").Key("key1").String())
  1486  		assert.Equal(t, "value2", f.Section("section").Key("key2").String())
  1487  	})
  1488  
  1489  	t.Run("preserve surrounded quote test inverse test", func(t *testing.T) {
  1490  		f, err := LoadSources(LoadOptions{
  1491  			PreserveSurroundedQuote: false,
  1492  		}, []byte(`
  1493  [section]
  1494  key1 = "value1"
  1495  key2 = value2
  1496  `))
  1497  		require.NoError(t, err)
  1498  		require.NotNil(t, f)
  1499  
  1500  		assert.Equal(t, "value1", f.Section("section").Key("key1").String())
  1501  		assert.Equal(t, "value2", f.Section("section").Key("key2").String())
  1502  	})
  1503  }
  1504  
  1505  type testData struct {
  1506  	Value1 string `ini:"value1"`
  1507  	Value2 string `ini:"value2"`
  1508  	Value3 string `ini:"value3"`
  1509  }
  1510  
  1511  func TestPythonMultiline(t *testing.T) {
  1512  	if runtime.GOOS == "windows" {
  1513  		t.Skip("Skipping testing on Windows")
  1514  	}
  1515  
  1516  	path := filepath.Join("testdata", "multiline.ini")
  1517  	f, err := LoadSources(LoadOptions{
  1518  		AllowPythonMultilineValues: true,
  1519  		ReaderBufferSize:           64 * 1024,
  1520  	}, path)
  1521  	require.NoError(t, err)
  1522  	require.NotNil(t, f)
  1523  	assert.Len(t, f.Sections(), 1)
  1524  
  1525  	defaultSection := f.Section("")
  1526  	assert.NotNil(t, f.Section(""))
  1527  
  1528  	var testData testData
  1529  	err = defaultSection.MapTo(&testData)
  1530  	require.NoError(t, err)
  1531  	assert.Equal(t, "some text here\n\tsome more text here\n\t\n\tthere is an empty line above and below\n\t", testData.Value1)
  1532  	assert.Equal(t, "there is an empty line above\n    that is not indented so it should not be part\n    of the value", testData.Value2)
  1533  	assert.Equal(t, `.
  1534   
  1535   Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eu consequat ac felis donec et odio pellentesque diam volutpat. Mauris commodo quis imperdiet massa tincidunt nunc. Interdum velit euismod in pellentesque. Nisl condimentum id venenatis a condimentum vitae sapien pellentesque. Nascetur ridiculus mus mauris vitae. Posuere urna nec tincidunt praesent semper feugiat. Lorem donec massa sapien faucibus et molestie ac feugiat sed. Ipsum dolor sit amet consectetur adipiscing elit. Enim sed faucibus turpis in eu mi. A diam sollicitudin tempor id. Quam nulla porttitor massa id neque aliquam vestibulum morbi blandit.
  1536   
  1537   Lectus sit amet est placerat in egestas. At risus viverra adipiscing at in tellus integer. Tristique senectus et netus et malesuada fames ac. In hac habitasse platea dictumst. Purus in mollis nunc sed. Pellentesque sit amet porttitor eget dolor morbi. Elit at imperdiet dui accumsan sit amet nulla. Cursus in hac habitasse platea dictumst. Bibendum arcu vitae elementum curabitur. Faucibus ornare suspendisse sed nisi lacus. In vitae turpis massa sed. Libero nunc consequat interdum varius sit amet. Molestie a iaculis at erat pellentesque.
  1538   
  1539   Dui faucibus in ornare quam viverra orci sagittis eu. Purus in mollis nunc sed id semper. Sed arcu non odio euismod lacinia at. Quis commodo odio aenean sed adipiscing diam donec. Quisque id diam vel quam elementum pulvinar. Lorem ipsum dolor sit amet. Purus ut faucibus pulvinar elementum integer enim neque volutpat ac. Fermentum posuere urna nec tincidunt praesent semper feugiat nibh sed. Gravida rutrum quisque non tellus orci. Ipsum dolor sit amet consectetur adipiscing elit pellentesque habitant. Et sollicitudin ac orci phasellus egestas tellus rutrum tellus pellentesque. Eget gravida cum sociis natoque penatibus et magnis. Elementum eu facilisis sed odio morbi quis commodo. Mollis nunc sed id semper risus in hendrerit gravida rutrum. Lorem dolor sed viverra ipsum.
  1540   
  1541   Pellentesque adipiscing commodo elit at imperdiet dui accumsan sit amet. Justo eget magna fermentum iaculis eu non diam. Condimentum mattis pellentesque id nibh tortor id aliquet lectus. Tellus molestie nunc non blandit massa enim. Mauris ultrices eros in cursus turpis. Purus viverra accumsan in nisl nisi scelerisque. Quis lectus nulla at volutpat. Purus ut faucibus pulvinar elementum integer enim. In pellentesque massa placerat duis ultricies lacus sed turpis. Elit sed vulputate mi sit amet mauris commodo. Tellus elementum sagittis vitae et. Duis tristique sollicitudin nibh sit amet commodo nulla facilisi nullam. Lectus vestibulum mattis ullamcorper velit sed ullamcorper morbi tincidunt ornare. Libero id faucibus nisl tincidunt eget nullam. Mattis aliquam faucibus purus in massa tempor. Fames ac turpis egestas sed tempus urna. Gravida in fermentum et sollicitudin ac orci phasellus egestas.
  1542   
  1543   Blandit turpis cursus in hac habitasse. Sed id semper risus in. Amet porttitor eget dolor morbi non arcu. Rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt. Ut morbi tincidunt augue interdum velit. Lorem mollis aliquam ut porttitor leo a. Nunc eget lorem dolor sed viverra. Scelerisque mauris pellentesque pulvinar pellentesque. Elit at imperdiet dui accumsan sit amet. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Laoreet non curabitur gravida arcu ac tortor dignissim. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae purus. Lacus sed viverra tellus in hac habitasse platea dictumst vestibulum. Viverra adipiscing at in tellus. Duis at tellus at urna condimentum. Eget gravida cum sociis natoque penatibus et magnis dis parturient. Pharetra massa massa ultricies mi quis hendrerit.
  1544   
  1545   Mauris pellentesque pulvinar pellentesque habitant morbi tristique. Maecenas volutpat blandit aliquam etiam. Sed turpis tincidunt id aliquet. Eget duis at tellus at urna condimentum. Pellentesque habitant morbi tristique senectus et. Amet aliquam id diam maecenas. Volutpat est velit egestas dui id. Vulputate eu scelerisque felis imperdiet proin fermentum leo vel orci. Massa sed elementum tempus egestas sed sed risus pretium. Quam quisque id diam vel quam elementum pulvinar etiam non. Sapien faucibus et molestie ac. Ipsum dolor sit amet consectetur adipiscing. Viverra orci sagittis eu volutpat. Leo urna molestie at elementum. Commodo viverra maecenas accumsan lacus. Non sodales neque sodales ut etiam sit amet. Habitant morbi tristique senectus et netus et malesuada fames. Habitant morbi tristique senectus et netus et malesuada. Blandit aliquam etiam erat velit scelerisque in. Varius duis at consectetur lorem donec massa sapien faucibus et.
  1546   
  1547   Augue mauris augue neque gravida in. Odio ut sem nulla pharetra diam sit amet nisl suscipit. Nulla aliquet enim tortor at auctor urna nunc id. Morbi tristique senectus et netus et malesuada fames ac. Quam id leo in vitae turpis massa sed elementum tempus. Ipsum faucibus vitae aliquet nec ullamcorper sit amet risus nullam. Maecenas volutpat blandit aliquam etiam erat velit scelerisque in. Sagittis nisl rhoncus mattis rhoncus urna neque viverra justo. Massa tempor nec feugiat nisl pretium. Vulputate sapien nec sagittis aliquam malesuada bibendum arcu vitae elementum. Enim lobortis scelerisque fermentum dui faucibus in ornare. Faucibus ornare suspendisse sed nisi lacus. Morbi tristique senectus et netus et malesuada fames. Malesuada pellentesque elit eget gravida cum sociis natoque penatibus et. Dictum non consectetur a erat nam at. Leo urna molestie at elementum eu facilisis sed odio morbi. Quam id leo in vitae turpis massa. Neque egestas congue quisque egestas diam in arcu. Varius morbi enim nunc faucibus a pellentesque sit. Aliquet enim tortor at auctor urna.
  1548   
  1549   Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique. Luctus accumsan tortor posuere ac. Eu ultrices vitae auctor eu augue ut lectus arcu bibendum. Pretium nibh ipsum consequat nisl vel pretium lectus. Aliquam etiam erat velit scelerisque in dictum. Sem et tortor consequat id porta nibh venenatis cras sed. A scelerisque purus semper eget duis at tellus at urna. At auctor urna nunc id. Ornare quam viverra orci sagittis eu volutpat odio. Nisl purus in mollis nunc sed id semper. Ornare suspendisse sed nisi lacus sed. Consectetur lorem donec massa sapien faucibus et. Ipsum dolor sit amet consectetur adipiscing elit ut. Porta nibh venenatis cras sed. Dignissim diam quis enim lobortis scelerisque. Quam nulla porttitor massa id. Tellus molestie nunc non blandit massa.
  1550   
  1551   Malesuada fames ac turpis egestas. Suscipit tellus mauris a diam maecenas. Turpis in eu mi bibendum neque egestas. Venenatis tellus in metus vulputate eu scelerisque felis imperdiet. Quis imperdiet massa tincidunt nunc pulvinar sapien et. Urna duis convallis convallis tellus id. Velit egestas dui id ornare arcu odio. Consectetur purus ut faucibus pulvinar elementum integer enim neque. Aenean sed adipiscing diam donec adipiscing tristique. Tortor aliquam nulla facilisi cras fermentum odio eu. Diam in arcu cursus euismod quis viverra nibh cras.
  1552   
  1553   Id ornare arcu odio ut sem. Arcu dictum varius duis at consectetur lorem donec massa sapien. Proin libero nunc consequat interdum varius sit. Ut eu sem integer vitae justo. Vitae elementum curabitur vitae nunc. Diam quam nulla porttitor massa. Lectus mauris ultrices eros in cursus turpis massa tincidunt dui. Natoque penatibus et magnis dis parturient montes. Pellentesque habitant morbi tristique senectus et netus et malesuada fames. Libero nunc consequat interdum varius sit. Rhoncus dolor purus non enim praesent. Pellentesque sit amet porttitor eget. Nibh tortor id aliquet lectus proin nibh. Fermentum iaculis eu non diam phasellus vestibulum lorem sed.
  1554   
  1555   Eu feugiat pretium nibh ipsum consequat nisl vel pretium lectus. Habitant morbi tristique senectus et netus et malesuada fames ac. Urna condimentum mattis pellentesque id. Lorem sed risus ultricies tristique nulla aliquet enim tortor at. Ipsum dolor sit amet consectetur adipiscing elit. Convallis a cras semper auctor neque vitae tempus quam. A diam sollicitudin tempor id eu nisl nunc mi ipsum. Maecenas sed enim ut sem viverra aliquet eget. Massa enim nec dui nunc mattis enim. Nam aliquam sem et tortor consequat. Adipiscing commodo elit at imperdiet dui accumsan sit amet nulla. Nullam eget felis eget nunc lobortis. Mauris a diam maecenas sed enim ut sem viverra. Ornare massa eget egestas purus. In hac habitasse platea dictumst. Ut tortor pretium viverra suspendisse potenti nullam ac tortor. Nisl nunc mi ipsum faucibus. At varius vel pharetra vel. Mauris ultrices eros in cursus turpis massa tincidunt.`,
  1556  		testData.Value3,
  1557  	)
  1558  }
  1559  
  1560  func TestPythonMultiline_EOF(t *testing.T) {
  1561  	if runtime.GOOS == "windows" {
  1562  		t.Skip("Skipping testing on Windows")
  1563  	}
  1564  
  1565  	path := filepath.Join("testdata", "multiline_eof.ini")
  1566  	f, err := LoadSources(LoadOptions{
  1567  		AllowPythonMultilineValues: true,
  1568  		ReaderBufferSize:           64 * 1024,
  1569  	}, path)
  1570  	require.NoError(t, err)
  1571  	require.NotNil(t, f)
  1572  	assert.Len(t, f.Sections(), 1)
  1573  
  1574  	defaultSection := f.Section("")
  1575  	assert.NotNil(t, f.Section(""))
  1576  
  1577  	var testData testData
  1578  	err = defaultSection.MapTo(&testData)
  1579  	require.NoError(t, err)
  1580  	assert.Equal(t, "some text here\n\tsome more text here 2", testData.Value1)
  1581  }
  1582  
  1583  func Test_NestedValuesSpanningSections(t *testing.T) {
  1584  	t.Run("basic nested value", func(t *testing.T) {
  1585  		f, err := LoadSources(LoadOptions{
  1586  			AllowNestedValues: true,
  1587  		}, []byte(`
  1588  [section]
  1589  key1 = value1
  1590  key2 =
  1591    nested1 = nestedvalue1
  1592  `))
  1593  		require.NoError(t, err)
  1594  		require.NotNil(t, f)
  1595  
  1596  		assert.Equal(t, "value1", f.Section("section").Key("key1").String())
  1597  		assert.Equal(t, "", f.Section("section").Key("key2").String())
  1598  		assert.Equal(t, []string{"nested1 = nestedvalue1"}, f.Section("section").Key("key2").NestedValues())
  1599  	})
  1600  
  1601  	t.Run("no nested values", func(t *testing.T) {
  1602  		f, err := LoadSources(LoadOptions{
  1603  			AllowNestedValues: true,
  1604  		}, []byte(`
  1605  [section]
  1606  key1 = value1
  1607  key2 =
  1608  `))
  1609  		require.NoError(t, err)
  1610  		require.NotNil(t, f)
  1611  
  1612  		assert.Equal(t, "value1", f.Section("section").Key("key1").String())
  1613  		assert.Equal(t, "", f.Section("section").Key("key2").String())
  1614  	})
  1615  
  1616  	t.Run("no nested values and following sections", func(t *testing.T) {
  1617  		f, err := LoadSources(LoadOptions{
  1618  			AllowNestedValues: true,
  1619  		}, []byte(`
  1620  [section]
  1621  key1 = value1
  1622  key2 =
  1623  
  1624  [section2]
  1625  key3 = value3
  1626  `))
  1627  		require.NoError(t, err)
  1628  		require.NotNil(t, f)
  1629  
  1630  		assert.Equal(t, "value1", f.Section("section").Key("key1").String())
  1631  		assert.Equal(t, "", f.Section("section").Key("key2").String())
  1632  		assert.Equal(t, "value3", f.Section("section2").Key("key3").String())
  1633  	})
  1634  
  1635  	t.Run("no nested values and following sections with indentation", func(t *testing.T) {
  1636  		f, err := LoadSources(LoadOptions{
  1637  			AllowNestedValues: true,
  1638  		}, []byte(`
  1639  [section]
  1640  key1 = value1
  1641  key2 =
  1642  
  1643  [section2]
  1644    key3 = value3
  1645  `))
  1646  		require.NoError(t, err)
  1647  		require.NotNil(t, f)
  1648  
  1649  		assert.Equal(t, "value1", f.Section("section").Key("key1").String())
  1650  		assert.Equal(t, "", f.Section("section").Key("key2").String())
  1651  		assert.Equal(t, "value3", f.Section("section2").Key("key3").String())
  1652  	})
  1653  }
  1654  

View as plain text