1 package ini
2
3 import (
4 "bytes"
5 "os"
6 "path/filepath"
7 "runtime"
8 "sort"
9 "testing"
10
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13
14 "edge-infra.dev/pkg/lib/build/bazel"
15 )
16
17 func TestEmpty(t *testing.T) {
18 f := Empty()
19 require.NotNil(t, f)
20
21
22 assert.Len(t, f.Sections(), 1)
23
24
25 assert.Len(t, f.Section("").Keys(), 0)
26 }
27
28 func TestFile_NewSection(t *testing.T) {
29 f := Empty()
30 require.NotNil(t, f)
31
32 sec, err := f.NewSection("author")
33 require.NoError(t, err)
34 require.NotNil(t, sec)
35 assert.Equal(t, "author", sec.Name())
36
37 assert.Equal(t, []string{DefaultSection, "author"}, f.SectionStrings())
38
39 t.Run("with duplicated name", func(t *testing.T) {
40 sec, err := f.NewSection("author")
41 require.NoError(t, err)
42 require.NotNil(t, sec)
43
44
45 assert.Equal(t, []string{DefaultSection, "author"}, f.SectionStrings())
46 })
47
48 t.Run("with empty string", func(t *testing.T) {
49 _, err := f.NewSection("")
50 require.Error(t, err)
51 })
52 }
53
54 func TestFile_NonUniqueSection(t *testing.T) {
55 t.Run("read and write non-unique sections", func(t *testing.T) {
56 f, err := LoadSources(LoadOptions{
57 AllowNonUniqueSections: true,
58 }, []byte(`[Interface]
59 Address = 192.168.2.1
60 PrivateKey = <server's privatekey>
61 ListenPort = 51820
62
63 [Peer]
64 PublicKey = <client's publickey>
65 AllowedIPs = 192.168.2.2/32
66
67 [Peer]
68 PublicKey = <client2's publickey>
69 AllowedIPs = 192.168.2.3/32`))
70 require.NoError(t, err)
71 require.NotNil(t, f)
72
73 sec, err := f.NewSection("Peer")
74 require.NoError(t, err)
75 require.NotNil(t, f)
76
77 _, _ = sec.NewKey("PublicKey", "<client3's publickey>")
78 _, _ = sec.NewKey("AllowedIPs", "192.168.2.4/32")
79
80 var buf bytes.Buffer
81 _, err = f.WriteTo(&buf)
82 require.NoError(t, err)
83 str := buf.String()
84 assert.Equal(t, `[Interface]
85 Address = 192.168.2.1
86 PrivateKey = <server's privatekey>
87 ListenPort = 51820
88
89 [Peer]
90 PublicKey = <client's publickey>
91 AllowedIPs = 192.168.2.2/32
92
93 [Peer]
94 PublicKey = <client2's publickey>
95 AllowedIPs = 192.168.2.3/32
96
97 [Peer]
98 PublicKey = <client3's publickey>
99 AllowedIPs = 192.168.2.4/32
100 `, str)
101 })
102
103 t.Run("delete non-unique section", func(t *testing.T) {
104 f, err := LoadSources(LoadOptions{
105 AllowNonUniqueSections: true,
106 }, []byte(`[Interface]
107 Address = 192.168.2.1
108 PrivateKey = <server's privatekey>
109 ListenPort = 51820
110
111 [Peer]
112 PublicKey = <client's publickey>
113 AllowedIPs = 192.168.2.2/32
114
115 [Peer]
116 PublicKey = <client2's publickey>
117 AllowedIPs = 192.168.2.3/32
118
119 [Peer]
120 PublicKey = <client3's publickey>
121 AllowedIPs = 192.168.2.4/32
122
123 `))
124 require.NoError(t, err)
125 require.NotNil(t, f)
126
127 err = f.DeleteSectionWithIndex("Peer", 1)
128 require.NoError(t, err)
129
130 var buf bytes.Buffer
131 _, err = f.WriteTo(&buf)
132 require.NoError(t, err)
133 str := buf.String()
134 assert.Equal(t, `[Interface]
135 Address = 192.168.2.1
136 PrivateKey = <server's privatekey>
137 ListenPort = 51820
138
139 [Peer]
140 PublicKey = <client's publickey>
141 AllowedIPs = 192.168.2.2/32
142
143 [Peer]
144 PublicKey = <client3's publickey>
145 AllowedIPs = 192.168.2.4/32
146 `, str)
147 })
148
149 t.Run("delete all sections", func(t *testing.T) {
150 f := Empty(LoadOptions{
151 AllowNonUniqueSections: true,
152 })
153 require.NotNil(t, f)
154
155 _ = f.NewSections("Interface", "Peer", "Peer")
156 assert.Equal(t, []string{DefaultSection, "Interface", "Peer", "Peer"}, f.SectionStrings())
157 f.DeleteSection("Peer")
158 assert.Equal(t, []string{DefaultSection, "Interface"}, f.SectionStrings())
159 })
160 }
161
162 func TestFile_NewRawSection(t *testing.T) {
163 f := Empty()
164 require.NotNil(t, f)
165
166 sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000
167 111111111111111111100000000000111000000000`)
168 require.NoError(t, err)
169 require.NotNil(t, sec)
170 assert.Equal(t, "comments", sec.Name())
171
172 assert.Equal(t, []string{DefaultSection, "comments"}, f.SectionStrings())
173 assert.Equal(t, `1111111111111111111000000000000000001110000
174 111111111111111111100000000000111000000000`, f.Section("comments").Body())
175
176 t.Run("with duplicated name", func(t *testing.T) {
177 sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000`)
178 require.NoError(t, err)
179 require.NotNil(t, sec)
180 assert.Equal(t, []string{DefaultSection, "comments"}, f.SectionStrings())
181
182
183 assert.Equal(t, `1111111111111111111000000000000000001110000`, f.Section("comments").Body())
184 })
185
186 t.Run("with empty string", func(t *testing.T) {
187 _, err := f.NewRawSection("", "")
188 require.Error(t, err)
189 })
190 }
191
192 func TestFile_NewSections(t *testing.T) {
193 f := Empty()
194 require.NotNil(t, f)
195
196 assert.NoError(t, f.NewSections("package", "author"))
197 assert.Equal(t, []string{DefaultSection, "package", "author"}, f.SectionStrings())
198
199 t.Run("with duplicated name", func(t *testing.T) {
200 assert.NoError(t, f.NewSections("author", "features"))
201
202
203 assert.Equal(t, []string{DefaultSection, "package", "author", "features"}, f.SectionStrings())
204 })
205
206 t.Run("with empty string", func(t *testing.T) {
207 assert.Error(t, f.NewSections("", ""))
208 })
209 }
210
211 func TestFile_GetSection(t *testing.T) {
212 f, err := Load(fullConf)
213 require.NoError(t, err)
214 require.NotNil(t, f)
215
216 sec, err := f.GetSection("author")
217 require.NoError(t, err)
218 require.NotNil(t, sec)
219 assert.Equal(t, "author", sec.Name())
220
221 t.Run("section not exists", func(t *testing.T) {
222 _, err := f.GetSection("404")
223 require.Error(t, err)
224 })
225 }
226
227 func TestFile_HasSection(t *testing.T) {
228 f, err := Load(fullConf)
229 require.NoError(t, err)
230 require.NotNil(t, f)
231
232 sec := f.HasSection("author")
233 assert.True(t, sec)
234
235 t.Run("section not exists", func(t *testing.T) {
236 nonexistent := f.HasSection("404")
237 assert.False(t, nonexistent)
238 })
239 }
240
241 func TestFile_Section(t *testing.T) {
242 t.Run("get a section", func(t *testing.T) {
243 f, err := Load(fullConf)
244 require.NoError(t, err)
245 require.NotNil(t, f)
246
247 sec := f.Section("author")
248 require.NotNil(t, sec)
249 assert.Equal(t, "author", sec.Name())
250
251 t.Run("section not exists", func(t *testing.T) {
252 sec := f.Section("404")
253 require.NotNil(t, sec)
254 assert.Equal(t, "404", sec.Name())
255 })
256 })
257
258 t.Run("get default section in lower case with insensitive load", func(t *testing.T) {
259 f, err := InsensitiveLoad([]byte(`
260 [default]
261 NAME = ini
262 VERSION = v1`))
263 require.NoError(t, err)
264 require.NotNil(t, f)
265
266 assert.Equal(t, "ini", f.Section("").Key("name").String())
267 assert.Equal(t, "v1", f.Section("").Key("version").String())
268 })
269
270 t.Run("get sections after deletion", func(t *testing.T) {
271 f, err := Load([]byte(`
272 [RANDOM]
273 `))
274 require.NoError(t, err)
275 require.NotNil(t, f)
276
277 sectionNames := f.SectionStrings()
278 sort.Strings(sectionNames)
279 assert.Equal(t, []string{DefaultSection, "RANDOM"}, sectionNames)
280
281 for _, currentSection := range sectionNames {
282 f.DeleteSection(currentSection)
283 }
284
285 for sectionParam, expectedSectionName := range map[string]string{
286 "": DefaultSection,
287 "RANDOM": "RANDOM",
288 } {
289 sec := f.Section(sectionParam)
290 require.NotNil(t, sec)
291 assert.Equal(t, expectedSectionName, sec.Name())
292 }
293 })
294
295 }
296
297 func TestFile_Sections(t *testing.T) {
298 f, err := Load(fullConf)
299 require.NoError(t, err)
300 require.NotNil(t, f)
301
302 secs := f.Sections()
303 names := []string{DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"}
304 assert.Len(t, secs, len(names))
305 for i, name := range names {
306 assert.Equal(t, name, secs[i].Name())
307 }
308 }
309
310 func TestFile_ChildSections(t *testing.T) {
311 f, err := Load([]byte(`
312 [node]
313 [node.biz1]
314 [node.biz2]
315 [node.biz3]
316 [node.bizN]
317 `))
318 require.NoError(t, err)
319 require.NotNil(t, f)
320
321 children := f.ChildSections("node")
322 names := []string{"node.biz1", "node.biz2", "node.biz3", "node.bizN"}
323 assert.Len(t, children, len(names))
324 for i, name := range names {
325 assert.Equal(t, name, children[i].Name())
326 }
327 }
328
329 func TestFile_SectionStrings(t *testing.T) {
330 f, err := Load(fullConf)
331 require.NoError(t, err)
332 require.NotNil(t, f)
333
334 assert.Equal(t, []string{DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"}, f.SectionStrings())
335 }
336
337 func TestFile_DeleteSection(t *testing.T) {
338 t.Run("delete a section", func(t *testing.T) {
339 f := Empty()
340 require.NotNil(t, f)
341
342 _ = f.NewSections("author", "package", "features")
343 f.DeleteSection("features")
344 f.DeleteSection("")
345 assert.Equal(t, []string{"author", "package"}, f.SectionStrings())
346 })
347
348 t.Run("delete default section", func(t *testing.T) {
349 f := Empty()
350 require.NotNil(t, f)
351
352 f.Section("").Key("foo").SetValue("bar")
353 f.Section("section1").Key("key1").SetValue("value1")
354 f.DeleteSection("")
355 assert.Equal(t, []string{"section1"}, f.SectionStrings())
356
357 var buf bytes.Buffer
358 _, err := f.WriteTo(&buf)
359 require.NoError(t, err)
360
361 assert.Equal(t, `[section1]
362 key1 = value1
363 `, buf.String())
364 })
365
366 t.Run("delete a section with InsensitiveSections", func(t *testing.T) {
367 f := Empty(LoadOptions{InsensitiveSections: true})
368 require.NotNil(t, f)
369
370 _ = f.NewSections("author", "package", "features")
371 f.DeleteSection("FEATURES")
372 f.DeleteSection("")
373 assert.Equal(t, []string{"author", "package"}, f.SectionStrings())
374 })
375 }
376
377 func TestFile_Append(t *testing.T) {
378 f := Empty()
379 require.NotNil(t, f)
380
381 assert.NoError(t, f.Append(minimalConf, []byte(`
382 [author]
383 NAME = Unknwon`)))
384
385 t.Run("with bad input", func(t *testing.T) {
386 assert.Error(t, f.Append(123))
387 assert.Error(t, f.Append(minimalConf, 123))
388 })
389 }
390
391 func TestFile_WriteTo(t *testing.T) {
392 if runtime.GOOS == "windows" {
393 t.Skip("Skipping testing on Windows")
394 }
395
396 t.Run("write content to somewhere", func(t *testing.T) {
397 f, err := Load(fullConf)
398 require.NoError(t, err)
399 require.NotNil(t, f)
400
401 f.Section("author").Comment = `Information about package author
402 # Bio can be written in multiple lines.`
403 f.Section("author").Key("NAME").Comment = "This is author name"
404 _, _ = f.Section("note").NewBooleanKey("boolean_key")
405 _, _ = f.Section("note").NewKey("more", "notes")
406
407 var buf bytes.Buffer
408 _, err = f.WriteTo(&buf)
409 require.NoError(t, err)
410
411 golden := "testdata/TestFile_WriteTo.golden"
412 if *update {
413 require.NoError(t, os.WriteFile(golden, buf.Bytes(), 0644))
414 }
415
416 expected, err := os.ReadFile(golden)
417 require.NoError(t, err)
418 assert.Equal(t, string(expected), buf.String())
419 })
420
421 t.Run("support multiline comments", func(t *testing.T) {
422 f, err := Load([]byte(`
423 #
424 # general.domain
425 #
426 # Domain name of XX system.
427 domain = mydomain.com
428 `))
429 require.NoError(t, err)
430
431 f.Section("").Key("test").Comment = "Multiline\nComment"
432
433 var buf bytes.Buffer
434 _, err = f.WriteTo(&buf)
435 require.NoError(t, err)
436
437 assert.Equal(t, `#
438 # general.domain
439 #
440 # Domain name of XX system.
441 domain = mydomain.com
442 ; Multiline
443 ; Comment
444 test =
445 `, buf.String())
446 })
447
448 t.Run("keep leading and trailing spaces in value", func(t *testing.T) {
449 f, _ := Load([]byte(`[foo]
450 bar1 = ' val ue1 '
451 bar2 = """ val ue2 """
452 bar3 = " val ue3 "
453 `))
454 require.NotNil(t, f)
455
456 var buf bytes.Buffer
457 _, err := f.WriteTo(&buf)
458 require.NoError(t, err)
459 assert.Equal(t, `[foo]
460 bar1 = " val ue1 "
461 bar2 = " val ue2 "
462 bar3 = " val ue3 "
463 `, buf.String())
464 })
465 }
466
467 func TestFile_SaveTo(t *testing.T) {
468 f, err := Load(fullConf)
469 require.NoError(t, err)
470 require.NotNil(t, f)
471
472 tmpdir, err := bazel.NewTestTmpDir("edge-infra-ini-test-*")
473 assert.NoError(t, err)
474
475 p := filepath.Join(tmpdir, "conf_out.ini")
476 assert.NoError(t, f.SaveTo(p))
477 assert.NoError(t, f.SaveToIndent(p, "\t"))
478 }
479
480 func TestFile_WriteToWithOutputDelimiter(t *testing.T) {
481 f, err := LoadSources(LoadOptions{
482 KeyValueDelimiterOnWrite: "->",
483 }, []byte(`[Others]
484 Cities = HangZhou|Boston
485 Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
486 Years = 1993,1994
487 Numbers = 10010,10086
488 Ages = 18,19
489 Populations = 12345678,98765432
490 Coordinates = 192.168,10.11
491 Flags = true,false
492 Note = Hello world!`))
493 require.NoError(t, err)
494 require.NotNil(t, f)
495
496 var actual bytes.Buffer
497 var expected = []byte(`[Others]
498 Cities -> HangZhou|Boston
499 Visits -> 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
500 Years -> 1993,1994
501 Numbers -> 10010,10086
502 Ages -> 18,19
503 Populations -> 12345678,98765432
504 Coordinates -> 192.168,10.11
505 Flags -> true,false
506 Note -> Hello world!
507 `)
508 _, err = f.WriteTo(&actual)
509 require.NoError(t, err)
510
511 assert.Equal(t, expected, actual.Bytes())
512 }
513
514
515 func TestReloadAfterShadowLoad(t *testing.T) {
516 f, err := ShadowLoad([]byte(`
517 [slice]
518 v = 1
519 v = 2
520 v = 3
521 `))
522 require.NoError(t, err)
523 require.NotNil(t, f)
524
525 assert.Equal(t, []string{"1", "2", "3"}, f.Section("slice").Key("v").ValueWithShadows())
526
527 require.NoError(t, f.Reload())
528 assert.Equal(t, []string{"1", "2", "3"}, f.Section("slice").Key("v").ValueWithShadows())
529 }
530
View as plain text