package toml import ( "bytes" "fmt" "log" "net" "testing" "time" ) func TestEncodeRoundTrip(t *testing.T) { type Config struct { Age int Cats []string Pi float64 Perfection []int DOB time.Time Ipaddress net.IP } var inputs = Config{ 13, []string{"one", "two", "three"}, 3.145, []int{11, 2, 3, 4}, time.Now(), net.ParseIP("192.168.59.254"), } var firstBuffer bytes.Buffer e := NewEncoder(&firstBuffer) err := e.Encode(inputs) if err != nil { t.Fatal(err) } var outputs Config if _, err := Decode(firstBuffer.String(), &outputs); err != nil { t.Logf("Could not decode:\n-----\n%s\n-----\n", firstBuffer.String()) t.Fatal(err) } // could test each value individually, but I'm lazy var secondBuffer bytes.Buffer e2 := NewEncoder(&secondBuffer) err = e2.Encode(outputs) if err != nil { t.Fatal(err) } if firstBuffer.String() != secondBuffer.String() { t.Error( firstBuffer.String(), "\n\n is not identical to\n\n", secondBuffer.String()) } } // XXX(burntsushi) // I think these tests probably should be removed. They are good, but they // ought to be obsolete by toml-test. func TestEncode(t *testing.T) { type Embedded struct { Int int `toml:"_int"` } type NonStruct int date := time.Date(2014, 5, 11, 20, 30, 40, 0, time.FixedZone("IST", 3600)) dateStr := "2014-05-11T19:30:40Z" tests := map[string]struct { input interface{} wantOutput string wantError error }{ "bool field": { input: struct { BoolTrue bool BoolFalse bool }{true, false}, wantOutput: "BoolTrue = true\nBoolFalse = false\n", }, "int fields": { input: struct { Int int Int8 int8 Int16 int16 Int32 int32 Int64 int64 }{1, 2, 3, 4, 5}, wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n", }, "uint fields": { input: struct { Uint uint Uint8 uint8 Uint16 uint16 Uint32 uint32 Uint64 uint64 }{1, 2, 3, 4, 5}, wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" + "\nUint64 = 5\n", }, "float fields": { input: struct { Float32 float32 Float64 float64 }{1.5, 2.5}, wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n", }, "string field": { input: struct{ String string }{"foo"}, wantOutput: "String = \"foo\"\n", }, "string field and unexported field": { input: struct { String string unexported int }{"foo", 0}, wantOutput: "String = \"foo\"\n", }, "datetime field in UTC": { input: struct{ Date time.Time }{date}, wantOutput: fmt.Sprintf("Date = %s\n", dateStr), }, "datetime field as primitive": { // Using a map here to fail if isStructOrMap() returns true for // time.Time. input: map[string]interface{}{ "Date": date, "Int": 1, }, wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr), }, "array fields": { input: struct { IntArray0 [0]int IntArray3 [3]int }{[0]int{}, [3]int{1, 2, 3}}, wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n", }, "slice fields": { input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{ nil, []int{}, []int{1, 2, 3}, }, wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n", }, "datetime slices": { input: struct{ DatetimeSlice []time.Time }{ []time.Time{date, date}, }, wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n", dateStr, dateStr), }, "nested arrays and slices": { input: struct { SliceOfArrays [][2]int ArrayOfSlices [2][]int SliceOfArraysOfSlices [][2][]int ArrayOfSlicesOfArrays [2][][2]int SliceOfMixedArrays [][2]interface{} ArrayOfMixedSlices [2][]interface{} }{ [][2]int{{1, 2}, {3, 4}}, [2][]int{{1, 2}, {3, 4}}, [][2][]int{ { {1, 2}, {3, 4}, }, { {5, 6}, {7, 8}, }, }, [2][][2]int{ { {1, 2}, {3, 4}, }, { {5, 6}, {7, 8}, }, }, [][2]interface{}{ {1, 2}, {"a", "b"}, }, [2][]interface{}{ {1, 2}, {"a", "b"}, }, }, wantOutput: `SliceOfArrays = [[1, 2], [3, 4]] ArrayOfSlices = [[1, 2], [3, 4]] SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] SliceOfMixedArrays = [[1, 2], ["a", "b"]] ArrayOfMixedSlices = [[1, 2], ["a", "b"]] `, }, "empty slice": { input: struct{ Empty []interface{} }{[]interface{}{}}, wantOutput: "Empty = []\n", }, "(error) slice with element type mismatch (string and integer)": { input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}}, wantError: errArrayMixedElementTypes, }, "(error) slice with element type mismatch (integer and float)": { input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}}, wantError: errArrayMixedElementTypes, }, "slice with elems of differing Go types, same TOML types": { input: struct { MixedInts []interface{} MixedFloats []interface{} }{ []interface{}{ int(1), int8(2), int16(3), int32(4), int64(5), uint(1), uint8(2), uint16(3), uint32(4), uint64(5), }, []interface{}{float32(1.5), float64(2.5)}, }, wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" + "MixedFloats = [1.5, 2.5]\n", }, "(error) slice w/ element type mismatch (one is nested array)": { input: struct{ Mixed []interface{} }{ []interface{}{1, []interface{}{2}}, }, wantError: errArrayMixedElementTypes, }, "(error) slice with 1 nil element": { input: struct{ NilElement1 []interface{} }{[]interface{}{nil}}, wantError: errArrayNilElement, }, "(error) slice with 1 nil element (and other non-nil elements)": { input: struct{ NilElement []interface{} }{ []interface{}{1, nil}, }, wantError: errArrayNilElement, }, "simple map": { input: map[string]int{"a": 1, "b": 2}, wantOutput: "a = 1\nb = 2\n", }, "map with interface{} value type": { input: map[string]interface{}{"a": 1, "b": "c"}, wantOutput: "a = 1\nb = \"c\"\n", }, "map with interface{} value type, some of which are structs": { input: map[string]interface{}{ "a": struct{ Int int }{2}, "b": 1, }, wantOutput: "b = 1\n\n[a]\n Int = 2\n", }, "nested map": { input: map[string]map[string]int{ "a": {"b": 1}, "c": {"d": 2}, }, wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n", }, "nested struct": { input: struct{ Struct struct{ Int int } }{ struct{ Int int }{1}, }, wantOutput: "[Struct]\n Int = 1\n", }, "nested struct and non-struct field": { input: struct { Struct struct{ Int int } Bool bool }{struct{ Int int }{1}, true}, wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n", }, "2 nested structs": { input: struct{ Struct1, Struct2 struct{ Int int } }{ struct{ Int int }{1}, struct{ Int int }{2}, }, wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n", }, "deeply nested structs": { input: struct { Struct1, Struct2 struct{ Struct3 *struct{ Int int } } }{ struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}}, struct{ Struct3 *struct{ Int int } }{nil}, }, wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" + "\n\n[Struct2]\n", }, "nested struct with nil struct elem": { input: struct { Struct struct{ Inner *struct{ Int int } } }{ struct{ Inner *struct{ Int int } }{nil}, }, wantOutput: "[Struct]\n", }, "nested struct with no fields": { input: struct { Struct struct{ Inner struct{} } }{ struct{ Inner struct{} }{struct{}{}}, }, wantOutput: "[Struct]\n [Struct.Inner]\n", }, "struct with tags": { input: struct { Struct struct { Int int `toml:"_int"` } `toml:"_struct"` Bool bool `toml:"_bool"` }{ struct { Int int `toml:"_int"` }{1}, true, }, wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n", }, "embedded struct": { input: struct{ Embedded }{Embedded{1}}, wantOutput: "_int = 1\n", }, "embedded *struct": { input: struct{ *Embedded }{&Embedded{1}}, wantOutput: "_int = 1\n", }, "nested embedded struct": { input: struct { Struct struct{ Embedded } `toml:"_struct"` }{struct{ Embedded }{Embedded{1}}}, wantOutput: "[_struct]\n _int = 1\n", }, "nested embedded *struct": { input: struct { Struct struct{ *Embedded } `toml:"_struct"` }{struct{ *Embedded }{&Embedded{1}}}, wantOutput: "[_struct]\n _int = 1\n", }, "embedded non-struct": { input: struct{ NonStruct }{5}, wantOutput: "NonStruct = 5\n", }, "array of tables": { input: struct { Structs []*struct{ Int int } `toml:"struct"` }{ []*struct{ Int int }{{1}, {3}}, }, wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n", }, "array of tables order": { input: map[string]interface{}{ "map": map[string]interface{}{ "zero": 5, "arr": []map[string]int{ { "friend": 5, }, }, }, }, wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n", }, "(error) top-level slice": { input: []struct{ Int int }{{1}, {2}, {3}}, wantError: errNoKey, }, "(error) slice of slice": { input: struct { Slices [][]struct{ Int int } }{ [][]struct{ Int int }{{{1}}, {{2}}, {{3}}}, }, wantError: errArrayNoTable, }, "(error) map no string key": { input: map[int]string{1: ""}, wantError: errNonString, }, "(error) empty key name": { input: map[string]int{"": 1}, wantError: errAnything, }, "(error) empty map name": { input: map[string]interface{}{ "": map[string]int{"v": 1}, }, wantError: errAnything, }, } for label, test := range tests { encodeExpected(t, label, test.input, test.wantOutput, test.wantError) } } func TestEncodeNestedTableArrays(t *testing.T) { type song struct { Name string `toml:"name"` } type album struct { Name string `toml:"name"` Songs []song `toml:"songs"` } type springsteen struct { Albums []album `toml:"albums"` } value := springsteen{ []album{ {"Born to Run", []song{{"Jungleland"}, {"Meeting Across the River"}}}, {"Born in the USA", []song{{"Glory Days"}, {"Dancing in the Dark"}}}, }, } expected := `[[albums]] name = "Born to Run" [[albums.songs]] name = "Jungleland" [[albums.songs]] name = "Meeting Across the River" [[albums]] name = "Born in the USA" [[albums.songs]] name = "Glory Days" [[albums.songs]] name = "Dancing in the Dark" ` encodeExpected(t, "nested table arrays", value, expected, nil) } func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) { type Alpha struct { V int } type Beta struct { V int } type Conf struct { V int A Alpha B []Beta } val := Conf{ V: 1, A: Alpha{2}, B: []Beta{{3}}, } expected := "V = 1\n\n[A]\n V = 2\n\n[[B]]\n V = 3\n" encodeExpected(t, "array hash with normal hash order", val, expected, nil) } func TestEncodeWithOmitEmpty(t *testing.T) { type simple struct { Bool bool `toml:"bool,omitempty"` String string `toml:"string,omitempty"` Array [0]byte `toml:"array,omitempty"` Slice []int `toml:"slice,omitempty"` Map map[string]string `toml:"map,omitempty"` } var v simple encodeExpected(t, "fields with omitempty are omitted when empty", v, "", nil) v = simple{ Bool: true, String: " ", Slice: []int{2, 3, 4}, Map: map[string]string{"foo": "bar"}, } expected := `bool = true string = " " slice = [2, 3, 4] [map] foo = "bar" ` encodeExpected(t, "fields with omitempty are not omitted when non-empty", v, expected, nil) } func TestEncodeWithOmitZero(t *testing.T) { type simple struct { Number int `toml:"number,omitzero"` Real float64 `toml:"real,omitzero"` Unsigned uint `toml:"unsigned,omitzero"` } value := simple{0, 0.0, uint(0)} expected := "" encodeExpected(t, "simple with omitzero, all zero", value, expected, nil) value.Number = 10 value.Real = 20 value.Unsigned = 5 expected = `number = 10 real = 20.0 unsigned = 5 ` encodeExpected(t, "simple with omitzero, non-zero", value, expected, nil) } func TestEncodeOmitemptyWithEmptyName(t *testing.T) { type simple struct { S []int `toml:",omitempty"` } v := simple{[]int{1, 2, 3}} expected := "S = [1, 2, 3]\n" encodeExpected(t, "simple with omitempty, no name, non-empty field", v, expected, nil) } func TestEncodeAnonymousStruct(t *testing.T) { type Inner struct{ N int } type Outer0 struct{ Inner } type Outer1 struct { Inner `toml:"inner"` } v0 := Outer0{Inner{3}} expected := "N = 3\n" encodeExpected(t, "embedded anonymous untagged struct", v0, expected, nil) v1 := Outer1{Inner{3}} expected = "[inner]\n N = 3\n" encodeExpected(t, "embedded anonymous tagged struct", v1, expected, nil) } func TestEncodeAnonymousStructPointerField(t *testing.T) { type Inner struct{ N int } type Outer0 struct{ *Inner } type Outer1 struct { *Inner `toml:"inner"` } v0 := Outer0{} expected := "" encodeExpected(t, "nil anonymous untagged struct pointer field", v0, expected, nil) v0 = Outer0{&Inner{3}} expected = "N = 3\n" encodeExpected(t, "non-nil anonymous untagged struct pointer field", v0, expected, nil) v1 := Outer1{} expected = "" encodeExpected(t, "nil anonymous tagged struct pointer field", v1, expected, nil) v1 = Outer1{&Inner{3}} expected = "[inner]\n N = 3\n" encodeExpected(t, "non-nil anonymous tagged struct pointer field", v1, expected, nil) } func TestEncodeIgnoredFields(t *testing.T) { type simple struct { Number int `toml:"-"` } value := simple{} expected := "" encodeExpected(t, "ignored field", value, expected, nil) } func encodeExpected( t *testing.T, label string, val interface{}, wantStr string, wantErr error, ) { var buf bytes.Buffer enc := NewEncoder(&buf) err := enc.Encode(val) if err != wantErr { if wantErr != nil { if wantErr == errAnything && err != nil { return } t.Errorf("%s: want Encode error %v, got %v", label, wantErr, err) } else { t.Errorf("%s: Encode failed: %s", label, err) } } if err != nil { return } if got := buf.String(); wantStr != got { t.Errorf("%s: want\n-----\n%q\n-----\nbut got\n-----\n%q\n-----\n", label, wantStr, got) } } func ExampleEncoder_Encode() { date, _ := time.Parse(time.RFC822, "14 Mar 10 18:00 UTC") var config = map[string]interface{}{ "date": date, "counts": []int{1, 1, 2, 3, 5, 8}, "hash": map[string]string{ "key1": "val1", "key2": "val2", }, } buf := new(bytes.Buffer) if err := NewEncoder(buf).Encode(config); err != nil { log.Fatal(err) } fmt.Println(buf.String()) // Output: // counts = [1, 1, 2, 3, 5, 8] // date = 2010-03-14T18:00:00Z // // [hash] // key1 = "val1" // key2 = "val2" }