2017-07-23 12:24:45 +00:00
|
|
|
// Copyright 2014 Unknwon
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
|
|
// not use this file except in compliance with the License. You may obtain
|
|
|
|
// a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
// License for the specific language governing permissions and limitations
|
|
|
|
// under the License.
|
|
|
|
|
|
|
|
package ini
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
|
|
)
|
|
|
|
|
|
|
|
type testNested struct {
|
|
|
|
Cities []string `delim:"|"`
|
|
|
|
Visits []time.Time
|
|
|
|
Years []int
|
|
|
|
Numbers []int64
|
|
|
|
Ages []uint
|
|
|
|
Populations []uint64
|
|
|
|
Coordinates []float64
|
|
|
|
Note string
|
|
|
|
Unused int `ini:"-"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type testEmbeded struct {
|
|
|
|
GPA float64
|
|
|
|
}
|
|
|
|
|
|
|
|
type testStruct struct {
|
|
|
|
Name string `ini:"NAME"`
|
|
|
|
Age int
|
|
|
|
Male bool
|
|
|
|
Money float64
|
|
|
|
Born time.Time
|
|
|
|
Time time.Duration `ini:"Duration"`
|
|
|
|
Others testNested
|
|
|
|
*testEmbeded `ini:"grade"`
|
|
|
|
Unused int `ini:"-"`
|
|
|
|
Unsigned uint
|
|
|
|
Omitted bool `ini:"omitthis,omitempty"`
|
|
|
|
Shadows []string `ini:",,allowshadow"`
|
|
|
|
ShadowInts []int `ini:"Shadows,,allowshadow"`
|
|
|
|
}
|
|
|
|
|
|
|
|
const _CONF_DATA_STRUCT = `
|
|
|
|
NAME = Unknwon
|
|
|
|
Age = 21
|
|
|
|
Male = true
|
|
|
|
Money = 1.25
|
|
|
|
Born = 1993-10-07T20:17:05Z
|
|
|
|
Duration = 2h45m
|
|
|
|
Unsigned = 3
|
|
|
|
omitthis = true
|
|
|
|
Shadows = 1, 2
|
|
|
|
Shadows = 3, 4
|
|
|
|
|
|
|
|
[Others]
|
|
|
|
Cities = HangZhou|Boston
|
|
|
|
Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
|
|
|
|
Years = 1993,1994
|
|
|
|
Numbers = 10010,10086
|
|
|
|
Ages = 18,19
|
|
|
|
Populations = 12345678,98765432
|
|
|
|
Coordinates = 192.168,10.11
|
|
|
|
Note = Hello world!
|
|
|
|
|
|
|
|
[grade]
|
|
|
|
GPA = 2.8
|
|
|
|
|
|
|
|
[foo.bar]
|
|
|
|
Here = there
|
|
|
|
When = then
|
|
|
|
`
|
|
|
|
|
|
|
|
type unsupport struct {
|
|
|
|
Byte byte
|
|
|
|
}
|
|
|
|
|
|
|
|
type unsupport2 struct {
|
|
|
|
Others struct {
|
|
|
|
Cities byte
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type unsupport3 struct {
|
|
|
|
Cities byte
|
|
|
|
}
|
|
|
|
|
|
|
|
type unsupport4 struct {
|
|
|
|
*unsupport3 `ini:"Others"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type defaultValue struct {
|
|
|
|
Name string
|
|
|
|
Age int
|
|
|
|
Male bool
|
|
|
|
Money float64
|
|
|
|
Born time.Time
|
|
|
|
Cities []string
|
|
|
|
}
|
|
|
|
|
|
|
|
type fooBar struct {
|
|
|
|
Here, When string
|
|
|
|
}
|
|
|
|
|
|
|
|
const _INVALID_DATA_CONF_STRUCT = `
|
|
|
|
Name =
|
|
|
|
Age = age
|
|
|
|
Male = 123
|
|
|
|
Money = money
|
|
|
|
Born = nil
|
|
|
|
Cities =
|
|
|
|
`
|
|
|
|
|
|
|
|
func Test_Struct(t *testing.T) {
|
|
|
|
Convey("Map to struct", t, func() {
|
|
|
|
Convey("Map file to struct", func() {
|
|
|
|
ts := new(testStruct)
|
|
|
|
So(MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
|
|
|
|
|
|
|
|
So(ts.Name, ShouldEqual, "Unknwon")
|
|
|
|
So(ts.Age, ShouldEqual, 21)
|
|
|
|
So(ts.Male, ShouldBeTrue)
|
|
|
|
So(ts.Money, ShouldEqual, 1.25)
|
|
|
|
So(ts.Unsigned, ShouldEqual, 3)
|
|
|
|
|
|
|
|
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(ts.Born.String(), ShouldEqual, t.String())
|
|
|
|
|
|
|
|
dur, err := time.ParseDuration("2h45m")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(ts.Time.Seconds(), ShouldEqual, dur.Seconds())
|
|
|
|
|
|
|
|
So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston")
|
|
|
|
So(ts.Others.Visits[0].String(), ShouldEqual, t.String())
|
|
|
|
So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]")
|
|
|
|
So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]")
|
|
|
|
So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]")
|
|
|
|
So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]")
|
|
|
|
So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]")
|
|
|
|
So(ts.Others.Note, ShouldEqual, "Hello world!")
|
|
|
|
So(ts.testEmbeded.GPA, ShouldEqual, 2.8)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Map section to struct", func() {
|
|
|
|
foobar := new(fooBar)
|
|
|
|
f, err := Load([]byte(_CONF_DATA_STRUCT))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil)
|
|
|
|
So(foobar.Here, ShouldEqual, "there")
|
|
|
|
So(foobar.When, ShouldEqual, "then")
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Map to non-pointer struct", func() {
|
|
|
|
cfg, err := Load([]byte(_CONF_DATA_STRUCT))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
|
|
|
|
So(cfg.MapTo(testStruct{}), ShouldNotBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Map to unsupported type", func() {
|
|
|
|
cfg, err := Load([]byte(_CONF_DATA_STRUCT))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
|
|
|
|
cfg.NameMapper = func(raw string) string {
|
|
|
|
if raw == "Byte" {
|
|
|
|
return "NAME"
|
|
|
|
}
|
|
|
|
return raw
|
|
|
|
}
|
|
|
|
So(cfg.MapTo(&unsupport{}), ShouldNotBeNil)
|
|
|
|
So(cfg.MapTo(&unsupport2{}), ShouldNotBeNil)
|
|
|
|
So(cfg.MapTo(&unsupport4{}), ShouldNotBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Map to omitempty field", func() {
|
|
|
|
ts := new(testStruct)
|
|
|
|
So(MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
|
|
|
|
|
|
|
|
So(ts.Omitted, ShouldEqual, true)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Map with shadows", func() {
|
|
|
|
cfg, err := LoadSources(LoadOptions{AllowShadows: true}, []byte(_CONF_DATA_STRUCT))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
ts := new(testStruct)
|
|
|
|
So(cfg.MapTo(ts), ShouldBeNil)
|
|
|
|
|
|
|
|
So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4")
|
|
|
|
So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]")
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Map from invalid data source", func() {
|
|
|
|
So(MapTo(&testStruct{}, "hi"), ShouldNotBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Map to wrong types and gain default values", func() {
|
|
|
|
cfg, err := Load([]byte(_INVALID_DATA_CONF_STRUCT))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
dv := &defaultValue{"Joe", 10, true, 1.25, t, []string{"HangZhou", "Boston"}}
|
|
|
|
So(cfg.MapTo(dv), ShouldBeNil)
|
|
|
|
So(dv.Name, ShouldEqual, "Joe")
|
|
|
|
So(dv.Age, ShouldEqual, 10)
|
|
|
|
So(dv.Male, ShouldBeTrue)
|
|
|
|
So(dv.Money, ShouldEqual, 1.25)
|
|
|
|
So(dv.Born.String(), ShouldEqual, t.String())
|
|
|
|
So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston")
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Map to struct in strict mode", t, func() {
|
|
|
|
cfg, err := Load([]byte(`
|
|
|
|
name=bruce
|
|
|
|
age=a30`))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
type Strict struct {
|
|
|
|
Name string `ini:"name"`
|
|
|
|
Age int `ini:"age"`
|
|
|
|
}
|
|
|
|
s := new(Strict)
|
|
|
|
|
|
|
|
So(cfg.Section("").StrictMapTo(s), ShouldNotBeNil)
|
|
|
|
})
|
|
|
|
|
2017-10-22 08:07:36 +00:00
|
|
|
Convey("Map slice in strict mode", t, func() {
|
|
|
|
cfg, err := Load([]byte(`
|
|
|
|
names=alice, bruce`))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
type Strict struct {
|
|
|
|
Names []string `ini:"names"`
|
|
|
|
}
|
|
|
|
s := new(Strict)
|
|
|
|
|
|
|
|
So(cfg.Section("").StrictMapTo(s), ShouldBeNil)
|
|
|
|
So(fmt.Sprint(s.Names), ShouldEqual, "[alice bruce]")
|
|
|
|
})
|
|
|
|
|
2017-07-23 12:24:45 +00:00
|
|
|
Convey("Reflect from struct", t, func() {
|
|
|
|
type Embeded struct {
|
2017-10-22 08:07:36 +00:00
|
|
|
Dates []time.Time `delim:"|" comment:"Time data"`
|
2017-07-23 12:24:45 +00:00
|
|
|
Places []string
|
|
|
|
Years []int
|
|
|
|
Numbers []int64
|
|
|
|
Ages []uint
|
|
|
|
Populations []uint64
|
|
|
|
Coordinates []float64
|
|
|
|
None []int
|
|
|
|
}
|
|
|
|
type Author struct {
|
|
|
|
Name string `ini:"NAME"`
|
|
|
|
Male bool
|
2017-10-22 08:07:36 +00:00
|
|
|
Age int `comment:"Author's age"`
|
2017-07-23 12:24:45 +00:00
|
|
|
Height uint
|
|
|
|
GPA float64
|
|
|
|
Date time.Time
|
|
|
|
NeverMind string `ini:"-"`
|
2017-10-22 08:07:36 +00:00
|
|
|
*Embeded `ini:"infos" comment:"Embeded section"`
|
2017-07-23 12:24:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
a := &Author{"Unknwon", true, 21, 100, 2.8, t, "",
|
|
|
|
&Embeded{
|
|
|
|
[]time.Time{t, t},
|
|
|
|
[]string{"HangZhou", "Boston"},
|
|
|
|
[]int{1993, 1994},
|
|
|
|
[]int64{10010, 10086},
|
|
|
|
[]uint{18, 19},
|
|
|
|
[]uint64{12345678, 98765432},
|
|
|
|
[]float64{192.168, 10.11},
|
|
|
|
[]int{},
|
|
|
|
}}
|
|
|
|
cfg := Empty()
|
|
|
|
So(ReflectFrom(cfg, a), ShouldBeNil)
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
_, err = cfg.WriteTo(&buf)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(buf.String(), ShouldEqual, `NAME = Unknwon
|
|
|
|
Male = true
|
2017-10-22 08:07:36 +00:00
|
|
|
; Author's age
|
2017-07-23 12:24:45 +00:00
|
|
|
Age = 21
|
|
|
|
Height = 100
|
|
|
|
GPA = 2.8
|
|
|
|
Date = 1993-10-07T20:17:05Z
|
|
|
|
|
2017-10-22 08:07:36 +00:00
|
|
|
; Embeded section
|
2017-07-23 12:24:45 +00:00
|
|
|
[infos]
|
2017-10-22 08:07:36 +00:00
|
|
|
; Time data
|
2017-07-23 12:24:45 +00:00
|
|
|
Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z
|
|
|
|
Places = HangZhou,Boston
|
|
|
|
Years = 1993,1994
|
|
|
|
Numbers = 10010,10086
|
|
|
|
Ages = 18,19
|
|
|
|
Populations = 12345678,98765432
|
|
|
|
Coordinates = 192.168,10.11
|
|
|
|
None =
|
|
|
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
Convey("Reflect from non-point struct", func() {
|
|
|
|
So(ReflectFrom(cfg, Author{}), ShouldNotBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Reflect from struct with omitempty", func() {
|
|
|
|
cfg := Empty()
|
|
|
|
type SpecialStruct struct {
|
|
|
|
FirstName string `ini:"first_name"`
|
|
|
|
LastName string `ini:"last_name"`
|
|
|
|
JustOmitMe string `ini:"omitempty"`
|
|
|
|
LastLogin time.Time `ini:"last_login,omitempty"`
|
|
|
|
LastLogin2 time.Time `ini:",omitempty"`
|
|
|
|
NotEmpty int `ini:"omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
So(ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil)
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
_, err = cfg.WriteTo(&buf)
|
|
|
|
So(buf.String(), ShouldEqual, `first_name = John
|
|
|
|
last_name = Doe
|
|
|
|
omitempty = 9
|
|
|
|
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
type testMapper struct {
|
|
|
|
PackageName string
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_NameGetter(t *testing.T) {
|
|
|
|
Convey("Test name mappers", t, func() {
|
|
|
|
So(MapToWithMapper(&testMapper{}, TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil)
|
|
|
|
|
|
|
|
cfg, err := Load([]byte("PACKAGE_NAME=ini"))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
|
|
|
|
cfg.NameMapper = AllCapsUnderscore
|
|
|
|
tg := new(testMapper)
|
|
|
|
So(cfg.MapTo(tg), ShouldBeNil)
|
|
|
|
So(tg.PackageName, ShouldEqual, "ini")
|
|
|
|
})
|
|
|
|
}
|