--- /dev/null
+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"
+}