package toml import ( "encoding/json" "fmt" "log" "reflect" "testing" "time" ) func init() { log.SetFlags(0) } var testSimple = ` age = 250 andrew = "gallant" kait = "brady" now = 1987-07-05T05:45:00Z yesOrNo = true pi = 3.14 colors = [ ["red", "green", "blue"], ["cyan", "magenta", "yellow", "black"], ] [Annoying.Cats] plato = "smelly" cauchy = "stupido" ` type kitties struct { Plato string Cauchy string } type simple struct { Age int Colors [][]string Pi float64 YesOrNo bool Now time.Time Andrew string Kait string Annoying map[string]kitties } func TestDecode(t *testing.T) { var val simple md, err := Decode(testSimple, &val) if err != nil { t.Fatal(err) } testf("Is 'Annoying.Cats.plato' defined? %v\n", md.IsDefined("Annoying", "Cats", "plato")) testf("Is 'Cats.Stinky' defined? %v\n", md.IsDefined("Cats", "Stinky")) testf("Type of 'colors'? %s\n\n", md.Type("colors")) testf("%v\n", val) } func TestDecodeEmbedded(t *testing.T) { type Dog struct{ Name string } tests := map[string]struct { input string decodeInto interface{} wantDecoded interface{} }{ "embedded struct": { input: `Name = "milton"`, decodeInto: &struct{ Dog }{}, wantDecoded: &struct{ Dog }{Dog{"milton"}}, }, "embedded non-nil pointer to struct": { input: `Name = "milton"`, decodeInto: &struct{ *Dog }{}, wantDecoded: &struct{ *Dog }{&Dog{"milton"}}, }, "embedded nil pointer to struct": { input: ``, decodeInto: &struct{ *Dog }{}, wantDecoded: &struct{ *Dog }{nil}, }, } for label, test := range tests { _, err := Decode(test.input, test.decodeInto) if err != nil { t.Fatal(err) } want, got := jsonstr(test.wantDecoded), jsonstr(test.decodeInto) if want != got { t.Errorf("%s: want decoded == %+v, got %+v", label, want, got) } } } // jsonstr allows comparison of deeply nested structs with pointer members. func jsonstr(o interface{}) string { s, err := json.MarshalIndent(o, "", " ") if err != nil { panic(err.Error()) } return string(s) } var tomlTableArrays = ` [[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" ` type Music struct { Albums []Album } type Album struct { Name string Songs []Song } type Song struct { Name string } func TestTableArrays(t *testing.T) { expected := Music{[]Album{ {"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}}, {"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}}, }} var got Music if _, err := Decode(tomlTableArrays, &got); err != nil { t.Fatal(err) } if !reflect.DeepEqual(expected, got) { t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) } } // Case insensitive matching tests. // A bit more comprehensive than needed given the current implementation, // but implementations change. // Probably still missing demonstrations of some ugly corner cases regarding // case insensitive matching and multiple fields. var caseToml = ` tOpString = "string" tOpInt = 1 tOpFloat = 1.1 tOpBool = true tOpdate = 2006-01-02T15:04:05Z tOparray = [ "array" ] Match = "i should be in Match only" MatcH = "i should be in MatcH only" once = "just once" [nEst.eD] nEstedString = "another string" ` type Insensitive struct { TopString string TopInt int TopFloat float64 TopBool bool TopDate time.Time TopArray []string Match string MatcH string Once string OncE string Nest InsensitiveNest } type InsensitiveNest struct { Ed InsensitiveEd } type InsensitiveEd struct { NestedString string } func TestCase(t *testing.T) { tme, err := time.Parse(time.RFC3339, time.RFC3339[:len(time.RFC3339)-5]) if err != nil { panic(err) } expected := Insensitive{ TopString: "string", TopInt: 1, TopFloat: 1.1, TopBool: true, TopDate: tme, TopArray: []string{"array"}, MatcH: "i should be in MatcH only", Match: "i should be in Match only", Once: "just once", OncE: "", Nest: InsensitiveNest{ Ed: InsensitiveEd{NestedString: "another string"}, }, } var got Insensitive _, err = Decode(caseToml, &got) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(expected, got) { t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) } } func TestPointers(t *testing.T) { type Object struct { Type string Description string } type Dict struct { NamedObject map[string]*Object BaseObject *Object Strptr *string Strptrs []*string } s1, s2, s3 := "blah", "abc", "def" expected := &Dict{ Strptr: &s1, Strptrs: []*string{&s2, &s3}, NamedObject: map[string]*Object{ "foo": {"FOO", "fooooo!!!"}, "bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"}, }, BaseObject: &Object{"BASE", "da base"}, } ex1 := ` Strptr = "blah" Strptrs = ["abc", "def"] [NamedObject.foo] Type = "FOO" Description = "fooooo!!!" [NamedObject.bar] Type = "BAR" Description = "ba-ba-ba-ba-barrrr!!!" [BaseObject] Type = "BASE" Description = "da base" ` dict := new(Dict) _, err := Decode(ex1, dict) if err != nil { t.Errorf("Decode error: %v", err) } if !reflect.DeepEqual(expected, dict) { t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict) } } func ExamplePrimitiveDecode() { var md MetaData var err error var tomlBlob = ` ranking = ["Springsteen", "J Geils"] [bands.Springsteen] started = 1973 albums = ["Greetings", "WIESS", "Born to Run", "Darkness"] [bands.J Geils] started = 1970 albums = ["The J. Geils Band", "Full House", "Blow Your Face Out"] ` type band struct { Started int Albums []string } type classics struct { Ranking []string Bands map[string]Primitive } // Do the initial decode. Reflection is delayed on Primitive values. var music classics if md, err = Decode(tomlBlob, &music); err != nil { log.Fatal(err) } // MetaData still includes information on Primitive values. fmt.Printf("Is `bands.Springsteen` defined? %v\n", md.IsDefined("bands", "Springsteen")) // Decode primitive data into Go values. for _, artist := range music.Ranking { // A band is a primitive value, so we need to decode it to get a // real `band` value. primValue := music.Bands[artist] var aBand band if err = PrimitiveDecode(primValue, &aBand); err != nil { log.Fatal(err) } fmt.Printf("%s started in %d.\n", artist, aBand.Started) } // Output: // Is `bands.Springsteen` defined? true // Springsteen started in 1973. // J Geils started in 1970. } func ExampleDecode() { var tomlBlob = ` # Some comments. [alpha] ip = "10.0.0.1" [alpha.config] Ports = [ 8001, 8002 ] Location = "Toronto" Created = 1987-07-05T05:45:00Z [beta] ip = "10.0.0.2" [beta.config] Ports = [ 9001, 9002 ] Location = "New Jersey" Created = 1887-01-05T05:55:00Z ` type serverConfig struct { Ports []int Location string Created time.Time } type server struct { IP string `toml:"ip"` Config serverConfig `toml:"config"` } type servers map[string]server var config servers if _, err := Decode(tomlBlob, &config); err != nil { log.Fatal(err) } for _, name := range []string{"alpha", "beta"} { s := config[name] fmt.Printf("Server: %s (ip: %s) in %s created on %s\n", name, s.IP, s.Config.Location, s.Config.Created.Format("2006-01-02")) fmt.Printf("Ports: %v\n", s.Config.Ports) } // // Output: // Server: alpha (ip: 10.0.0.1) in Toronto created on 1987-07-05 // Ports: [8001 8002] // Server: beta (ip: 10.0.0.2) in New Jersey created on 1887-01-05 // Ports: [9001 9002] }