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