...

Source file src/edge-infra.dev/pkg/lib/ini/ini_test.go

Documentation: edge-infra.dev/pkg/lib/ini

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

View as plain text