2017-05-11 14:39:54 +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"
|
|
|
|
"io/ioutil"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
|
|
)
|
|
|
|
|
|
|
|
func Test_Version(t *testing.T) {
|
|
|
|
Convey("Get version", t, func() {
|
|
|
|
So(Version(), ShouldEqual, _VERSION)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const _CONF_DATA = `
|
|
|
|
; Package name
|
|
|
|
NAME = ini
|
|
|
|
; Package version
|
|
|
|
VERSION = v1
|
|
|
|
; Package import path
|
|
|
|
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
|
|
|
|
|
|
|
# Information about package author
|
|
|
|
# Bio can be written in multiple lines.
|
|
|
|
[author]
|
|
|
|
NAME = Unknwon ; Succeeding comment
|
|
|
|
E-MAIL = fake@localhost
|
|
|
|
GITHUB = https://github.com/%(NAME)s
|
|
|
|
BIO = """Gopher.
|
|
|
|
Coding addict.
|
|
|
|
Good man.
|
|
|
|
""" # Succeeding comment
|
|
|
|
|
|
|
|
[package]
|
|
|
|
CLONE_URL = https://%(IMPORT_PATH)s
|
|
|
|
|
|
|
|
[package.sub]
|
|
|
|
UNUSED_KEY = should be deleted
|
|
|
|
|
|
|
|
[features]
|
|
|
|
-: Support read/write comments of keys and sections
|
|
|
|
-: Support auto-increment of key names
|
|
|
|
-: Support load multiple files to overwrite key values
|
|
|
|
|
|
|
|
[types]
|
|
|
|
STRING = str
|
|
|
|
BOOL = true
|
|
|
|
BOOL_FALSE = false
|
|
|
|
FLOAT64 = 1.25
|
|
|
|
INT = 10
|
|
|
|
TIME = 2015-01-01T20:17:05Z
|
|
|
|
DURATION = 2h45m
|
|
|
|
UINT = 3
|
|
|
|
|
|
|
|
[array]
|
|
|
|
STRINGS = en, zh, de
|
|
|
|
FLOAT64S = 1.1, 2.2, 3.3
|
|
|
|
INTS = 1, 2, 3
|
|
|
|
UINTS = 1, 2, 3
|
|
|
|
TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
|
|
|
|
|
|
|
|
[note]
|
|
|
|
empty_lines = next line is empty\
|
|
|
|
|
|
|
|
; Comment before the section
|
|
|
|
[comments] ; This is a comment for the section too
|
|
|
|
; Comment before key
|
|
|
|
key = "value"
|
|
|
|
key2 = "value2" ; This is a comment for key2
|
|
|
|
key3 = "one", "two", "three"
|
|
|
|
|
|
|
|
[advance]
|
|
|
|
value with quotes = "some value"
|
|
|
|
value quote2 again = 'some value'
|
|
|
|
includes comment sign = ` + "`" + "my#password" + "`" + `
|
|
|
|
includes comment sign2 = ` + "`" + "my;password" + "`" + `
|
|
|
|
true = 2+3=5
|
|
|
|
"1+1=2" = true
|
|
|
|
"""6+1=7""" = true
|
|
|
|
"""` + "`" + `5+5` + "`" + `""" = 10
|
|
|
|
` + "`" + `"6+6"` + "`" + ` = 12
|
|
|
|
` + "`" + `7-2=4` + "`" + ` = false
|
|
|
|
ADDRESS = ` + "`" + `404 road,
|
|
|
|
NotFound, State, 50000` + "`" + `
|
|
|
|
|
|
|
|
two_lines = how about \
|
|
|
|
continuation lines?
|
|
|
|
lots_of_lines = 1 \
|
|
|
|
2 \
|
|
|
|
3 \
|
|
|
|
4 \
|
|
|
|
`
|
|
|
|
|
|
|
|
func Test_Load(t *testing.T) {
|
|
|
|
Convey("Load from data sources", t, func() {
|
|
|
|
|
|
|
|
Convey("Load with empty data", func() {
|
|
|
|
So(Empty(), ShouldNotBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Load with multiple data sources", func() {
|
|
|
|
cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini", ioutil.NopCloser(bytes.NewReader([]byte(_CONF_DATA))))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
|
|
|
|
f, err := Load([]byte(_CONF_DATA), "testdata/404.ini")
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
So(f, ShouldBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Load with io.ReadCloser", func() {
|
|
|
|
cfg, err := Load(ioutil.NopCloser(bytes.NewReader([]byte(_CONF_DATA))))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
|
|
|
|
So(cfg.Section("").Key("NAME").String(), ShouldEqual, "ini")
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Bad load process", t, func() {
|
|
|
|
|
|
|
|
Convey("Load from invalid data sources", func() {
|
|
|
|
_, err := Load(_CONF_DATA)
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
|
|
|
|
f, err := Load("testdata/404.ini")
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
So(f, ShouldBeNil)
|
|
|
|
|
|
|
|
_, err = Load(1)
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
|
|
|
|
_, err = Load([]byte(""), 1)
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Load with bad section name", func() {
|
|
|
|
_, err := Load([]byte("[]"))
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
|
|
|
|
_, err = Load([]byte("["))
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Load with bad keys", func() {
|
|
|
|
_, err := Load([]byte(`"""name`))
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
|
|
|
|
_, err = Load([]byte(`"""name"""`))
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
|
|
|
|
_, err = Load([]byte(`""=1`))
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
|
|
|
|
_, err = Load([]byte(`=`))
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
|
|
|
|
_, err = Load([]byte(`name`))
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Load with bad values", func() {
|
|
|
|
_, err := Load([]byte(`name="""Unknwon`))
|
|
|
|
So(err, ShouldNotBeNil)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Get section and key insensitively", t, func() {
|
|
|
|
cfg, err := InsensitiveLoad([]byte(_CONF_DATA), "testdata/conf.ini")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
|
|
|
|
sec, err := cfg.GetSection("Author")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(sec, ShouldNotBeNil)
|
|
|
|
|
|
|
|
key, err := sec.GetKey("E-mail")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(key, ShouldNotBeNil)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Load with ignoring continuation lines", t, func() {
|
|
|
|
cfg, err := LoadSources(LoadOptions{IgnoreContinuation: true}, []byte(`key1=a\b\
|
|
|
|
key2=c\d\`))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
|
|
|
|
So(cfg.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
|
|
|
|
So(cfg.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Load with ignoring inline comments", t, func() {
|
|
|
|
cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, []byte(`key1=value ;comment
|
|
|
|
key2=value #comment2`))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
|
|
|
|
So(cfg.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
|
|
|
|
So(cfg.Section("").Key("key2").String(), ShouldEqual, `value #comment2`)
|
2017-07-23 07:51:42 +00:00
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
cfg.WriteTo(&buf)
|
|
|
|
So(buf.String(), ShouldEqual, `key1 = value ;comment
|
|
|
|
key2 = value #comment2
|
|
|
|
|
|
|
|
`)
|
2017-05-11 14:39:54 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Load with boolean type keys", t, func() {
|
|
|
|
cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, []byte(`key1=hello
|
|
|
|
key2
|
|
|
|
#key3
|
|
|
|
key4
|
|
|
|
key5`))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
|
|
|
|
So(strings.Join(cfg.Section("").KeyStrings(), ","), ShouldEqual, "key1,key2,key4,key5")
|
|
|
|
So(cfg.Section("").Key("key2").MustBool(false), ShouldBeTrue)
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
cfg.WriteTo(&buf)
|
|
|
|
// there is always a trailing \n at the end of the section
|
|
|
|
So(buf.String(), ShouldEqual, `key1 = hello
|
|
|
|
key2
|
|
|
|
#key3
|
|
|
|
key4
|
|
|
|
key5
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_File_ChildSections(t *testing.T) {
|
|
|
|
Convey("Find child sections by parent name", t, func() {
|
|
|
|
cfg, err := Load([]byte(`
|
|
|
|
[node]
|
|
|
|
|
|
|
|
[node.biz1]
|
|
|
|
|
|
|
|
[node.biz2]
|
|
|
|
|
|
|
|
[node.biz3]
|
|
|
|
|
|
|
|
[node.bizN]
|
|
|
|
`))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
|
|
|
|
children := cfg.ChildSections("node")
|
|
|
|
names := make([]string, len(children))
|
|
|
|
for i := range children {
|
|
|
|
names[i] = children[i].name
|
|
|
|
}
|
|
|
|
So(strings.Join(names, ","), ShouldEqual, "node.biz1,node.biz2,node.biz3,node.bizN")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_LooseLoad(t *testing.T) {
|
|
|
|
Convey("Loose load from data sources", t, func() {
|
|
|
|
Convey("Loose load mixed with nonexistent file", func() {
|
|
|
|
cfg, err := LooseLoad("testdata/404.ini")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
var fake struct {
|
|
|
|
Name string `ini:"name"`
|
|
|
|
}
|
|
|
|
So(cfg.MapTo(&fake), ShouldBeNil)
|
|
|
|
|
|
|
|
cfg, err = LooseLoad([]byte("name=Unknwon"), "testdata/404.ini")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg.Section("").Key("name").String(), ShouldEqual, "Unknwon")
|
|
|
|
So(cfg.MapTo(&fake), ShouldBeNil)
|
|
|
|
So(fake.Name, ShouldEqual, "Unknwon")
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_File_Append(t *testing.T) {
|
|
|
|
Convey("Append data sources", t, func() {
|
|
|
|
cfg, err := Load([]byte(""))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
|
|
|
|
So(cfg.Append([]byte(""), []byte("")), ShouldBeNil)
|
|
|
|
|
|
|
|
Convey("Append bad data sources", func() {
|
|
|
|
So(cfg.Append(1), ShouldNotBeNil)
|
|
|
|
So(cfg.Append([]byte(""), 1), ShouldNotBeNil)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_File_WriteTo(t *testing.T) {
|
|
|
|
Convey("Write to somewhere", t, func() {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
cfg := Empty()
|
|
|
|
cfg.WriteTo(&buf)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_File_SaveTo_WriteTo(t *testing.T) {
|
|
|
|
Convey("Save file", t, func() {
|
|
|
|
cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
|
|
|
|
cfg.Section("").Key("NAME").Comment = "Package name"
|
|
|
|
cfg.Section("author").Comment = `Information about package author
|
|
|
|
# Bio can be written in multiple lines.`
|
|
|
|
cfg.Section("advanced").Key("val w/ pound").SetValue("my#password")
|
|
|
|
cfg.Section("advanced").Key("longest key has a colon : yes/no").SetValue("yes")
|
|
|
|
So(cfg.SaveTo("testdata/conf_out.ini"), ShouldBeNil)
|
|
|
|
|
|
|
|
cfg.Section("author").Key("NAME").Comment = "This is author name"
|
|
|
|
|
|
|
|
So(cfg.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil)
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
_, err = cfg.WriteToIndent(&buf, "\t")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(buf.String(), ShouldEqual, `; Package name
|
|
|
|
NAME = ini
|
|
|
|
; Package version
|
|
|
|
VERSION = v1
|
|
|
|
; Package import path
|
|
|
|
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
|
|
|
|
|
|
|
; Information about package author
|
|
|
|
# Bio can be written in multiple lines.
|
|
|
|
[author]
|
|
|
|
; This is author name
|
|
|
|
NAME = Unknwon
|
|
|
|
E-MAIL = u@gogs.io
|
|
|
|
GITHUB = https://github.com/%(NAME)s
|
|
|
|
# Succeeding comment
|
|
|
|
BIO = """Gopher.
|
|
|
|
Coding addict.
|
|
|
|
Good man.
|
|
|
|
"""
|
|
|
|
|
|
|
|
[package]
|
|
|
|
CLONE_URL = https://%(IMPORT_PATH)s
|
|
|
|
|
|
|
|
[package.sub]
|
|
|
|
UNUSED_KEY = should be deleted
|
|
|
|
|
|
|
|
[features]
|
|
|
|
- = Support read/write comments of keys and sections
|
|
|
|
- = Support auto-increment of key names
|
|
|
|
- = Support load multiple files to overwrite key values
|
|
|
|
|
|
|
|
[types]
|
|
|
|
STRING = str
|
|
|
|
BOOL = true
|
|
|
|
BOOL_FALSE = false
|
|
|
|
FLOAT64 = 1.25
|
|
|
|
INT = 10
|
|
|
|
TIME = 2015-01-01T20:17:05Z
|
|
|
|
DURATION = 2h45m
|
|
|
|
UINT = 3
|
|
|
|
|
|
|
|
[array]
|
|
|
|
STRINGS = en, zh, de
|
|
|
|
FLOAT64S = 1.1, 2.2, 3.3
|
|
|
|
INTS = 1, 2, 3
|
|
|
|
UINTS = 1, 2, 3
|
|
|
|
TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
|
|
|
|
|
|
|
|
[note]
|
|
|
|
empty_lines = next line is empty
|
|
|
|
|
|
|
|
; Comment before the section
|
|
|
|
; This is a comment for the section too
|
|
|
|
[comments]
|
|
|
|
; Comment before key
|
|
|
|
key = value
|
|
|
|
; This is a comment for key2
|
|
|
|
key2 = value2
|
|
|
|
key3 = "one", "two", "three"
|
|
|
|
|
|
|
|
[advance]
|
|
|
|
value with quotes = some value
|
|
|
|
value quote2 again = some value
|
|
|
|
includes comment sign = `+"`"+"my#password"+"`"+`
|
|
|
|
includes comment sign2 = `+"`"+"my;password"+"`"+`
|
|
|
|
true = 2+3=5
|
|
|
|
`+"`"+`1+1=2`+"`"+` = true
|
|
|
|
`+"`"+`6+1=7`+"`"+` = true
|
|
|
|
"""`+"`"+`5+5`+"`"+`""" = 10
|
|
|
|
`+"`"+`"6+6"`+"`"+` = 12
|
|
|
|
`+"`"+`7-2=4`+"`"+` = false
|
|
|
|
ADDRESS = """404 road,
|
|
|
|
NotFound, State, 50000"""
|
|
|
|
two_lines = how about continuation lines?
|
|
|
|
lots_of_lines = 1 2 3 4
|
|
|
|
|
|
|
|
[advanced]
|
|
|
|
val w/ pound = `+"`"+`my#password`+"`"+`
|
|
|
|
`+"`"+`longest key has a colon : yes/no`+"`"+` = yes
|
|
|
|
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_File_WriteTo_SectionRaw(t *testing.T) {
|
|
|
|
Convey("Write a INI with a raw section", t, func() {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
cfg, err := LoadSources(
|
|
|
|
LoadOptions{
|
|
|
|
UnparseableSections: []string{"CORE_LESSON", "COMMENTS"},
|
|
|
|
},
|
|
|
|
"testdata/aicc.ini")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(cfg, ShouldNotBeNil)
|
|
|
|
cfg.WriteToIndent(&buf, "\t")
|
|
|
|
So(buf.String(), ShouldEqual, `[Core]
|
|
|
|
Lesson_Location = 87
|
|
|
|
Lesson_Status = C
|
|
|
|
Score = 3
|
|
|
|
Time = 00:02:30
|
|
|
|
|
|
|
|
[CORE_LESSON]
|
|
|
|
my lesson state data – 1111111111111111111000000000000000001110000
|
|
|
|
111111111111111111100000000000111000000000 – end my lesson state data
|
|
|
|
[COMMENTS]
|
|
|
|
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helpers for slice tests.
|
|
|
|
func float64sEqual(values []float64, expected ...float64) {
|
|
|
|
So(values, ShouldHaveLength, len(expected))
|
|
|
|
for i, v := range expected {
|
|
|
|
So(values[i], ShouldEqual, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func intsEqual(values []int, expected ...int) {
|
|
|
|
So(values, ShouldHaveLength, len(expected))
|
|
|
|
for i, v := range expected {
|
|
|
|
So(values[i], ShouldEqual, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func int64sEqual(values []int64, expected ...int64) {
|
|
|
|
So(values, ShouldHaveLength, len(expected))
|
|
|
|
for i, v := range expected {
|
|
|
|
So(values[i], ShouldEqual, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func uintsEqual(values []uint, expected ...uint) {
|
|
|
|
So(values, ShouldHaveLength, len(expected))
|
|
|
|
for i, v := range expected {
|
|
|
|
So(values[i], ShouldEqual, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func uint64sEqual(values []uint64, expected ...uint64) {
|
|
|
|
So(values, ShouldHaveLength, len(expected))
|
|
|
|
for i, v := range expected {
|
|
|
|
So(values[i], ShouldEqual, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func timesEqual(values []time.Time, expected ...time.Time) {
|
|
|
|
So(values, ShouldHaveLength, len(expected))
|
|
|
|
for i, v := range expected {
|
|
|
|
So(values[i].String(), ShouldEqual, v.String())
|
|
|
|
}
|
|
|
|
}
|