Rainbond/vendor/github.com/src-d/gcfg/read_test.go
2018-01-22 16:50:23 +08:00

475 lines
17 KiB
Go

package gcfg
import (
"fmt"
"math/big"
"os"
"reflect"
"testing"
"bytes"
"strconv"
"github.com/pkg/errors"
)
const (
// 64 spaces
sp64 = " "
// 512 spaces
sp512 = sp64 + sp64 + sp64 + sp64 + sp64 + sp64 + sp64 + sp64
// 4096 spaces
sp4096 = sp512 + sp512 + sp512 + sp512 + sp512 + sp512 + sp512 + sp512
)
type cBasic struct {
Section cBasicS1
Hyphen_In_Section cBasicS2
unexported cBasicS1
Exported cBasicS3
TagName cBasicS1 `gcfg:"tag-name"`
}
type cBasicS1 struct {
Name string
Int int
PName *string
}
type cBasicS2 struct {
Hyphen_In_Name string
}
type cBasicS3 struct {
unexported string
}
type nonMulti []string
type unmarshalable string
func (u *unmarshalable) UnmarshalText(text []byte) error {
s := string(text)
if s == "error" {
return fmt.Errorf("%s", s)
}
*u = unmarshalable(s)
return nil
}
var _ textUnmarshaler = new(unmarshalable)
type cUni struct {
X甲 cUniS1
XSection cUniS2
}
type cUniS1 struct {
X乙 string
}
type cUniS2 struct {
XName string
}
type cMulti struct {
M1 cMultiS1
M2 cMultiS2
M3 cMultiS3
}
type cMultiS1 struct{ Multi []string }
type cMultiS2 struct{ NonMulti nonMulti }
type cMultiS3 struct{ PMulti *[]string }
type cSubs struct{ Sub map[string]*cSubsS1 }
type cSubsS1 struct{ Name string }
type cBool struct{ Section cBoolS1 }
type cBoolS1 struct{ Bool bool }
type cTxUnm struct{ Section cTxUnmS1 }
type cTxUnmS1 struct{ Name unmarshalable }
type cNum struct {
N1 cNumS1
N2 cNumS2
N3 cNumS3
}
type cNumS1 struct {
Int int
IntDHO int `gcfg:",int=dho"`
Big *big.Int
}
type cNumS2 struct {
MultiInt []int
MultiBig []*big.Int
}
type cNumS3 struct{ FileMode os.FileMode }
type readtest struct {
gcfg string
exp interface{}
ok bool
}
func newString(s string) *string { return &s }
func newStringSlice(s ...string) *[]string { return &s }
var readtests = []struct {
group string
tests []readtest
}{{"scanning", []readtest{
{"[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
// hyphen in name
{"[hyphen-in-section]\nhyphen-in-name=value", &cBasic{Hyphen_In_Section: cBasicS2{Hyphen_In_Name: "value"}}, true},
// quoted string value
{"[section]\nname=\"\"", &cBasic{Section: cBasicS1{Name: ""}}, true},
{"[section]\nname=\" \"", &cBasic{Section: cBasicS1{Name: " "}}, true},
{"[section]\nname=\"value\"", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]\nname=\" value \"", &cBasic{Section: cBasicS1{Name: " value "}}, true},
{"\n[section]\nname=\"va ; lue\"", &cBasic{Section: cBasicS1{Name: "va ; lue"}}, true},
{"[section]\nname=\"val\" \"ue\"", &cBasic{Section: cBasicS1{Name: "val ue"}}, true},
{"[section]\nname=\"value", &cBasic{}, false},
// escape sequences
{"[section]\nname=\"va\\\\lue\"", &cBasic{Section: cBasicS1{Name: "va\\lue"}}, true},
{"[section]\nname=\"va\\\"lue\"", &cBasic{Section: cBasicS1{Name: "va\"lue"}}, true},
{"[section]\nname=\"va\\nlue\"", &cBasic{Section: cBasicS1{Name: "va\nlue"}}, true},
{"[section]\nname=\"va\\tlue\"", &cBasic{Section: cBasicS1{Name: "va\tlue"}}, true},
{"\n[section]\nname=\\", &cBasic{}, false},
{"\n[section]\nname=\\a", &cBasic{}, false},
{"\n[section]\nname=\"val\\a\"", &cBasic{}, false},
{"\n[section]\nname=val\\", &cBasic{}, false},
{"\n[sub \"A\\\n\"]\nname=value", &cSubs{}, false},
{"\n[sub \"A\\\t\"]\nname=value", &cSubs{}, false},
// broken line
{"[section]\nname=value \\\n value", &cBasic{Section: cBasicS1{Name: "value value"}}, true},
{"[section]\nname=\"value \\\n value\"", &cBasic{}, false},
}}, {"scanning:whitespace", []readtest{
{" \n[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{" [section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"\t[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[ section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section ]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]\n name=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]\nname =value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]\nname= value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]\nname=value ", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]\r\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]\r\nname=value\r\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{";cmnt\r\n[section]\r\nname=value\r\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
// long lines
{sp4096 + "[section]\nname=value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[" + sp4096 + "section]\nname=value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section" + sp4096 + "]\nname=value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]" + sp4096 + "\nname=value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]\n" + sp4096 + "name=value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]\nname" + sp4096 + "=value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]\nname=" + sp4096 + "value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]\nname=value\n" + sp4096, &cBasic{Section: cBasicS1{Name: "value"}}, true},
}}, {"scanning:comments", []readtest{
{"; cmnt\n[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"# cmnt\n[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{" ; cmnt\n[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"\t; cmnt\n[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"\n[section]; cmnt\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"\n[section] ; cmnt\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"\n[section]\nname=value; cmnt", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"\n[section]\nname=value ; cmnt", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"\n[section]\nname=\"value\" ; cmnt", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"\n[section]\nname=value ; \"cmnt", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"\n[section]\nname=\"va ; lue\" ; cmnt", &cBasic{Section: cBasicS1{Name: "va ; lue"}}, true},
{"\n[section]\nname=; cmnt", &cBasic{Section: cBasicS1{Name: ""}}, true},
}}, {"scanning:subsections", []readtest{
{"\n[sub \"A\"]\nname=value", &cSubs{map[string]*cSubsS1{"A": &cSubsS1{"value"}}}, true},
{"\n[sub \"b\"]\nname=value", &cSubs{map[string]*cSubsS1{"b": &cSubsS1{"value"}}}, true},
{"\n[sub \"A\\\\\"]\nname=value", &cSubs{map[string]*cSubsS1{"A\\": &cSubsS1{"value"}}}, true},
{"\n[sub \"A\\\"\"]\nname=value", &cSubs{map[string]*cSubsS1{"A\"": &cSubsS1{"value"}}}, true},
}}, {"syntax", []readtest{
// invalid line
{"\n[section]\n=", &cBasic{}, false},
// no section
{"name=value", &cBasic{}, false},
// empty section
{"\n[]\nname=value", &cBasic{}, false},
// empty subsection
{"\n[sub \"\"]\nname=value", &cSubs{}, false},
}}, {"setting", []readtest{
{"[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
// pointer
{"[section]", &cBasic{Section: cBasicS1{PName: nil}}, true},
{"[section]\npname=value", &cBasic{Section: cBasicS1{PName: newString("value")}}, true},
{"[m3]", &cMulti{M3: cMultiS3{PMulti: nil}}, true},
{"[m3]\npmulti", &cMulti{M3: cMultiS3{PMulti: newStringSlice()}}, true},
{"[m3]\npmulti=value", &cMulti{M3: cMultiS3{PMulti: newStringSlice("value")}}, true},
{"[m3]\npmulti=value1\npmulti=value2", &cMulti{M3: cMultiS3{PMulti: newStringSlice("value1", "value2")}}, true},
// section name not matched
{"\n[nonexistent]\nname=value", &cBasic{}, false},
// subsection name not matched
{"\n[section \"nonexistent\"]\nname=value", &cBasic{}, false},
// variable name not matched
{"\n[section]\nnonexistent=value", &cBasic{}, false},
// hyphen in name
{"[hyphen-in-section]\nhyphen-in-name=value", &cBasic{Hyphen_In_Section: cBasicS2{Hyphen_In_Name: "value"}}, true},
// ignore unexported fields
{"[unexported]\nname=value", &cBasic{}, false},
{"[exported]\nunexported=value", &cBasic{}, false},
// 'X' prefix for non-upper/lower-case letters
{"[甲]\n乙=丙", &cUni{X甲: cUniS1{X乙: "丙"}}, true},
//{"[section]\nxname=value", &cBasic{XSection: cBasicS4{XName: "value"}}, false},
//{"[xsection]\nname=value", &cBasic{XSection: cBasicS4{XName: "value"}}, false},
// name specified as struct tag
{"[tag-name]\nname=value", &cBasic{TagName: cBasicS1{Name: "value"}}, true},
// empty subsections
{"\n[sub \"A\"]\n[sub \"B\"]", &cSubs{map[string]*cSubsS1{"A": &cSubsS1{}, "B": &cSubsS1{}}}, true},
}}, {"multivalue", []readtest{
// unnamed slice type: treat as multi-value
{"\n[m1]", &cMulti{M1: cMultiS1{}}, true},
{"\n[m1]\nmulti=value", &cMulti{M1: cMultiS1{[]string{"value"}}}, true},
{"\n[m1]\nmulti=value1\nmulti=value2", &cMulti{M1: cMultiS1{[]string{"value1", "value2"}}}, true},
// "blank" empties multi-valued slice -- here same result as above
{"\n[m1]\nmulti\nmulti=value1\nmulti=value2", &cMulti{M1: cMultiS1{[]string{"value1", "value2"}}}, true},
// named slice type: do not treat as multi-value
{"\n[m2]", &cMulti{}, true},
{"\n[m2]\nmulti=value", &cMulti{}, false},
{"\n[m2]\nmulti=value1\nmulti=value2", &cMulti{}, false},
}}, {"type:string", []readtest{
{"[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
{"[section]\nname=", &cBasic{Section: cBasicS1{Name: ""}}, true},
}}, {"type:bool", []readtest{
// explicit values
{"[section]\nbool=true", &cBool{cBoolS1{true}}, true},
{"[section]\nbool=yes", &cBool{cBoolS1{true}}, true},
{"[section]\nbool=on", &cBool{cBoolS1{true}}, true},
{"[section]\nbool=1", &cBool{cBoolS1{true}}, true},
{"[section]\nbool=tRuE", &cBool{cBoolS1{true}}, true},
{"[section]\nbool=false", &cBool{cBoolS1{false}}, true},
{"[section]\nbool=no", &cBool{cBoolS1{false}}, true},
{"[section]\nbool=off", &cBool{cBoolS1{false}}, true},
{"[section]\nbool=0", &cBool{cBoolS1{false}}, true},
{"[section]\nbool=NO", &cBool{cBoolS1{false}}, true},
// "blank" value handled as true
{"[section]\nbool", &cBool{cBoolS1{true}}, true},
// bool parse errors
{"[section]\nbool=maybe", &cBool{}, false},
{"[section]\nbool=t", &cBool{}, false},
{"[section]\nbool=truer", &cBool{}, false},
{"[section]\nbool=2", &cBool{}, false},
{"[section]\nbool=-1", &cBool{}, false},
}}, {"type:numeric", []readtest{
{"[section]\nint=0", &cBasic{Section: cBasicS1{Int: 0}}, true},
{"[section]\nint=1", &cBasic{Section: cBasicS1{Int: 1}}, true},
{"[section]\nint=-1", &cBasic{Section: cBasicS1{Int: -1}}, true},
{"[section]\nint=0.2", &cBasic{}, false},
{"[section]\nint=1e3", &cBasic{}, false},
// primitive [u]int(|8|16|32|64) and big.Int is parsed as dec or hex (not octal)
{"[n1]\nint=010", &cNum{N1: cNumS1{Int: 10}}, true},
{"[n1]\nint=0x10", &cNum{N1: cNumS1{Int: 0x10}}, true},
{"[n1]\nbig=1", &cNum{N1: cNumS1{Big: big.NewInt(1)}}, true},
{"[n1]\nbig=0x10", &cNum{N1: cNumS1{Big: big.NewInt(0x10)}}, true},
{"[n1]\nbig=010", &cNum{N1: cNumS1{Big: big.NewInt(10)}}, true},
{"[n2]\nmultiint=010", &cNum{N2: cNumS2{MultiInt: []int{10}}}, true},
{"[n2]\nmultibig=010", &cNum{N2: cNumS2{MultiBig: []*big.Int{big.NewInt(10)}}}, true},
// set parse mode for int types via struct tag
{"[n1]\nintdho=010", &cNum{N1: cNumS1{IntDHO: 010}}, true},
// octal allowed for named type
{"[n3]\nfilemode=0777", &cNum{N3: cNumS3{FileMode: 0777}}, true},
}}, {"type:textUnmarshaler", []readtest{
{"[section]\nname=value", &cTxUnm{Section: cTxUnmS1{Name: "value"}}, true},
{"[section]\nname=error", &cTxUnm{}, false},
}},
}
func TestReadStringInto(t *testing.T) {
for _, tg := range readtests {
for i, tt := range tg.tests {
id := fmt.Sprintf("%s:%d", tg.group, i)
testRead(t, id, tt)
}
}
}
func TestReadStringIntoMultiBlankPreset(t *testing.T) {
tt := readtest{"\n[m1]\nmulti\nmulti=value1\nmulti=value2", &cMulti{M1: cMultiS1{[]string{"value1", "value2"}}}, true}
cfg := &cMulti{M1: cMultiS1{[]string{"preset1", "preset2"}}}
testReadInto(t, "multi:blank", tt, cfg)
}
func testRead(t *testing.T, id string, tt readtest) {
// get the type of the expected result
restyp := reflect.TypeOf(tt.exp).Elem()
// create a new instance to hold the actual result
res := reflect.New(restyp).Interface()
testReadInto(t, id, tt, res)
}
func testReadInto(t *testing.T, id string, tt readtest, res interface{}) {
err := ReadStringInto(res, tt.gcfg)
if tt.ok {
if err != nil {
t.Errorf("%s fail: got error %v, wanted ok", id, err)
return
} else if !reflect.DeepEqual(res, tt.exp) {
t.Errorf("%s fail: got value %#v, wanted value %#v", id, res, tt.exp)
return
}
if !testing.Short() {
t.Logf("%s pass: got value %#v", id, res)
}
} else { // !tt.ok
if err == nil {
t.Errorf("%s fail: got value %#v, wanted error", id, res)
return
}
if !testing.Short() {
t.Logf("%s pass: got error %v", id, err)
}
}
}
func TestReadFileInto(t *testing.T) {
res := &struct{ Section struct{ Name string } }{}
err := ReadFileInto(res, "testdata/gcfg_test.gcfg")
if err != nil {
t.Error(err)
}
if "value" != res.Section.Name {
t.Errorf("got %q, wanted %q", res.Section.Name, "value")
}
}
func TestReadFileIntoUnicode(t *testing.T) {
res := &struct{ X甲 struct{ X乙 string } }{}
err := ReadFileInto(res, "testdata/gcfg_unicode_test.gcfg")
if err != nil {
t.Error(err)
}
if "丙" != res.X甲.X乙 {
t.Errorf("got %q, wanted %q", res.X甲.X乙, "丙")
}
}
func TestReadStringIntoSubsectDefaults(t *testing.T) {
type subsect struct {
Color string
Orientation string
}
res := &struct {
Default_Profile subsect
Profile map[string]*subsect
}{Default_Profile: subsect{Color: "green"}}
cfg := `
[profile "one"]
orientation = left`
err := ReadStringInto(res, cfg)
if err != nil {
t.Error(err)
}
if res.Profile["one"].Color != "green" {
t.Errorf("got %q; want %q", res.Profile["one"].Color, "green")
}
}
func TestReadStringIntoExtraData(t *testing.T) {
res := &struct {
Section struct {
Name string
}
}{}
cfg := `
[section]
name = value
name2 = value2`
err := FatalOnly(ReadStringInto(res, cfg))
if err != nil {
t.Error(err)
}
if res.Section.Name != "value" {
t.Errorf("res.Section.Name=%q; want %q", res.Section.Name, "value")
}
}
func TestReadWithCallback(t *testing.T) {
results := [][]string{}
cb := func(s string, ss string, k string, v string, bv bool) error {
results = append(results, []string{s, ss, k, v, strconv.FormatBool(bv)})
return nil
}
text := `
[sect1]
key1=value1
[sect1 "subsect1"]
key2=value2
key3=value3
key4
key5=
[sect1 "subsect2"]
[sect2]
`
expected := [][]string{
[]string{"sect1", "", "", "", "true"},
[]string{"sect1", "", "key1", "value1", "false"},
[]string{"sect1", "subsect1", "", "", "true"},
[]string{"sect1", "subsect1", "key2", "value2", "false"},
[]string{"sect1", "subsect1", "key3", "value3", "false"},
[]string{"sect1", "subsect1", "key4", "", "true"},
[]string{"sect1", "subsect1", "key5", "", "false"},
[]string{"sect1", "subsect2", "", "", "true"},
[]string{"sect2", "", "", "", "true"},
}
err := ReadWithCallback(bytes.NewReader([]byte(text)), cb)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(results, expected) {
t.Errorf("expected %+v, got %+v", expected, results)
}
i := 0
expectedErr := errors.New("FATAL ERROR")
results = [][]string{}
cbWithError := func(s string, ss string, k string, v string, bv bool) error {
results = append(results, []string{s, ss, k, v, strconv.FormatBool(bv)})
i += 1
if i == 3 {
return expectedErr
}
return nil
}
err = ReadWithCallback(bytes.NewReader([]byte(text)), cbWithError)
if err != expectedErr {
t.Errorf("expected error: %+v", err)
}
if !reflect.DeepEqual(results, expected[:3]) {
t.Errorf("expected %+v, got %+v", expected, results[:3])
}
}
func TestReadWithCallback_WithError(t *testing.T) {
results := [][]string{}
cb := func(s string, ss string, k string, v string, bv bool) error {
results = append(results, []string{s, ss, k, v, strconv.FormatBool(bv)})
return nil
}
text := `
[sect1]
key1=value1
[sect1 "subsect1"]
key2=value2
key3=value3
key4
key5=
[sect1 "subsect2"]
[sect2]
`
expected := [][]string{
[]string{"sect1", "", "", "", "true"},
[]string{"sect1", "", "key1", "value1", "false"},
[]string{"sect1", "subsect1", "", "", "true"},
[]string{"sect1", "subsect1", "key2", "value2", "false"},
[]string{"sect1", "subsect1", "key3", "value3", "false"},
[]string{"sect1", "subsect1", "key4", "", "true"},
[]string{"sect1", "subsect1", "key5", "", "false"},
[]string{"sect1", "subsect2", "", "", "true"},
[]string{"sect2", "", "", "", "true"},
}
err := ReadWithCallback(bytes.NewReader([]byte(text)), cb)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(results, expected) {
t.Errorf("expected %+v, got %+v", expected, results)
}
}