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