decode_test.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. package toml
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "reflect"
  7. "testing"
  8. "time"
  9. )
  10. func init() {
  11. log.SetFlags(0)
  12. }
  13. var testSimple = `
  14. age = 250
  15. andrew = "gallant"
  16. kait = "brady"
  17. now = 1987-07-05T05:45:00Z
  18. yesOrNo = true
  19. pi = 3.14
  20. colors = [
  21. ["red", "green", "blue"],
  22. ["cyan", "magenta", "yellow", "black"],
  23. ]
  24. [Annoying.Cats]
  25. plato = "smelly"
  26. cauchy = "stupido"
  27. `
  28. type kitties struct {
  29. Plato string
  30. Cauchy string
  31. }
  32. type simple struct {
  33. Age int
  34. Colors [][]string
  35. Pi float64
  36. YesOrNo bool
  37. Now time.Time
  38. Andrew string
  39. Kait string
  40. Annoying map[string]kitties
  41. }
  42. func TestDecode(t *testing.T) {
  43. var val simple
  44. md, err := Decode(testSimple, &val)
  45. if err != nil {
  46. t.Fatal(err)
  47. }
  48. testf("Is 'Annoying.Cats.plato' defined? %v\n",
  49. md.IsDefined("Annoying", "Cats", "plato"))
  50. testf("Is 'Cats.Stinky' defined? %v\n", md.IsDefined("Cats", "Stinky"))
  51. testf("Type of 'colors'? %s\n\n", md.Type("colors"))
  52. testf("%v\n", val)
  53. }
  54. func TestDecodeEmbedded(t *testing.T) {
  55. type Dog struct{ Name string }
  56. tests := map[string]struct {
  57. input string
  58. decodeInto interface{}
  59. wantDecoded interface{}
  60. }{
  61. "embedded struct": {
  62. input: `Name = "milton"`,
  63. decodeInto: &struct{ Dog }{},
  64. wantDecoded: &struct{ Dog }{Dog{"milton"}},
  65. },
  66. "embedded non-nil pointer to struct": {
  67. input: `Name = "milton"`,
  68. decodeInto: &struct{ *Dog }{},
  69. wantDecoded: &struct{ *Dog }{&Dog{"milton"}},
  70. },
  71. "embedded nil pointer to struct": {
  72. input: ``,
  73. decodeInto: &struct{ *Dog }{},
  74. wantDecoded: &struct{ *Dog }{nil},
  75. },
  76. }
  77. for label, test := range tests {
  78. _, err := Decode(test.input, test.decodeInto)
  79. if err != nil {
  80. t.Fatal(err)
  81. }
  82. want, got := jsonstr(test.wantDecoded), jsonstr(test.decodeInto)
  83. if want != got {
  84. t.Errorf("%s: want decoded == %+v, got %+v", label, want, got)
  85. }
  86. }
  87. }
  88. // jsonstr allows comparison of deeply nested structs with pointer members.
  89. func jsonstr(o interface{}) string {
  90. s, err := json.MarshalIndent(o, "", " ")
  91. if err != nil {
  92. panic(err.Error())
  93. }
  94. return string(s)
  95. }
  96. var tomlTableArrays = `
  97. [[albums]]
  98. name = "Born to Run"
  99. [[albums.songs]]
  100. name = "Jungleland"
  101. [[albums.songs]]
  102. name = "Meeting Across the River"
  103. [[albums]]
  104. name = "Born in the USA"
  105. [[albums.songs]]
  106. name = "Glory Days"
  107. [[albums.songs]]
  108. name = "Dancing in the Dark"
  109. `
  110. type Music struct {
  111. Albums []Album
  112. }
  113. type Album struct {
  114. Name string
  115. Songs []Song
  116. }
  117. type Song struct {
  118. Name string
  119. }
  120. func TestTableArrays(t *testing.T) {
  121. expected := Music{[]Album{
  122. {"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}},
  123. {"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}},
  124. }}
  125. var got Music
  126. if _, err := Decode(tomlTableArrays, &got); err != nil {
  127. t.Fatal(err)
  128. }
  129. if !reflect.DeepEqual(expected, got) {
  130. t.Fatalf("\n%#v\n!=\n%#v\n", expected, got)
  131. }
  132. }
  133. // Case insensitive matching tests.
  134. // A bit more comprehensive than needed given the current implementation,
  135. // but implementations change.
  136. // Probably still missing demonstrations of some ugly corner cases regarding
  137. // case insensitive matching and multiple fields.
  138. var caseToml = `
  139. tOpString = "string"
  140. tOpInt = 1
  141. tOpFloat = 1.1
  142. tOpBool = true
  143. tOpdate = 2006-01-02T15:04:05Z
  144. tOparray = [ "array" ]
  145. Match = "i should be in Match only"
  146. MatcH = "i should be in MatcH only"
  147. once = "just once"
  148. [nEst.eD]
  149. nEstedString = "another string"
  150. `
  151. type Insensitive struct {
  152. TopString string
  153. TopInt int
  154. TopFloat float64
  155. TopBool bool
  156. TopDate time.Time
  157. TopArray []string
  158. Match string
  159. MatcH string
  160. Once string
  161. OncE string
  162. Nest InsensitiveNest
  163. }
  164. type InsensitiveNest struct {
  165. Ed InsensitiveEd
  166. }
  167. type InsensitiveEd struct {
  168. NestedString string
  169. }
  170. func TestCase(t *testing.T) {
  171. tme, err := time.Parse(time.RFC3339, time.RFC3339[:len(time.RFC3339)-5])
  172. if err != nil {
  173. panic(err)
  174. }
  175. expected := Insensitive{
  176. TopString: "string",
  177. TopInt: 1,
  178. TopFloat: 1.1,
  179. TopBool: true,
  180. TopDate: tme,
  181. TopArray: []string{"array"},
  182. MatcH: "i should be in MatcH only",
  183. Match: "i should be in Match only",
  184. Once: "just once",
  185. OncE: "",
  186. Nest: InsensitiveNest{
  187. Ed: InsensitiveEd{NestedString: "another string"},
  188. },
  189. }
  190. var got Insensitive
  191. _, err = Decode(caseToml, &got)
  192. if err != nil {
  193. t.Fatal(err)
  194. }
  195. if !reflect.DeepEqual(expected, got) {
  196. t.Fatalf("\n%#v\n!=\n%#v\n", expected, got)
  197. }
  198. }
  199. func TestPointers(t *testing.T) {
  200. type Object struct {
  201. Type string
  202. Description string
  203. }
  204. type Dict struct {
  205. NamedObject map[string]*Object
  206. BaseObject *Object
  207. Strptr *string
  208. Strptrs []*string
  209. }
  210. s1, s2, s3 := "blah", "abc", "def"
  211. expected := &Dict{
  212. Strptr: &s1,
  213. Strptrs: []*string{&s2, &s3},
  214. NamedObject: map[string]*Object{
  215. "foo": {"FOO", "fooooo!!!"},
  216. "bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"},
  217. },
  218. BaseObject: &Object{"BASE", "da base"},
  219. }
  220. ex1 := `
  221. Strptr = "blah"
  222. Strptrs = ["abc", "def"]
  223. [NamedObject.foo]
  224. Type = "FOO"
  225. Description = "fooooo!!!"
  226. [NamedObject.bar]
  227. Type = "BAR"
  228. Description = "ba-ba-ba-ba-barrrr!!!"
  229. [BaseObject]
  230. Type = "BASE"
  231. Description = "da base"
  232. `
  233. dict := new(Dict)
  234. _, err := Decode(ex1, dict)
  235. if err != nil {
  236. t.Errorf("Decode error: %v", err)
  237. }
  238. if !reflect.DeepEqual(expected, dict) {
  239. t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict)
  240. }
  241. }
  242. func ExamplePrimitiveDecode() {
  243. var md MetaData
  244. var err error
  245. var tomlBlob = `
  246. ranking = ["Springsteen", "J Geils"]
  247. [bands.Springsteen]
  248. started = 1973
  249. albums = ["Greetings", "WIESS", "Born to Run", "Darkness"]
  250. [bands.J Geils]
  251. started = 1970
  252. albums = ["The J. Geils Band", "Full House", "Blow Your Face Out"]
  253. `
  254. type band struct {
  255. Started int
  256. Albums []string
  257. }
  258. type classics struct {
  259. Ranking []string
  260. Bands map[string]Primitive
  261. }
  262. // Do the initial decode. Reflection is delayed on Primitive values.
  263. var music classics
  264. if md, err = Decode(tomlBlob, &music); err != nil {
  265. log.Fatal(err)
  266. }
  267. // MetaData still includes information on Primitive values.
  268. fmt.Printf("Is `bands.Springsteen` defined? %v\n",
  269. md.IsDefined("bands", "Springsteen"))
  270. // Decode primitive data into Go values.
  271. for _, artist := range music.Ranking {
  272. // A band is a primitive value, so we need to decode it to get a
  273. // real `band` value.
  274. primValue := music.Bands[artist]
  275. var aBand band
  276. if err = PrimitiveDecode(primValue, &aBand); err != nil {
  277. log.Fatal(err)
  278. }
  279. fmt.Printf("%s started in %d.\n", artist, aBand.Started)
  280. }
  281. // Output:
  282. // Is `bands.Springsteen` defined? true
  283. // Springsteen started in 1973.
  284. // J Geils started in 1970.
  285. }
  286. func ExampleDecode() {
  287. var tomlBlob = `
  288. # Some comments.
  289. [alpha]
  290. ip = "10.0.0.1"
  291. [alpha.config]
  292. Ports = [ 8001, 8002 ]
  293. Location = "Toronto"
  294. Created = 1987-07-05T05:45:00Z
  295. [beta]
  296. ip = "10.0.0.2"
  297. [beta.config]
  298. Ports = [ 9001, 9002 ]
  299. Location = "New Jersey"
  300. Created = 1887-01-05T05:55:00Z
  301. `
  302. type serverConfig struct {
  303. Ports []int
  304. Location string
  305. Created time.Time
  306. }
  307. type server struct {
  308. IP string `toml:"ip"`
  309. Config serverConfig `toml:"config"`
  310. }
  311. type servers map[string]server
  312. var config servers
  313. if _, err := Decode(tomlBlob, &config); err != nil {
  314. log.Fatal(err)
  315. }
  316. for _, name := range []string{"alpha", "beta"} {
  317. s := config[name]
  318. fmt.Printf("Server: %s (ip: %s) in %s created on %s\n",
  319. name, s.IP, s.Config.Location,
  320. s.Config.Created.Format("2006-01-02"))
  321. fmt.Printf("Ports: %v\n", s.Config.Ports)
  322. }
  323. // Output:
  324. // Server: alpha (ip: 10.0.0.1) in Toronto created on 1987-07-05
  325. // Ports: [8001 8002]
  326. // Server: beta (ip: 10.0.0.2) in New Jersey created on 1887-01-05
  327. // Ports: [9001 9002]
  328. }
  329. type duration struct {
  330. time.Duration
  331. }
  332. func (d *duration) UnmarshalText(text []byte) error {
  333. var err error
  334. d.Duration, err = time.ParseDuration(string(text))
  335. return err
  336. }
  337. // Example Unmarshaler blah blah.
  338. func ExampleUnmarshaler() {
  339. blob := `
  340. [[song]]
  341. name = "Thunder Road"
  342. duration = "4m49s"
  343. [[song]]
  344. name = "Stairway to Heaven"
  345. duration = "8m03s"
  346. `
  347. type song struct {
  348. Name string
  349. Duration duration
  350. }
  351. type songs struct {
  352. Song []song
  353. }
  354. var favorites songs
  355. if _, err := Decode(blob, &favorites); err != nil {
  356. log.Fatal(err)
  357. }
  358. for _, s := range favorites.Song {
  359. fmt.Printf("%s (%s)\n", s.Name, s.Duration)
  360. }
  361. // Output:
  362. // Thunder Road (4m49s)
  363. // Stairway to Heaven (8m3s)
  364. }