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
15package ini
16
17import (
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
29const (
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
53var update = flag.Bool("update", false, "Update .golden files")
54
55func 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]
108long_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]
123long_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
228func 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
239func 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]
252e-mail = u@gogs.io
253
254`,
255				buf.String(),
256			)
257		})
258
259		t.Run("inverse case", func(t *testing.T) {
260			f, err := Load(minimalConf)
261			require.NoError(t, err)
262			require.NotNil(t, f)
263
264			assert.Empty(t, f.Section("Author").Key("e-mail").String())
265		})
266	})
267
268	// Ref: https://github.com/go-ini/ini/issues/198
269	t.Run("insensitive load with default section", func(t *testing.T) {
270		f, err := InsensitiveLoad([]byte(`
271user = unknwon
272[profile]
273email = unknwon@local
274`))
275		require.NoError(t, err)
276		require.NotNil(t, f)
277
278		assert.Equal(t, "unknwon", f.Section(DefaultSection).Key("user").String())
279	})
280}
281
282func TestLoadSources(t *testing.T) {
283	t.Run("with true `AllowPythonMultilineValues`", func(t *testing.T) {
284		t.Run("ignore nonexistent files", func(t *testing.T) {
285			f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true, Loose: true}, notFoundConf, minimalConf)
286			require.NoError(t, err)
287			require.NotNil(t, f)
288
289			t.Run("inverse case", func(t *testing.T) {
290				_, err = LoadSources(LoadOptions{AllowPythonMultilineValues: true}, notFoundConf)
291				require.Error(t, err)
292			})
293		})
294
295		t.Run("insensitive to section and key names", func(t *testing.T) {
296			f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true, Insensitive: true}, minimalConf)
297			require.NoError(t, err)
298			require.NotNil(t, f)
299
300			assert.Equal(t, "u@gogs.io", f.Section("Author").Key("e-mail").String())
301
302			t.Run("write out", func(t *testing.T) {
303				var buf bytes.Buffer
304				_, err := f.WriteTo(&buf)
305				require.NoError(t, err)
306				assert.Equal(t, `[author]
307e-mail = u@gogs.io
308
309`,
310					buf.String(),
311				)
312			})
313
314			t.Run("inverse case", func(t *testing.T) {
315				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, minimalConf)
316				require.NoError(t, err)
317				require.NotNil(t, f)
318
319				assert.Empty(t, f.Section("Author").Key("e-mail").String())
320			})
321		})
322
323		t.Run("insensitive to sections and sensitive to key names", func(t *testing.T) {
324			f, err := LoadSources(LoadOptions{InsensitiveSections: true}, minimalConf)
325			require.NoError(t, err)
326			require.NotNil(t, f)
327
328			assert.Equal(t, "u@gogs.io", f.Section("Author").Key("E-MAIL").String())
329
330			t.Run("write out", func(t *testing.T) {
331				var buf bytes.Buffer
332				_, err := f.WriteTo(&buf)
333				require.NoError(t, err)
334				assert.Equal(t, `[author]
335E-MAIL = u@gogs.io
336
337`,
338					buf.String(),
339				)
340			})
341
342			t.Run("inverse case", func(t *testing.T) {
343				f, err := LoadSources(LoadOptions{}, minimalConf)
344				require.NoError(t, err)
345				require.NotNil(t, f)
346
347				assert.Empty(t, f.Section("Author").Key("e-mail").String())
348			})
349		})
350
351		t.Run("sensitive to sections and insensitive to key names", func(t *testing.T) {
352			f, err := LoadSources(LoadOptions{InsensitiveKeys: true}, minimalConf)
353			require.NoError(t, err)
354			require.NotNil(t, f)
355
356			assert.Equal(t, "u@gogs.io", f.Section("author").Key("e-mail").String())
357
358			t.Run("write out", func(t *testing.T) {
359				var buf bytes.Buffer
360				_, err := f.WriteTo(&buf)
361				require.NoError(t, err)
362				assert.Equal(t, `[author]
363e-mail = u@gogs.io
364
365`,
366					buf.String(),
367				)
368			})
369
370			t.Run("inverse case", func(t *testing.T) {
371				f, err := LoadSources(LoadOptions{}, minimalConf)
372				require.NoError(t, err)
373				require.NotNil(t, f)
374
375				assert.Empty(t, f.Section("Author").Key("e-mail").String())
376			})
377		})
378
379		t.Run("ignore continuation lines", func(t *testing.T) {
380			f, err := LoadSources(LoadOptions{
381				AllowPythonMultilineValues: true,
382				IgnoreContinuation:         true,
383			}, []byte(`
384key1=a\b\
385key2=c\d\
386key3=value`))
387			require.NoError(t, err)
388			require.NotNil(t, f)
389
390			assert.Equal(t, `a\b\`, f.Section("").Key("key1").String())
391			assert.Equal(t, `c\d\`, f.Section("").Key("key2").String())
392			assert.Equal(t, "value", f.Section("").Key("key3").String())
393
394			t.Run("inverse case", func(t *testing.T) {
395				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
396key1=a\b\
397key2=c\d\`))
398				require.NoError(t, err)
399				require.NotNil(t, f)
400
401				assert.Equal(t, `a\bkey2=c\d`, f.Section("").Key("key1").String())
402			})
403		})
404
405		t.Run("ignore inline comments", func(t *testing.T) {
406			f, err := LoadSources(LoadOptions{
407				AllowPythonMultilineValues: true,
408				IgnoreInlineComment:        true,
409			}, []byte(`
410key1=value ;comment
411key2=value2 #comment2
412key3=val#ue #comment3`))
413			require.NoError(t, err)
414			require.NotNil(t, f)
415
416			assert.Equal(t, `value ;comment`, f.Section("").Key("key1").String())
417			assert.Equal(t, `value2 #comment2`, f.Section("").Key("key2").String())
418			assert.Equal(t, `val#ue #comment3`, f.Section("").Key("key3").String())
419
420			t.Run("inverse case", func(t *testing.T) {
421				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
422key1=value ;comment
423key2=value2 #comment2`))
424				require.NoError(t, err)
425				require.NotNil(t, f)
426
427				assert.Equal(t, `value`, f.Section("").Key("key1").String())
428				assert.Equal(t, `;comment`, f.Section("").Key("key1").Comment)
429				assert.Equal(t, `value2`, f.Section("").Key("key2").String())
430				assert.Equal(t, `#comment2`, f.Section("").Key("key2").Comment)
431			})
432		})
433
434		t.Run("skip unrecognizable lines", func(t *testing.T) {
435			f, err := LoadSources(LoadOptions{
436				SkipUnrecognizableLines: true,
437			}, []byte(`
438GenerationDepth: 13
439
440BiomeRarityScale: 100
441
442################
443# Biome Groups #
444################
445
446BiomeGroup(NormalBiomes, 3, 99, RoofedForestEnchanted, ForestSakura, FloatingJungle
447BiomeGroup(IceBiomes, 4, 85, Ice Plains)
448`))
449			require.NoError(t, err)
450			require.NotNil(t, f)
451
452			assert.Equal(t, "13", f.Section("").Key("GenerationDepth").String())
453			assert.Equal(t, "100", f.Section("").Key("BiomeRarityScale").String())
454			assert.False(t, f.Section("").HasKey("BiomeGroup"))
455		})
456
457		t.Run("allow boolean type keys", func(t *testing.T) {
458			f, err := LoadSources(LoadOptions{
459				AllowPythonMultilineValues: true,
460				AllowBooleanKeys:           true,
461			}, []byte(`
462key1=hello
463#key2
464key3`))
465			require.NoError(t, err)
466			require.NotNil(t, f)
467
468			assert.Equal(t, []string{"key1", "key3"}, f.Section("").KeyStrings())
469			assert.True(t, f.Section("").Key("key3").MustBool(false))
470
471			t.Run("write out", func(t *testing.T) {
472				var buf bytes.Buffer
473				_, err := f.WriteTo(&buf)
474				require.NoError(t, err)
475				assert.Equal(t, `key1 = hello
476# key2
477key3
478`,
479					buf.String(),
480				)
481			})
482
483			t.Run("inverse case", func(t *testing.T) {
484				_, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
485key1=hello
486#key2
487key3`))
488				require.Error(t, err)
489			})
490		})
491
492		t.Run("allow shadow keys", func(t *testing.T) {
493			f, err := LoadSources(LoadOptions{AllowShadows: true, AllowPythonMultilineValues: true}, []byte(`
494[remote "origin"]
495url = https://github.com/Antergone/test1.git
496url = https://github.com/Antergone/test2.git
497fetch = +refs/heads/*:refs/remotes/origin/*`))
498			require.NoError(t, err)
499			require.NotNil(t, f)
500
501			assert.Equal(t, "https://github.com/Antergone/test1.git", f.Section(`remote "origin"`).Key("url").String())
502			assert.Equal(
503				t,
504				[]string{
505					"https://github.com/Antergone/test1.git",
506					"https://github.com/Antergone/test2.git",
507				},
508				f.Section(`remote "origin"`).Key("url").ValueWithShadows(),
509			)
510			assert.Equal(t, "+refs/heads/*:refs/remotes/origin/*", f.Section(`remote "origin"`).Key("fetch").String())
511
512			t.Run("write out", func(t *testing.T) {
513				var buf bytes.Buffer
514				_, err := f.WriteTo(&buf)
515				require.NoError(t, err)
516				assert.Equal(t, `[remote "origin"]
517url   = https://github.com/Antergone/test1.git
518url   = https://github.com/Antergone/test2.git
519fetch = +refs/heads/*:refs/remotes/origin/*
520
521`,
522					buf.String(),
523				)
524			})
525
526			t.Run("inverse case", func(t *testing.T) {
527				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
528[remote "origin"]
529url = https://github.com/Antergone/test1.git
530url = https://github.com/Antergone/test2.git`))
531				require.NoError(t, err)
532				require.NotNil(t, f)
533
534				assert.Equal(t, "https://github.com/Antergone/test2.git", f.Section(`remote "origin"`).Key("url").String())
535			})
536		})
537
538		t.Run("unescape double quotes inside value", func(t *testing.T) {
539			f, err := LoadSources(LoadOptions{
540				AllowPythonMultilineValues: true,
541				UnescapeValueDoubleQuotes:  true,
542			}, []byte(`
543create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
544			require.NoError(t, err)
545			require.NotNil(t, f)
546
547			assert.Equal(t, `创建了仓库 <a href="%s">%s</a>`, f.Section("").Key("create_repo").String())
548
549			t.Run("inverse case", func(t *testing.T) {
550				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
551create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
552				require.NoError(t, err)
553				require.NotNil(t, f)
554
555				assert.Equal(t, `"创建了仓库 <a href=\"%s\">%s</a>"`, f.Section("").Key("create_repo").String())
556			})
557		})
558
559		t.Run("unescape comment symbols inside value", func(t *testing.T) {
560			f, err := LoadSources(LoadOptions{
561				AllowPythonMultilineValues:  true,
562				IgnoreInlineComment:         true,
563				UnescapeValueCommentSymbols: true,
564			}, []byte(`
565key = test value <span style="color: %s\; background: %s">more text</span>
566`))
567			require.NoError(t, err)
568			require.NotNil(t, f)
569
570			assert.Equal(t, `test value <span style="color: %s; background: %s">more text</span>`, f.Section("").Key("key").String())
571		})
572
573		t.Run("can parse small python-compatible INI files", func(t *testing.T) {
574			f, err := LoadSources(LoadOptions{
575				AllowPythonMultilineValues: true,
576				Insensitive:                true,
577				UnparseableSections:        []string{"core_lesson", "comments"},
578			}, []byte(`
579[long]
580long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
581  foo
582  bar
583  foobar
584  barfoo
585  -----END RSA PRIVATE KEY-----
586multiline_list =
587  first
588  second
589  third
590`))
591			require.NoError(t, err)
592			require.NotNil(t, f)
593
594			assert.Equal(t, "-----BEGIN RSA PRIVATE KEY-----\nfoo\nbar\nfoobar\nbarfoo\n-----END RSA PRIVATE KEY-----", f.Section("long").Key("long_rsa_private_key").String())
595			assert.Equal(t, "\nfirst\nsecond\nthird", f.Section("long").Key("multiline_list").String())
596		})
597
598		t.Run("can parse big python-compatible INI files", func(t *testing.T) {
599			f, err := LoadSources(LoadOptions{
600				AllowPythonMultilineValues: true,
601				Insensitive:                true,
602				UnparseableSections:        []string{"core_lesson", "comments"},
603			}, []byte(`
604[long]
605long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
606   1foo
607   2bar
608   3foobar
609   4barfoo
610   5foo
611   6bar
612   7foobar
613   8barfoo
614   9foo
615   10bar
616   11foobar
617   12barfoo
618   13foo
619   14bar
620   15foobar
621   16barfoo
622   17foo
623   18bar
624   19foobar
625   20barfoo
626   21foo
627   22bar
628   23foobar
629   24barfoo
630   25foo
631   26bar
632   27foobar
633   28barfoo
634   29foo
635   30bar
636   31foobar
637   32barfoo
638   33foo
639   34bar
640   35foobar
641   36barfoo
642   37foo
643   38bar
644   39foobar
645   40barfoo
646   41foo
647   42bar
648   43foobar
649   44barfoo
650   45foo
651   46bar
652   47foobar
653   48barfoo
654   49foo
655   50bar
656   51foobar
657   52barfoo
658   53foo
659   54bar
660   55foobar
661   56barfoo
662   57foo
663   58bar
664   59foobar
665   60barfoo
666   61foo
667   62bar
668   63foobar
669   64barfoo
670   65foo
671   66bar
672   67foobar
673   68barfoo
674   69foo
675   70bar
676   71foobar
677   72barfoo
678   73foo
679   74bar
680   75foobar
681   76barfoo
682   77foo
683   78bar
684   79foobar
685   80barfoo
686   81foo
687   82bar
688   83foobar
689   84barfoo
690   85foo
691   86bar
692   87foobar
693   88barfoo
694   89foo
695   90bar
696   91foobar
697   92barfoo
698   93foo
699   94bar
700   95foobar
701   96barfoo
702   -----END RSA PRIVATE KEY-----
703`))
704			require.NoError(t, err)
705			require.NotNil(t, f)
706
707			assert.Equal(t, `-----BEGIN RSA PRIVATE KEY-----
7081foo
7092bar
7103foobar
7114barfoo
7125foo
7136bar
7147foobar
7158barfoo
7169foo
71710bar
71811foobar
71912barfoo
72013foo
72114bar
72215foobar
72316barfoo
72417foo
72518bar
72619foobar
72720barfoo
72821foo
72922bar
73023foobar
73124barfoo
73225foo
73326bar
73427foobar
73528barfoo
73629foo
73730bar
73831foobar
73932barfoo
74033foo
74134bar
74235foobar
74336barfoo
74437foo
74538bar
74639foobar
74740barfoo
74841foo
74942bar
75043foobar
75144barfoo
75245foo
75346bar
75447foobar
75548barfoo
75649foo
75750bar
75851foobar
75952barfoo
76053foo
76154bar
76255foobar
76356barfoo
76457foo
76558bar
76659foobar
76760barfoo
76861foo
76962bar
77063foobar
77164barfoo
77265foo
77366bar
77467foobar
77568barfoo
77669foo
77770bar
77871foobar
77972barfoo
78073foo
78174bar
78275foobar
78376barfoo
78477foo
78578bar
78679foobar
78780barfoo
78881foo
78982bar
79083foobar
79184barfoo
79285foo
79386bar
79487foobar
79588barfoo
79689foo
79790bar
79891foobar
79992barfoo
80093foo
80194bar
80295foobar
80396barfoo
804-----END RSA PRIVATE KEY-----`,
805				f.Section("long").Key("long_rsa_private_key").String(),
806			)
807		})
808
809		t.Run("allow unparsable sections", func(t *testing.T) {
810			f, err := LoadSources(LoadOptions{
811				AllowPythonMultilineValues: true,
812				Insensitive:                true,
813				UnparseableSections:        []string{"core_lesson", "comments"},
814			}, []byte(`
815Lesson_Location = 87
816Lesson_Status = C
817Score = 3
818Time = 00:02:30
819
820[CORE_LESSON]
821my lesson state data – 1111111111111111111000000000000000001110000
822111111111111111111100000000000111000000000 – end my lesson state data
823
824[COMMENTS]
825<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
826			require.NoError(t, err)
827			require.NotNil(t, f)
828
829			assert.Equal(t, "3", f.Section("").Key("score").String())
830			assert.Empty(t, f.Section("").Body())
831			assert.Equal(t, `my lesson state data – 1111111111111111111000000000000000001110000
832111111111111111111100000000000111000000000 – end my lesson state data`,
833				f.Section("core_lesson").Body(),
834			)
835			assert.Equal(t, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`, f.Section("comments").Body())
836
837			t.Run("write out", func(t *testing.T) {
838				var buf bytes.Buffer
839				_, err := f.WriteTo(&buf)
840				require.NoError(t, err)
841				assert.Equal(t, `lesson_location = 87
842lesson_status   = C
843score           = 3
844time            = 00:02:30
845
846[core_lesson]
847my lesson state data – 1111111111111111111000000000000000001110000
848111111111111111111100000000000111000000000 – end my lesson state data
849
850[comments]
851<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
852`,
853					buf.String(),
854				)
855			})
856
857			t.Run("inverse case", func(t *testing.T) {
858				_, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
859[CORE_LESSON]
860my lesson state data – 1111111111111111111000000000000000001110000
861111111111111111111100000000000111000000000 – end my lesson state data`))
862				require.Error(t, err)
863			})
864		})
865
866		t.Run("and false `SpaceBeforeInlineComment`", func(t *testing.T) {
867			t.Run("cannot parse INI files containing `#` or `;` in value", func(t *testing.T) {
868				f, err := LoadSources(
869					LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: false},
870					[]byte(`
871[author]
872NAME = U#n#k#n#w#o#n
873GITHUB = U;n;k;n;w;o;n
874`))
875				require.NoError(t, err)
876				require.NotNil(t, f)
877				sec := f.Section("author")
878				nameValue := sec.Key("NAME").String()
879				githubValue := sec.Key("GITHUB").String()
880				assert.Equal(t, "U", nameValue)
881				assert.Equal(t, "U", githubValue)
882			})
883		})
884
885		t.Run("and true `SpaceBeforeInlineComment`", func(t *testing.T) {
886			t.Run("can parse INI files containing `#` or `;` in value", func(t *testing.T) {
887				f, err := LoadSources(
888					LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: true},
889					[]byte(`
890[author]
891NAME = U#n#k#n#w#o#n
892GITHUB = U;n;k;n;w;o;n
893`))
894				require.NoError(t, err)
895				require.NotNil(t, f)
896				sec := f.Section("author")
897				nameValue := sec.Key("NAME").String()
898				githubValue := sec.Key("GITHUB").String()
899				assert.Equal(t, "U#n#k#n#w#o#n", nameValue)
900				assert.Equal(t, "U;n;k;n;w;o;n", githubValue)
901			})
902		})
903	})
904
905	t.Run("with false `AllowPythonMultilineValues`", func(t *testing.T) {
906		t.Run("ignore nonexistent files", func(t *testing.T) {
907			f, err := LoadSources(LoadOptions{
908				AllowPythonMultilineValues: false,
909				Loose:                      true,
910			}, notFoundConf, minimalConf)
911			require.NoError(t, err)
912			require.NotNil(t, f)
913
914			t.Run("inverse case", func(t *testing.T) {
915				_, err = LoadSources(LoadOptions{
916					AllowPythonMultilineValues: false,
917				}, notFoundConf)
918				require.Error(t, err)
919			})
920		})
921
922		t.Run("insensitive to section and key names", func(t *testing.T) {
923			f, err := LoadSources(LoadOptions{
924				AllowPythonMultilineValues: false,
925				Insensitive:                true,
926			}, minimalConf)
927			require.NoError(t, err)
928			require.NotNil(t, f)
929
930			assert.Equal(t, "u@gogs.io", f.Section("Author").Key("e-mail").String())
931
932			t.Run("write out", func(t *testing.T) {
933				var buf bytes.Buffer
934				_, err := f.WriteTo(&buf)
935				require.NoError(t, err)
936				assert.Equal(t, `[author]
937e-mail = u@gogs.io
938
939`,
940					buf.String(),
941				)
942			})
943
944			t.Run("inverse case", func(t *testing.T) {
945				f, err := LoadSources(LoadOptions{
946					AllowPythonMultilineValues: false,
947				}, minimalConf)
948				require.NoError(t, err)
949				require.NotNil(t, f)
950
951				assert.Empty(t, f.Section("Author").Key("e-mail").String())
952			})
953		})
954
955		t.Run("ignore continuation lines", func(t *testing.T) {
956			f, err := LoadSources(LoadOptions{
957				AllowPythonMultilineValues: false,
958				IgnoreContinuation:         true,
959			}, []byte(`
960key1=a\b\
961key2=c\d\
962key3=value`))
963			require.NoError(t, err)
964			require.NotNil(t, f)
965
966			assert.Equal(t, `a\b\`, f.Section("").Key("key1").String())
967			assert.Equal(t, `c\d\`, f.Section("").Key("key2").String())
968			assert.Equal(t, "value", f.Section("").Key("key3").String())
969
970			t.Run("inverse case", func(t *testing.T) {
971				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
972key1=a\b\
973key2=c\d\`))
974				require.NoError(t, err)
975				require.NotNil(t, f)
976
977				assert.Equal(t, `a\bkey2=c\d`, f.Section("").Key("key1").String())
978			})
979		})
980
981		t.Run("ignore inline comments", func(t *testing.T) {
982			f, err := LoadSources(LoadOptions{
983				AllowPythonMultilineValues: false,
984				IgnoreInlineComment:        true,
985			}, []byte(`
986key1=value ;comment
987key2=value2 #comment2
988key3=val#ue #comment3`))
989			require.NoError(t, err)
990			require.NotNil(t, f)
991
992			assert.Equal(t, `value ;comment`, f.Section("").Key("key1").String())
993			assert.Equal(t, `value2 #comment2`, f.Section("").Key("key2").String())
994			assert.Equal(t, `val#ue #comment3`, f.Section("").Key("key3").String())
995
996			t.Run("inverse case", func(t *testing.T) {
997				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
998key1=value ;comment
999key2=value2 #comment2`))
1000				require.NoError(t, err)
1001				require.NotNil(t, f)
1002
1003				assert.Equal(t, `value`, f.Section("").Key("key1").String())
1004				assert.Equal(t, `;comment`, f.Section("").Key("key1").Comment)
1005				assert.Equal(t, `value2`, f.Section("").Key("key2").String())
1006				assert.Equal(t, `#comment2`, f.Section("").Key("key2").Comment)
1007			})
1008		})
1009
1010		t.Run("allow boolean type keys", func(t *testing.T) {
1011			f, err := LoadSources(LoadOptions{
1012				AllowPythonMultilineValues: false,
1013				AllowBooleanKeys:           true,
1014			}, []byte(`
1015key1=hello
1016#key2
1017key3`))
1018			require.NoError(t, err)
1019			require.NotNil(t, f)
1020
1021			assert.Equal(t, []string{"key1", "key3"}, f.Section("").KeyStrings())
1022			assert.True(t, f.Section("").Key("key3").MustBool(false))
1023
1024			t.Run("write out", func(t *testing.T) {
1025				var buf bytes.Buffer
1026				_, err := f.WriteTo(&buf)
1027				require.NoError(t, err)
1028				assert.Equal(t, `key1 = hello
1029# key2
1030key3
1031`,
1032					buf.String(),
1033				)
1034			})
1035
1036			t.Run("inverse case", func(t *testing.T) {
1037				_, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1038key1=hello
1039#key2
1040key3`))
1041				require.Error(t, err)
1042			})
1043		})
1044
1045		t.Run("allow shadow keys", func(t *testing.T) {
1046			f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false, AllowShadows: true}, []byte(`
1047[remote "origin"]
1048url = https://github.com/Antergone/test1.git
1049url = https://github.com/Antergone/test2.git
1050fetch = +refs/heads/*:refs/remotes/origin/*`))
1051			require.NoError(t, err)
1052			require.NotNil(t, f)
1053
1054			assert.Equal(t, "https://github.com/Antergone/test1.git", f.Section(`remote "origin"`).Key("url").String())
1055			assert.Equal(
1056				t,
1057				[]string{
1058					"https://github.com/Antergone/test1.git",
1059					"https://github.com/Antergone/test2.git",
1060				},
1061				f.Section(`remote "origin"`).Key("url").ValueWithShadows(),
1062			)
1063			assert.Equal(t, "+refs/heads/*:refs/remotes/origin/*", f.Section(`remote "origin"`).Key("fetch").String())
1064
1065			t.Run("write out", func(t *testing.T) {
1066				var buf bytes.Buffer
1067				_, err := f.WriteTo(&buf)
1068				require.NoError(t, err)
1069				assert.Equal(t, `[remote "origin"]
1070url   = https://github.com/Antergone/test1.git
1071url   = https://github.com/Antergone/test2.git
1072fetch = +refs/heads/*:refs/remotes/origin/*
1073
1074`,
1075					buf.String(),
1076				)
1077			})
1078
1079			t.Run("inverse case", func(t *testing.T) {
1080				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1081[remote "origin"]
1082url = https://github.com/Antergone/test1.git
1083url = https://github.com/Antergone/test2.git`))
1084				require.NoError(t, err)
1085				require.NotNil(t, f)
1086
1087				assert.Equal(t, "https://github.com/Antergone/test2.git", f.Section(`remote "origin"`).Key("url").String())
1088			})
1089		})
1090
1091		t.Run("unescape double quotes inside value", func(t *testing.T) {
1092			f, err := LoadSources(LoadOptions{
1093				AllowPythonMultilineValues: false,
1094				UnescapeValueDoubleQuotes:  true,
1095			}, []byte(`
1096create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
1097			require.NoError(t, err)
1098			require.NotNil(t, f)
1099
1100			assert.Equal(t, `创建了仓库 <a href="%s">%s</a>`, f.Section("").Key("create_repo").String())
1101
1102			t.Run("inverse case", func(t *testing.T) {
1103				f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1104create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
1105				require.NoError(t, err)
1106				require.NotNil(t, f)
1107
1108				assert.Equal(t, `"创建了仓库 <a href=\"%s\">%s</a>"`, f.Section("").Key("create_repo").String())
1109			})
1110		})
1111
1112		t.Run("unescape comment symbols inside value", func(t *testing.T) {
1113			f, err := LoadSources(LoadOptions{
1114				AllowPythonMultilineValues:  false,
1115				IgnoreInlineComment:         true,
1116				UnescapeValueCommentSymbols: true,
1117			}, []byte(`
1118key = test value <span style="color: %s\; background: %s">more text</span>
1119`))
1120			require.NoError(t, err)
1121			require.NotNil(t, f)
1122
1123			assert.Equal(t, `test value <span style="color: %s; background: %s">more text</span>`, f.Section("").Key("key").String())
1124		})
1125
1126		t.Run("cannot parse small python-compatible INI files", func(t *testing.T) {
1127			f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1128[long]
1129long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
1130  foo
1131  bar
1132  foobar
1133  barfoo
1134  -----END RSA PRIVATE KEY-----
1135`))
1136			require.Error(t, err)
1137			assert.Nil(t, f)
1138			assert.Equal(t, "key-value delimiter not found: foo\n", err.Error())
1139		})
1140
1141		t.Run("cannot parse big python-compatible INI files", func(t *testing.T) {
1142			f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1143[long]
1144long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
1145  1foo
1146  2bar
1147  3foobar
1148  4barfoo
1149  5foo
1150  6bar
1151  7foobar
1152  8barfoo
1153  9foo
1154  10bar
1155  11foobar
1156  12barfoo
1157  13foo
1158  14bar
1159  15foobar
1160  16barfoo
1161  17foo
1162  18bar
1163  19foobar
1164  20barfoo
1165  21foo
1166  22bar
1167  23foobar
1168  24barfoo
1169  25foo
1170  26bar
1171  27foobar
1172  28barfoo
1173  29foo
1174  30bar
1175  31foobar
1176  32barfoo
1177  33foo
1178  34bar
1179  35foobar
1180  36barfoo
1181  37foo
1182  38bar
1183  39foobar
1184  40barfoo
1185  41foo
1186  42bar
1187  43foobar
1188  44barfoo
1189  45foo
1190  46bar
1191  47foobar
1192  48barfoo
1193  49foo
1194  50bar
1195  51foobar
1196  52barfoo
1197  53foo
1198  54bar
1199  55foobar
1200  56barfoo
1201  57foo
1202  58bar
1203  59foobar
1204  60barfoo
1205  61foo
1206  62bar
1207  63foobar
1208  64barfoo
1209  65foo
1210  66bar
1211  67foobar
1212  68barfoo
1213  69foo
1214  70bar
1215  71foobar
1216  72barfoo
1217  73foo
1218  74bar
1219  75foobar
1220  76barfoo
1221  77foo
1222  78bar
1223  79foobar
1224  80barfoo
1225  81foo
1226  82bar
1227  83foobar
1228  84barfoo
1229  85foo
1230  86bar
1231  87foobar
1232  88barfoo
1233  89foo
1234  90bar
1235  91foobar
1236  92barfoo
1237  93foo
1238  94bar
1239  95foobar
1240  96barfoo
1241  -----END RSA PRIVATE KEY-----
1242`))
1243			require.Error(t, err)
1244			assert.Nil(t, f)
1245			assert.Equal(t, "key-value delimiter not found: 1foo\n", err.Error())
1246		})
1247
1248		t.Run("allow unparsable sections", func(t *testing.T) {
1249			f, err := LoadSources(LoadOptions{
1250				AllowPythonMultilineValues: false,
1251				Insensitive:                true,
1252				UnparseableSections:        []string{"core_lesson", "comments"},
1253			}, []byte(`
1254Lesson_Location = 87
1255Lesson_Status = C
1256Score = 3
1257Time = 00:02:30
1258
1259[CORE_LESSON]
1260my lesson state data – 1111111111111111111000000000000000001110000
1261111111111111111111100000000000111000000000 – end my lesson state data
1262
1263[COMMENTS]
1264<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
1265			require.NoError(t, err)
1266			require.NotNil(t, f)
1267
1268			assert.Equal(t, "3", f.Section("").Key("score").String())
1269			assert.Empty(t, f.Section("").Body())
1270			assert.Equal(t, `my lesson state data – 1111111111111111111000000000000000001110000
1271111111111111111111100000000000111000000000 – end my lesson state data`,
1272				f.Section("core_lesson").Body(),
1273			)
1274			assert.Equal(t, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`, f.Section("comments").Body())
1275
1276			t.Run("write out", func(t *testing.T) {
1277				var buf bytes.Buffer
1278				_, err := f.WriteTo(&buf)
1279				require.NoError(t, err)
1280				assert.Equal(t, `lesson_location = 87
1281lesson_status   = C
1282score           = 3
1283time            = 00:02:30
1284
1285[core_lesson]
1286my lesson state data – 1111111111111111111000000000000000001110000
1287111111111111111111100000000000111000000000 – end my lesson state data
1288
1289[comments]
1290<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
1291`,
1292					buf.String(),
1293				)
1294			})
1295
1296			t.Run("inverse case", func(t *testing.T) {
1297				_, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1298[CORE_LESSON]
1299my lesson state data – 1111111111111111111000000000000000001110000
1300111111111111111111100000000000111000000000 – end my lesson state data`))
1301				require.Error(t, err)
1302			})
1303		})
1304
1305		t.Run("and false `SpaceBeforeInlineComment`", func(t *testing.T) {
1306			t.Run("cannot parse INI files containing `#` or `;` in value", func(t *testing.T) {
1307				f, err := LoadSources(
1308					LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: false},
1309					[]byte(`
1310[author]
1311NAME = U#n#k#n#w#o#n
1312GITHUB = U;n;k;n;w;o;n
1313`))
1314				require.NoError(t, err)
1315				require.NotNil(t, f)
1316				sec := f.Section("author")
1317				nameValue := sec.Key("NAME").String()
1318				githubValue := sec.Key("GITHUB").String()
1319				assert.Equal(t, "U", nameValue)
1320				assert.Equal(t, "U", githubValue)
1321			})
1322		})
1323
1324		t.Run("and true `SpaceBeforeInlineComment`", func(t *testing.T) {
1325			t.Run("can parse INI files containing `#` or `;` in value", func(t *testing.T) {
1326				f, err := LoadSources(
1327					LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: true},
1328					[]byte(`
1329[author]
1330NAME = U#n#k#n#w#o#n
1331GITHUB = U;n;k;n;w;o;n
1332`))
1333				require.NoError(t, err)
1334				require.NotNil(t, f)
1335				sec := f.Section("author")
1336				nameValue := sec.Key("NAME").String()
1337				githubValue := sec.Key("GITHUB").String()
1338				assert.Equal(t, "U#n#k#n#w#o#n", nameValue)
1339				assert.Equal(t, "U;n;k;n;w;o;n", githubValue)
1340			})
1341		})
1342	})
1343
1344	t.Run("with `ChildSectionDelimiter` ':'", func(t *testing.T) {
1345		t.Run("get all keys of parent sections", func(t *testing.T) {
1346			f := Empty(LoadOptions{ChildSectionDelimiter: ":"})
1347			require.NotNil(t, f)
1348
1349			k, err := f.Section("package").NewKey("NAME", "ini")
1350			require.NoError(t, err)
1351			assert.NotNil(t, k)
1352			k, err = f.Section("package").NewKey("VERSION", "v1")
1353			require.NoError(t, err)
1354			assert.NotNil(t, k)
1355			k, err = f.Section("package").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
1356			require.NoError(t, err)
1357			assert.NotNil(t, k)
1358
1359			keys := f.Section("package:sub:sub2").ParentKeys()
1360			names := []string{"NAME", "VERSION", "IMPORT_PATH"}
1361			assert.Equal(t, len(names), len(keys))
1362			for i, name := range names {
1363				assert.Equal(t, name, keys[i].Name())
1364			}
1365		})
1366
1367		t.Run("getting and setting values", func(t *testing.T) {
1368			f, err := LoadSources(LoadOptions{ChildSectionDelimiter: ":"}, fullConf)
1369			require.NoError(t, err)
1370			require.NotNil(t, f)
1371
1372			t.Run("get parent-keys that are available to the child section", func(t *testing.T) {
1373				parentKeys := f.Section("package:sub").ParentKeys()
1374				assert.NotNil(t, parentKeys)
1375				for _, k := range parentKeys {
1376					assert.Equal(t, "CLONE_URL", k.Name())
1377				}
1378			})
1379
1380			t.Run("get parent section value", func(t *testing.T) {
1381				assert.Equal(t, "https://gopkg.in/ini.v1", f.Section("package:sub").Key("CLONE_URL").String())
1382				assert.Equal(t, "https://gopkg.in/ini.v1", f.Section("package:fake:sub").Key("CLONE_URL").String())
1383			})
1384		})
1385
1386		t.Run("get child sections by parent name", func(t *testing.T) {
1387			f, err := LoadSources(LoadOptions{ChildSectionDelimiter: ":"}, []byte(`
1388[node]
1389[node:biz1]
1390[node:biz2]
1391[node.biz3]
1392[node.bizN]
1393`))
1394			require.NoError(t, err)
1395			require.NotNil(t, f)
1396
1397			children := f.ChildSections("node")
1398			names := []string{"node:biz1", "node:biz2"}
1399			assert.Equal(t, len(names), len(children))
1400			for i, name := range names {
1401				assert.Equal(t, name, children[i].Name())
1402			}
1403		})
1404	})
1405
1406	t.Run("ShortCircuit", func(t *testing.T) {
1407		t.Run("load the first available configuration, ignore other configuration", func(t *testing.T) {
1408			f, err := LoadSources(LoadOptions{ShortCircuit: true}, minimalConf, []byte(`key1 = value1`))
1409			require.NotNil(t, f)
1410			require.NoError(t, err)
1411			var buf bytes.Buffer
1412			_, err = f.WriteTo(&buf)
1413			require.NoError(t, err)
1414			assert.Equal(t, `[author]
1415E-MAIL = u@gogs.io
1416
1417`,
1418				buf.String(),
1419			)
1420		})
1421
1422		t.Run("return an error when fail to load", func(t *testing.T) {
1423			f, err := LoadSources(LoadOptions{ShortCircuit: true}, notFoundConf, minimalConf)
1424			assert.Nil(t, f)
1425			require.Error(t, err)
1426		})
1427
1428		t.Run("used with Loose to ignore errors that the file does not exist", func(t *testing.T) {
1429			f, err := LoadSources(LoadOptions{ShortCircuit: true, Loose: true}, notFoundConf, minimalConf)
1430			require.NotNil(t, f)
1431			require.NoError(t, err)
1432			var buf bytes.Buffer
1433			_, err = f.WriteTo(&buf)
1434			require.NoError(t, err)
1435			assert.Equal(t, `[author]
1436E-MAIL = u@gogs.io
1437
1438`,
1439				buf.String(),
1440			)
1441		})
1442
1443		t.Run("ensure all sources are loaded without ShortCircuit", func(t *testing.T) {
1444			f, err := LoadSources(LoadOptions{ShortCircuit: false}, minimalConf, []byte(`key1 = value1`))
1445			require.NotNil(t, f)
1446			require.NoError(t, err)
1447			var buf bytes.Buffer
1448			_, err = f.WriteTo(&buf)
1449			require.NoError(t, err)
1450			assert.Equal(t, `key1 = value1
1451
1452[author]
1453E-MAIL = u@gogs.io
1454
1455`,
1456				buf.String(),
1457			)
1458		})
1459	})
1460}
1461
1462func Test_KeyValueDelimiters(t *testing.T) {
1463	t.Run("custom key-value delimiters", func(t *testing.T) {
1464		f, err := LoadSources(LoadOptions{
1465			KeyValueDelimiters: "?!",
1466		}, []byte(`
1467[section]
1468key1?value1
1469key2!value2
1470`))
1471		require.NoError(t, err)
1472		require.NotNil(t, f)
1473
1474		assert.Equal(t, "value1", f.Section("section").Key("key1").String())
1475		assert.Equal(t, "value2", f.Section("section").Key("key2").String())
1476	})
1477}
1478
1479func Test_PreserveSurroundedQuote(t *testing.T) {
1480	t.Run("preserve surrounded quote test", func(t *testing.T) {
1481		f, err := LoadSources(LoadOptions{
1482			PreserveSurroundedQuote: true,
1483		}, []byte(`
1484[section]
1485key1 = "value1"
1486key2 = value2
1487`))
1488		require.NoError(t, err)
1489		require.NotNil(t, f)
1490
1491		assert.Equal(t, "\"value1\"", f.Section("section").Key("key1").String())
1492		assert.Equal(t, "value2", f.Section("section").Key("key2").String())
1493	})
1494
1495	t.Run("preserve surrounded quote test inverse test", func(t *testing.T) {
1496		f, err := LoadSources(LoadOptions{
1497			PreserveSurroundedQuote: false,
1498		}, []byte(`
1499[section]
1500key1 = "value1"
1501key2 = value2
1502`))
1503		require.NoError(t, err)
1504		require.NotNil(t, f)
1505
1506		assert.Equal(t, "value1", f.Section("section").Key("key1").String())
1507		assert.Equal(t, "value2", f.Section("section").Key("key2").String())
1508	})
1509}
1510
1511type testData struct {
1512	Value1 string `ini:"value1"`
1513	Value2 string `ini:"value2"`
1514	Value3 string `ini:"value3"`
1515}
1516
1517func TestPythonMultiline(t *testing.T) {
1518	if runtime.GOOS == "windows" {
1519		t.Skip("Skipping testing on Windows")
1520	}
1521
1522	path := filepath.Join("testdata", "multiline.ini")
1523	f, err := LoadSources(LoadOptions{
1524		AllowPythonMultilineValues: true,
1525		ReaderBufferSize:           64 * 1024,
1526	}, path)
1527	require.NoError(t, err)
1528	require.NotNil(t, f)
1529	assert.Len(t, f.Sections(), 1)
1530
1531	defaultSection := f.Section("")
1532	assert.NotNil(t, f.Section(""))
1533
1534	var testData testData
1535	err = defaultSection.MapTo(&testData)
1536	require.NoError(t, err)
1537	assert.Equal(t, "some text here\nsome more text here\n\nthere is an empty line above and below\n", testData.Value1)
1538	assert.Equal(t, "there is an empty line above\nthat is not indented so it should not be part\nof the value", testData.Value2)
1539	assert.Equal(t, `.
1540
1541Lorem 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.
1542
1543Lectus 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.
1544
1545Dui 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.
1546
1547Pellentesque 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.
1548
1549Blandit 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.
1550
1551Mauris 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.
1552
1553Augue 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.
1554
1555Elit 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.
1556
1557Malesuada 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.
1558
1559Id 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.
1560
1561Eu 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.`,
1562		testData.Value3,
1563	)
1564}
1565