1 package ini
2
3 import (
4 "bytes"
5 "fmt"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12 )
13
14 type testNested struct {
15 Cities []string `delim:"|"`
16 Visits []time.Time
17 Years []int
18 Numbers []int64
19 Ages []uint
20 Populations []uint64
21 Coordinates []float64
22 Flags []bool
23 Note string
24 Unused int `ini:"-"`
25 }
26
27 type TestEmbeded struct {
28 GPA float64
29 }
30
31 type testStruct struct {
32 Name string `ini:"NAME"`
33 Age int
34 Male bool
35 Money float64
36 Born time.Time
37 Time time.Duration `ini:"Duration"`
38 OldVersionTime time.Duration
39 Others testNested
40 OthersPtr *testNested
41 NilPtr *testNested
42 *TestEmbeded `ini:"grade"`
43 Unused int `ini:"-"`
44 Unsigned uint
45 Omitted bool `ini:"omitthis,omitempty"`
46 Shadows []string `ini:",allowshadow"`
47 ShadowInts []int `ini:"Shadows,allowshadow"`
48 BoolPtr *bool
49 BoolPtrNil *bool
50 FloatPtr *float64
51 FloatPtrNil *float64
52 IntPtr *int
53 IntPtrNil *int
54 UintPtr *uint
55 UintPtrNil *uint
56 StringPtr *string
57 StringPtrNil *string
58 TimePtr *time.Time
59 TimePtrNil *time.Time
60 DurationPtr *time.Duration
61 DurationPtrNil *time.Duration
62 }
63
64 type testInterface struct {
65 Address string
66 ListenPort int
67 PrivateKey string
68 }
69
70 type testPeer struct {
71 PublicKey string
72 PresharedKey string
73 AllowedIPs []string `delim:","`
74 }
75
76 type testNonUniqueSectionsStruct struct {
77 Interface testInterface
78 Peer []testPeer `ini:",nonunique"`
79 }
80
81 type BaseStruct struct {
82 Base bool
83 }
84
85 type testExtend struct {
86 BaseStruct `ini:",extends"`
87 Extend bool
88 }
89
90 const confDataStruct = `
91 NAME = Unknwon
92 Age = 21
93 Male = true
94 Money = 1.25
95 Born = 1993-10-07T20:17:05Z
96 Duration = 2h45m
97 OldVersionTime = 30
98 Unsigned = 3
99 omitthis = true
100 Shadows = 1, 2
101 Shadows = 3, 4
102 BoolPtr = false
103 FloatPtr = 0
104 IntPtr = 0
105 UintPtr = 0
106 StringPtr = ""
107 TimePtr = 0001-01-01T00:00:00Z
108 DurationPtr = 0s
109
110 [Others]
111 Cities = HangZhou|Boston
112 Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
113 Years = 1993,1994
114 Numbers = 10010,10086
115 Ages = 18,19
116 Populations = 12345678,98765432
117 Coordinates = 192.168,10.11
118 Flags = true,false
119 Note = Hello world!
120
121 [OthersPtr]
122 Cities = HangZhou|Boston
123 Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
124 Years = 1993,1994
125 Numbers = 10010,10086
126 Ages = 18,19
127 Populations = 12345678,98765432
128 Coordinates = 192.168,10.11
129 Flags = true,false
130 Note = Hello world!
131
132 [grade]
133 GPA = 2.8
134
135 [foo.bar]
136 Here = there
137 When = then
138
139 [extended]
140 Base = true
141 Extend = true
142 `
143
144 const confNonUniqueSectionDataStruct = `[Interface]
145 Address = 10.2.0.1/24
146 ListenPort = 34777
147 PrivateKey = privServerKey
148
149 [Peer]
150 PublicKey = pubClientKey
151 PresharedKey = psKey
152 AllowedIPs = 10.2.0.2/32,fd00:2::2/128
153
154 [Peer]
155 PublicKey = pubClientKey2
156 PresharedKey = psKey2
157 AllowedIPs = 10.2.0.3/32,fd00:2::3/128
158 `
159
160 type unsupport struct {
161 Byte byte
162 }
163
164 type unsupport2 struct {
165 Others struct {
166 Cities byte
167 }
168 }
169
170 type Unsupport3 struct {
171 Cities byte
172 }
173
174 type unsupport4 struct {
175 *Unsupport3 `ini:"Others"`
176 }
177
178 type defaultValue struct {
179 Name string
180 Age int
181 Male bool
182 Optional *bool
183 Money float64
184 Born time.Time
185 Cities []string
186 }
187
188 type fooBar struct {
189 Here, When string
190 }
191
192 const invalidDataConfStruct = `
193 Name =
194 Age = age
195 Male = 123
196 Money = money
197 Born = nil
198 Cities =
199 `
200
201 func Test_MapToStruct(t *testing.T) {
202 t.Run("map to struct", func(t *testing.T) {
203 t.Run("map file to struct", func(t *testing.T) {
204 ts := new(testStruct)
205 assert.NoError(t, MapTo(ts, []byte(confDataStruct)))
206
207 assert.Equal(t, "Unknwon", ts.Name)
208 assert.Equal(t, 21, ts.Age)
209 assert.True(t, ts.Male)
210 assert.Equal(t, 1.25, ts.Money)
211 assert.Equal(t, uint(3), ts.Unsigned)
212
213 ti, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
214 require.NoError(t, err)
215 assert.Equal(t, ti.String(), ts.Born.String())
216
217 dur, err := time.ParseDuration("2h45m")
218 require.NoError(t, err)
219 assert.Equal(t, dur.Seconds(), ts.Time.Seconds())
220
221 assert.Equal(t, 30*time.Second, ts.OldVersionTime*time.Second)
222
223 assert.Equal(t, "HangZhou,Boston", strings.Join(ts.Others.Cities, ","))
224 assert.Equal(t, ti.String(), ts.Others.Visits[0].String())
225 assert.Equal(t, "[1993 1994]", fmt.Sprint(ts.Others.Years))
226 assert.Equal(t, "[10010 10086]", fmt.Sprint(ts.Others.Numbers))
227 assert.Equal(t, "[18 19]", fmt.Sprint(ts.Others.Ages))
228 assert.Equal(t, "[12345678 98765432]", fmt.Sprint(ts.Others.Populations))
229 assert.Equal(t, "[192.168 10.11]", fmt.Sprint(ts.Others.Coordinates))
230 assert.Equal(t, "[true false]", fmt.Sprint(ts.Others.Flags))
231 assert.Equal(t, "Hello world!", ts.Others.Note)
232 assert.Equal(t, 2.8, ts.TestEmbeded.GPA)
233
234 assert.Equal(t, "HangZhou,Boston", strings.Join(ts.OthersPtr.Cities, ","))
235 assert.Equal(t, ti.String(), ts.OthersPtr.Visits[0].String())
236 assert.Equal(t, "[1993 1994]", fmt.Sprint(ts.OthersPtr.Years))
237 assert.Equal(t, "[10010 10086]", fmt.Sprint(ts.OthersPtr.Numbers))
238 assert.Equal(t, "[18 19]", fmt.Sprint(ts.OthersPtr.Ages))
239 assert.Equal(t, "[12345678 98765432]", fmt.Sprint(ts.OthersPtr.Populations))
240 assert.Equal(t, "[192.168 10.11]", fmt.Sprint(ts.OthersPtr.Coordinates))
241 assert.Equal(t, "[true false]", fmt.Sprint(ts.OthersPtr.Flags))
242 assert.Equal(t, "Hello world!", ts.OthersPtr.Note)
243
244 assert.Nil(t, ts.NilPtr)
245
246 assert.Equal(t, false, *ts.BoolPtr)
247 assert.Nil(t, ts.BoolPtrNil)
248 assert.Equal(t, float64(0), *ts.FloatPtr)
249 assert.Nil(t, ts.FloatPtrNil)
250 assert.Equal(t, 0, *ts.IntPtr)
251 assert.Nil(t, ts.IntPtrNil)
252 assert.Equal(t, uint(0), *ts.UintPtr)
253 assert.Nil(t, ts.UintPtrNil)
254 assert.Equal(t, "", *ts.StringPtr)
255 assert.Nil(t, ts.StringPtrNil)
256 assert.NotNil(t, *ts.TimePtr)
257 assert.Nil(t, ts.TimePtrNil)
258 assert.Equal(t, time.Duration(0), *ts.DurationPtr)
259 assert.Nil(t, ts.DurationPtrNil)
260 })
261
262 t.Run("map section to struct", func(t *testing.T) {
263 foobar := new(fooBar)
264 f, err := Load([]byte(confDataStruct))
265 require.NoError(t, err)
266
267 assert.NoError(t, f.Section("foo.bar").MapTo(foobar))
268 assert.Equal(t, "there", foobar.Here)
269 assert.Equal(t, "then", foobar.When)
270 })
271
272 t.Run("map to non-pointer struct", func(t *testing.T) {
273 f, err := Load([]byte(confDataStruct))
274 require.NoError(t, err)
275 require.NotNil(t, f)
276
277 assert.Error(t, f.MapTo(testStruct{}))
278 })
279
280 t.Run("map to unsupported type", func(t *testing.T) {
281 f, err := Load([]byte(confDataStruct))
282 require.NoError(t, err)
283 require.NotNil(t, f)
284
285 f.NameMapper = func(raw string) string {
286 if raw == "Byte" {
287 return "NAME"
288 }
289 return raw
290 }
291 assert.Error(t, f.MapTo(&unsupport{}))
292 assert.Error(t, f.MapTo(&unsupport2{}))
293 assert.Error(t, f.MapTo(&unsupport4{}))
294 })
295
296 t.Run("map to omitempty field", func(t *testing.T) {
297 ts := new(testStruct)
298 assert.NoError(t, MapTo(ts, []byte(confDataStruct)))
299
300 assert.Equal(t, true, ts.Omitted)
301 })
302
303 t.Run("map with shadows", func(t *testing.T) {
304 f, err := LoadSources(LoadOptions{AllowShadows: true}, []byte(confDataStruct))
305 require.NoError(t, err)
306 ts := new(testStruct)
307 assert.NoError(t, f.MapTo(ts))
308
309 assert.Equal(t, "1 2 3 4", strings.Join(ts.Shadows, " "))
310 assert.Equal(t, "[1 2 3 4]", fmt.Sprintf("%v", ts.ShadowInts))
311 })
312
313 t.Run("map from invalid data source", func(t *testing.T) {
314 assert.Error(t, MapTo(&testStruct{}, "hi"))
315 })
316
317 t.Run("map to wrong types and gain default values", func(t *testing.T) {
318 f, err := Load([]byte(invalidDataConfStruct))
319 require.NoError(t, err)
320
321 ti, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
322 require.NoError(t, err)
323 dv := &defaultValue{"Joe", 10, true, nil, 1.25, ti, []string{"HangZhou", "Boston"}}
324 assert.NoError(t, f.MapTo(dv))
325 assert.Equal(t, "Joe", dv.Name)
326 assert.Equal(t, 10, dv.Age)
327 assert.True(t, dv.Male)
328 assert.Equal(t, 1.25, dv.Money)
329 assert.Equal(t, ti.String(), dv.Born.String())
330 assert.Equal(t, "HangZhou,Boston", strings.Join(dv.Cities, ","))
331 })
332
333 t.Run("map to extended base", func(t *testing.T) {
334 f, err := Load([]byte(confDataStruct))
335 require.NoError(t, err)
336 require.NotNil(t, f)
337 te := testExtend{}
338 assert.NoError(t, f.Section("extended").MapTo(&te))
339 assert.True(t, te.Base)
340 assert.True(t, te.Extend)
341 })
342 })
343
344 t.Run("map to struct in strict mode", func(t *testing.T) {
345 f, err := Load([]byte(`
346 name=bruce
347 age=a30`))
348 require.NoError(t, err)
349
350 type Strict struct {
351 Name string `ini:"name"`
352 Age int `ini:"age"`
353 }
354 s := new(Strict)
355
356 assert.Error(t, f.Section("").StrictMapTo(s))
357 })
358
359 t.Run("map slice in strict mode", func(t *testing.T) {
360 f, err := Load([]byte(`
361 names=alice, bruce`))
362 require.NoError(t, err)
363
364 type Strict struct {
365 Names []string `ini:"names"`
366 }
367 s := new(Strict)
368
369 assert.NoError(t, f.Section("").StrictMapTo(s))
370 assert.Equal(t, "[alice bruce]", fmt.Sprint(s.Names))
371 })
372 }
373
374 func Test_MapToStructNonUniqueSections(t *testing.T) {
375 t.Run("map to struct non unique", func(t *testing.T) {
376 t.Run("map file to struct non unique", func(t *testing.T) {
377 f, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct))
378 require.NoError(t, err)
379 ts := new(testNonUniqueSectionsStruct)
380
381 assert.NoError(t, f.MapTo(ts))
382
383 assert.Equal(t, "10.2.0.1/24", ts.Interface.Address)
384 assert.Equal(t, 34777, ts.Interface.ListenPort)
385 assert.Equal(t, "privServerKey", ts.Interface.PrivateKey)
386
387 assert.Equal(t, "pubClientKey", ts.Peer[0].PublicKey)
388 assert.Equal(t, "psKey", ts.Peer[0].PresharedKey)
389 assert.Equal(t, "10.2.0.2/32", ts.Peer[0].AllowedIPs[0])
390 assert.Equal(t, "fd00:2::2/128", ts.Peer[0].AllowedIPs[1])
391
392 assert.Equal(t, "pubClientKey2", ts.Peer[1].PublicKey)
393 assert.Equal(t, "psKey2", ts.Peer[1].PresharedKey)
394 assert.Equal(t, "10.2.0.3/32", ts.Peer[1].AllowedIPs[0])
395 assert.Equal(t, "fd00:2::3/128", ts.Peer[1].AllowedIPs[1])
396 })
397
398 t.Run("map non unique section to struct", func(t *testing.T) {
399 newPeer := new(testPeer)
400 newPeerSlice := make([]testPeer, 0)
401
402 f, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct))
403 require.NoError(t, err)
404
405
406 assert.NoError(t, f.Section("Peer").MapTo(newPeer))
407 assert.Equal(t, "pubClientKey", newPeer.PublicKey)
408 assert.Equal(t, "psKey", newPeer.PresharedKey)
409 assert.Equal(t, "10.2.0.2/32", newPeer.AllowedIPs[0])
410 assert.Equal(t, "fd00:2::2/128", newPeer.AllowedIPs[1])
411
412
413 assert.NoError(t, f.Section("Peer").MapTo(&newPeerSlice))
414 assert.Equal(t, "pubClientKey", newPeerSlice[0].PublicKey)
415 assert.Equal(t, "psKey", newPeerSlice[0].PresharedKey)
416 assert.Equal(t, "10.2.0.2/32", newPeerSlice[0].AllowedIPs[0])
417 assert.Equal(t, "fd00:2::2/128", newPeerSlice[0].AllowedIPs[1])
418
419 assert.Equal(t, "pubClientKey2", newPeerSlice[1].PublicKey)
420 assert.Equal(t, "psKey2", newPeerSlice[1].PresharedKey)
421 assert.Equal(t, "10.2.0.3/32", newPeerSlice[1].AllowedIPs[0])
422 assert.Equal(t, "fd00:2::3/128", newPeerSlice[1].AllowedIPs[1])
423 })
424
425 t.Run("map non unique sections with subsections to struct", func(t *testing.T) {
426 iniFile, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, strings.NewReader(`
427 [Section]
428 FieldInSubSection = 1
429 FieldInSubSection2 = 2
430 FieldInSection = 3
431
432 [Section]
433 FieldInSubSection = 4
434 FieldInSubSection2 = 5
435 FieldInSection = 6
436 `))
437 require.NoError(t, err)
438
439 type SubSection struct {
440 FieldInSubSection string `ini:"FieldInSubSection"`
441 }
442 type SubSection2 struct {
443 FieldInSubSection2 string `ini:"FieldInSubSection2"`
444 }
445
446 type Section struct {
447 SubSection `ini:"Section"`
448 SubSection2 `ini:"Section"`
449 FieldInSection string `ini:"FieldInSection"`
450 }
451
452 type File struct {
453 Sections []Section `ini:"Section,nonunique"`
454 }
455
456 f := new(File)
457 err = iniFile.MapTo(f)
458 require.NoError(t, err)
459
460 assert.Equal(t, "1", f.Sections[0].FieldInSubSection)
461 assert.Equal(t, "2", f.Sections[0].FieldInSubSection2)
462 assert.Equal(t, "3", f.Sections[0].FieldInSection)
463
464 assert.Equal(t, "4", f.Sections[1].FieldInSubSection)
465 assert.Equal(t, "5", f.Sections[1].FieldInSubSection2)
466 assert.Equal(t, "6", f.Sections[1].FieldInSection)
467 })
468 })
469 }
470
471 func Test_ReflectFromStruct(t *testing.T) {
472 t.Run("reflect from struct", func(t *testing.T) {
473 type Embeded struct {
474 Dates []time.Time `delim:"|" comment:"Time data"`
475 Places []string
476 Years []int
477 Numbers []int64
478 Ages []uint
479 Populations []uint64
480 Coordinates []float64
481 Flags []bool
482 None []int
483 }
484 type Author struct {
485 Name string `ini:"NAME"`
486 Male bool
487 Optional *bool
488 Age int `comment:"Author's age"`
489 Height uint
490 GPA float64
491 Date time.Time
492 NeverMind string `ini:"-"`
493 ignored string
494 *Embeded `ini:"infos" comment:"Embeded section"`
495 }
496
497 ti, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
498 require.NoError(t, err)
499 a := &Author{"Unknwon", true, nil, 21, 100, 2.8, ti, "", "ignored",
500 &Embeded{
501 []time.Time{ti, ti},
502 []string{"HangZhou", "Boston"},
503 []int{1993, 1994},
504 []int64{10010, 10086},
505 []uint{18, 19},
506 []uint64{12345678, 98765432},
507 []float64{192.168, 10.11},
508 []bool{true, false},
509 []int{},
510 }}
511 cfg := Empty()
512 assert.NoError(t, ReflectFrom(cfg, a))
513
514 var buf bytes.Buffer
515 _, err = cfg.WriteTo(&buf)
516 require.NoError(t, err)
517 assert.Equal(t, `NAME = Unknwon
518 Male = true
519 Optional =
520 ; Author's age
521 Age = 21
522 Height = 100
523 GPA = 2.8
524 Date = 1993-10-07T20:17:05Z
525
526 ; Embeded section
527 [infos]
528 ; Time data
529 Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z
530 Places = HangZhou,Boston
531 Years = 1993,1994
532 Numbers = 10010,10086
533 Ages = 18,19
534 Populations = 12345678,98765432
535 Coordinates = 192.168,10.11
536 Flags = true,false
537 None =
538 `,
539 buf.String(),
540 )
541
542 t.Run("reflect from non-point struct", func(t *testing.T) {
543 assert.Error(t, ReflectFrom(cfg, Author{}))
544 })
545
546 t.Run("reflect from struct with omitempty", func(t *testing.T) {
547 cfg := Empty()
548 type SpecialStruct struct {
549 FirstName string `ini:"first_name"`
550 LastName string `ini:"last_name,omitempty"`
551 JustOmitMe string `ini:"omitempty"`
552 LastLogin time.Time `ini:"last_login,omitempty"`
553 LastLogin2 time.Time `ini:",omitempty"`
554 NotEmpty int `ini:"omitempty"`
555 Number int64 `ini:",omitempty"`
556 Ages uint `ini:",omitempty"`
557 Population uint64 `ini:",omitempty"`
558 Coordinate float64 `ini:",omitempty"`
559 Flag bool `ini:",omitempty"`
560 Note *string `ini:",omitempty"`
561 }
562 special := &SpecialStruct{
563 FirstName: "John",
564 LastName: "Doe",
565 NotEmpty: 9,
566 }
567
568 assert.NoError(t, ReflectFrom(cfg, special))
569
570 var buf bytes.Buffer
571 _, err = cfg.WriteTo(&buf)
572 assert.Equal(t, `first_name = John
573 last_name = Doe
574 omitempty = 9
575 `,
576 buf.String(),
577 )
578 })
579
580 t.Run("reflect from struct with non-anonymous structure pointer", func(t *testing.T) {
581 cfg := Empty()
582 type Rpc struct {
583 Enable bool `ini:"enable"`
584 Type string `ini:"type"`
585 Address string `ini:"addr"`
586 Name string `ini:"name"`
587 }
588 type Cfg struct {
589 Rpc *Rpc `ini:"rpc"`
590 }
591
592 config := &Cfg{
593 Rpc: &Rpc{
594 Enable: true,
595 Type: "type",
596 Address: "address",
597 Name: "name",
598 },
599 }
600 assert.NoError(t, cfg.ReflectFrom(config))
601
602 var buf bytes.Buffer
603 _, err = cfg.WriteTo(&buf)
604 assert.Equal(t, `[rpc]
605 enable = true
606 type = type
607 addr = address
608 name = name
609 `,
610 buf.String(),
611 )
612 })
613 })
614 }
615
616 func Test_ReflectFromStructNonUniqueSections(t *testing.T) {
617 t.Run("reflect from struct with non unique sections", func(t *testing.T) {
618 nonUnique := &testNonUniqueSectionsStruct{
619 Interface: testInterface{
620 Address: "10.2.0.1/24",
621 ListenPort: 34777,
622 PrivateKey: "privServerKey",
623 },
624 Peer: []testPeer{
625 {
626 PublicKey: "pubClientKey",
627 PresharedKey: "psKey",
628 AllowedIPs: []string{"10.2.0.2/32,fd00:2::2/128"},
629 },
630 {
631 PublicKey: "pubClientKey2",
632 PresharedKey: "psKey2",
633 AllowedIPs: []string{"10.2.0.3/32,fd00:2::3/128"},
634 },
635 },
636 }
637
638 cfg := Empty(LoadOptions{
639 AllowNonUniqueSections: true,
640 })
641
642 assert.NoError(t, ReflectFrom(cfg, nonUnique))
643
644 var buf bytes.Buffer
645 _, err := cfg.WriteTo(&buf)
646 require.NoError(t, err)
647 assert.Equal(t, confNonUniqueSectionDataStruct, buf.String())
648
649
650 err = cfg.Section("Peer").ReflectFrom([]*testPeer{
651 {
652 PublicKey: "pubClientKey3",
653 PresharedKey: "psKey3",
654 AllowedIPs: []string{"10.2.0.4/32,fd00:2::4/128"},
655 },
656 {
657 PublicKey: "pubClientKey4",
658 PresharedKey: "psKey4",
659 AllowedIPs: []string{"10.2.0.5/32,fd00:2::5/128"},
660 },
661 })
662
663 require.NoError(t, err)
664
665 buf = bytes.Buffer{}
666 _, err = cfg.WriteTo(&buf)
667 require.NoError(t, err)
668 assert.Equal(t, `[Interface]
669 Address = 10.2.0.1/24
670 ListenPort = 34777
671 PrivateKey = privServerKey
672
673 [Peer]
674 PublicKey = pubClientKey3
675 PresharedKey = psKey3
676 AllowedIPs = 10.2.0.4/32,fd00:2::4/128
677
678 [Peer]
679 PublicKey = pubClientKey4
680 PresharedKey = psKey4
681 AllowedIPs = 10.2.0.5/32,fd00:2::5/128
682 `,
683 buf.String(),
684 )
685
686
687 err = cfg.Section("Peer").ReflectFrom(&testPeer{
688 PublicKey: "pubClientKey5",
689 PresharedKey: "psKey5",
690 AllowedIPs: []string{"10.2.0.6/32,fd00:2::6/128"},
691 })
692
693 require.NoError(t, err)
694
695 buf = bytes.Buffer{}
696 _, err = cfg.WriteTo(&buf)
697 require.NoError(t, err)
698 assert.Equal(t, `[Interface]
699 Address = 10.2.0.1/24
700 ListenPort = 34777
701 PrivateKey = privServerKey
702
703 [Peer]
704 PublicKey = pubClientKey5
705 PresharedKey = psKey5
706 AllowedIPs = 10.2.0.6/32,fd00:2::6/128
707 `,
708 buf.String(),
709 )
710 })
711 }
712
713
714 func TestMapToAndReflectFromStructWithShadows(t *testing.T) {
715 t.Run("map to struct and then reflect with shadows should generate original config content", func(t *testing.T) {
716 type include struct {
717 Paths []string `ini:"path,omitempty,allowshadow"`
718 }
719
720 cfg, err := LoadSources(LoadOptions{
721 AllowShadows: true,
722 }, []byte(`
723 [include]
724 path = /tmp/gpm-profiles/test5.profile
725 path = /tmp/gpm-profiles/test1.profile`))
726 require.NoError(t, err)
727
728 sec := cfg.Section("include")
729 inc := new(include)
730 err = sec.MapTo(inc)
731 require.NoError(t, err)
732
733 err = sec.ReflectFrom(inc)
734 require.NoError(t, err)
735
736 var buf bytes.Buffer
737 _, err = cfg.WriteTo(&buf)
738 require.NoError(t, err)
739 assert.Equal(t, `[include]
740 path = /tmp/gpm-profiles/test5.profile
741 path = /tmp/gpm-profiles/test1.profile
742 `,
743 buf.String(),
744 )
745
746 t.Run("reflect from struct with shadows", func(t *testing.T) {
747 cfg := Empty(LoadOptions{
748 AllowShadows: true,
749 })
750 type ShadowStruct struct {
751 StringArray []string `ini:"sa,allowshadow"`
752 EmptyStringArrat []string `ini:"empty,omitempty,allowshadow"`
753 Allowshadow []string `ini:"allowshadow,allowshadow"`
754 Dates []time.Time `ini:",allowshadow"`
755 Places []string `ini:",allowshadow"`
756 Years []int `ini:",allowshadow"`
757 Numbers []int64 `ini:",allowshadow"`
758 Ages []uint `ini:",allowshadow"`
759 Populations []uint64 `ini:",allowshadow"`
760 Coordinates []float64 `ini:",allowshadow"`
761 Flags []bool `ini:",allowshadow"`
762 None []int `ini:",allowshadow"`
763 }
764
765 shadow := &ShadowStruct{
766 StringArray: []string{"s1", "s2"},
767 Allowshadow: []string{"s3", "s4"},
768 Dates: []time.Time{time.Date(2020, 9, 12, 00, 00, 00, 651387237, time.UTC),
769 time.Date(2020, 9, 12, 00, 00, 00, 651387237, time.UTC)},
770 Places: []string{"HangZhou", "Boston"},
771 Years: []int{1993, 1994},
772 Numbers: []int64{10010, 10086},
773 Ages: []uint{18, 19},
774 Populations: []uint64{12345678, 98765432},
775 Coordinates: []float64{192.168, 10.11},
776 Flags: []bool{true, false},
777 None: []int{},
778 }
779
780 assert.NoError(t, ReflectFrom(cfg, shadow))
781
782 var buf bytes.Buffer
783 _, err := cfg.WriteTo(&buf)
784 require.NoError(t, err)
785 assert.Equal(t, `sa = s1
786 sa = s2
787 allowshadow = s3
788 allowshadow = s4
789 Dates = 2020-09-12T00:00:00Z
790 Places = HangZhou
791 Places = Boston
792 Years = 1993
793 Years = 1994
794 Numbers = 10010
795 Numbers = 10086
796 Ages = 18
797 Ages = 19
798 Populations = 12345678
799 Populations = 98765432
800 Coordinates = 192.168
801 Coordinates = 10.11
802 Flags = true
803 Flags = false
804 None =
805 `,
806 buf.String(),
807 )
808 })
809 })
810 }
811
812 type testMapper struct {
813 PackageName string
814 }
815
816 func Test_NameGetter(t *testing.T) {
817 t.Run("test name mappers", func(t *testing.T) {
818 assert.NoError(t, MapToWithMapper(&testMapper{}, TitleUnderscore, []byte("packag_name=ini")))
819
820 cfg, err := Load([]byte("PACKAGE_NAME=ini"))
821 require.NoError(t, err)
822 require.NotNil(t, cfg)
823
824 cfg.NameMapper = SnackCase
825 tg := new(testMapper)
826 assert.NoError(t, cfg.MapTo(tg))
827 assert.Equal(t, "ini", tg.PackageName)
828 })
829 }
830
831 type testDurationStruct struct {
832 Duration time.Duration `ini:"Duration"`
833 }
834
835 func Test_Duration(t *testing.T) {
836 t.Run("duration less than 16m50s", func(t *testing.T) {
837 ds := new(testDurationStruct)
838 assert.NoError(t, MapTo(ds, []byte("Duration=16m49s")))
839
840 dur, err := time.ParseDuration("16m49s")
841 require.NoError(t, err)
842 assert.Equal(t, dur.Seconds(), ds.Duration.Seconds())
843 })
844 }
845
846 type Employer struct {
847 Name string
848 Title string
849 }
850
851 type Employers []*Employer
852
853 func (es Employers) ReflectINIStruct(f *File) error {
854 for _, e := range es {
855 f.Section(e.Name).Key("Title").SetValue(e.Title)
856 }
857 return nil
858 }
859
860
861 func Test_StructReflector(t *testing.T) {
862 t.Run("reflect with StructReflector interface", func(t *testing.T) {
863 p := &struct {
864 FirstName string
865 Employer Employers
866 }{
867 FirstName: "Andrew",
868 Employer: []*Employer{
869 {
870 Name: `Employer "VMware"`,
871 Title: "Staff II Engineer",
872 },
873 {
874 Name: `Employer "EMC"`,
875 Title: "Consultant Engineer",
876 },
877 },
878 }
879
880 f := Empty()
881 assert.NoError(t, f.ReflectFrom(p))
882
883 var buf bytes.Buffer
884 _, err := f.WriteTo(&buf)
885 require.NoError(t, err)
886
887 assert.Equal(t, `FirstName = Andrew
888
889 [Employer "VMware"]
890 Title = Staff II Engineer
891
892 [Employer "EMC"]
893 Title = Consultant Engineer
894 `,
895 buf.String(),
896 )
897 })
898 }
899
View as plain text