struct_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. // Copyright 2014 Unknwon
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. // License for the specific language governing permissions and limitations
  13. // under the License.
  14. package ini_test
  15. import (
  16. "bytes"
  17. "fmt"
  18. "strings"
  19. "testing"
  20. "time"
  21. . "github.com/smartystreets/goconvey/convey"
  22. "gopkg.in/ini.v1"
  23. )
  24. type testNested struct {
  25. Cities []string `delim:"|"`
  26. Visits []time.Time
  27. Years []int
  28. Numbers []int64
  29. Ages []uint
  30. Populations []uint64
  31. Coordinates []float64
  32. Note string
  33. Unused int `ini:"-"`
  34. }
  35. type TestEmbeded struct {
  36. GPA float64
  37. }
  38. type testStruct struct {
  39. Name string `ini:"NAME"`
  40. Age int
  41. Male bool
  42. Optional *bool
  43. Money float64
  44. Born time.Time
  45. Time time.Duration `ini:"Duration"`
  46. Others testNested
  47. OthersPtr *testNested
  48. NilPtr *testNested
  49. *TestEmbeded `ini:"grade"`
  50. Unused int `ini:"-"`
  51. Unsigned uint
  52. Omitted bool `ini:"omitthis,omitempty"`
  53. Shadows []string `ini:",,allowshadow"`
  54. ShadowInts []int `ini:"Shadows,,allowshadow"`
  55. }
  56. const _CONF_DATA_STRUCT = `
  57. NAME = Unknwon
  58. Age = 21
  59. Male = true
  60. Optional = true
  61. Money = 1.25
  62. Born = 1993-10-07T20:17:05Z
  63. Duration = 2h45m
  64. Unsigned = 3
  65. omitthis = true
  66. Shadows = 1, 2
  67. Shadows = 3, 4
  68. [Others]
  69. Cities = HangZhou|Boston
  70. Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
  71. Years = 1993,1994
  72. Numbers = 10010,10086
  73. Ages = 18,19
  74. Populations = 12345678,98765432
  75. Coordinates = 192.168,10.11
  76. Note = Hello world!
  77. [OthersPtr]
  78. Cities = HangZhou|Boston
  79. Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
  80. Years = 1993,1994
  81. Numbers = 10010,10086
  82. Ages = 18,19
  83. Populations = 12345678,98765432
  84. Coordinates = 192.168,10.11
  85. Note = Hello world!
  86. [grade]
  87. GPA = 2.8
  88. [foo.bar]
  89. Here = there
  90. When = then
  91. `
  92. type unsupport struct {
  93. Byte byte
  94. }
  95. type unsupport2 struct {
  96. Others struct {
  97. Cities byte
  98. }
  99. }
  100. type Unsupport3 struct {
  101. Cities byte
  102. }
  103. type unsupport4 struct {
  104. *Unsupport3 `ini:"Others"`
  105. }
  106. type defaultValue struct {
  107. Name string
  108. Age int
  109. Male bool
  110. Optional *bool
  111. Money float64
  112. Born time.Time
  113. Cities []string
  114. }
  115. type fooBar struct {
  116. Here, When string
  117. }
  118. const _INVALID_DATA_CONF_STRUCT = `
  119. Name =
  120. Age = age
  121. Male = 123
  122. Money = money
  123. Born = nil
  124. Cities =
  125. `
  126. func Test_MapToStruct(t *testing.T) {
  127. Convey("Map to struct", t, func() {
  128. Convey("Map file to struct", func() {
  129. ts := new(testStruct)
  130. So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
  131. So(ts.Name, ShouldEqual, "Unknwon")
  132. So(ts.Age, ShouldEqual, 21)
  133. So(ts.Male, ShouldBeTrue)
  134. So(*ts.Optional, ShouldBeTrue)
  135. So(ts.Money, ShouldEqual, 1.25)
  136. So(ts.Unsigned, ShouldEqual, 3)
  137. t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
  138. So(err, ShouldBeNil)
  139. So(ts.Born.String(), ShouldEqual, t.String())
  140. dur, err := time.ParseDuration("2h45m")
  141. So(err, ShouldBeNil)
  142. So(ts.Time.Seconds(), ShouldEqual, dur.Seconds())
  143. So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston")
  144. So(ts.Others.Visits[0].String(), ShouldEqual, t.String())
  145. So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]")
  146. So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]")
  147. So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]")
  148. So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]")
  149. So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]")
  150. So(ts.Others.Note, ShouldEqual, "Hello world!")
  151. So(ts.TestEmbeded.GPA, ShouldEqual, 2.8)
  152. So(strings.Join(ts.OthersPtr.Cities, ","), ShouldEqual, "HangZhou,Boston")
  153. So(ts.OthersPtr.Visits[0].String(), ShouldEqual, t.String())
  154. So(fmt.Sprint(ts.OthersPtr.Years), ShouldEqual, "[1993 1994]")
  155. So(fmt.Sprint(ts.OthersPtr.Numbers), ShouldEqual, "[10010 10086]")
  156. So(fmt.Sprint(ts.OthersPtr.Ages), ShouldEqual, "[18 19]")
  157. So(fmt.Sprint(ts.OthersPtr.Populations), ShouldEqual, "[12345678 98765432]")
  158. So(fmt.Sprint(ts.OthersPtr.Coordinates), ShouldEqual, "[192.168 10.11]")
  159. So(ts.OthersPtr.Note, ShouldEqual, "Hello world!")
  160. So(ts.NilPtr, ShouldBeNil)
  161. })
  162. Convey("Map section to struct", func() {
  163. foobar := new(fooBar)
  164. f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
  165. So(err, ShouldBeNil)
  166. So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil)
  167. So(foobar.Here, ShouldEqual, "there")
  168. So(foobar.When, ShouldEqual, "then")
  169. })
  170. Convey("Map to non-pointer struct", func() {
  171. f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
  172. So(err, ShouldBeNil)
  173. So(f, ShouldNotBeNil)
  174. So(f.MapTo(testStruct{}), ShouldNotBeNil)
  175. })
  176. Convey("Map to unsupported type", func() {
  177. f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
  178. So(err, ShouldBeNil)
  179. So(f, ShouldNotBeNil)
  180. f.NameMapper = func(raw string) string {
  181. if raw == "Byte" {
  182. return "NAME"
  183. }
  184. return raw
  185. }
  186. So(f.MapTo(&unsupport{}), ShouldNotBeNil)
  187. So(f.MapTo(&unsupport2{}), ShouldNotBeNil)
  188. So(f.MapTo(&unsupport4{}), ShouldNotBeNil)
  189. })
  190. Convey("Map to omitempty field", func() {
  191. ts := new(testStruct)
  192. So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
  193. So(ts.Omitted, ShouldEqual, true)
  194. })
  195. Convey("Map with shadows", func() {
  196. f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true}, []byte(_CONF_DATA_STRUCT))
  197. So(err, ShouldBeNil)
  198. ts := new(testStruct)
  199. So(f.MapTo(ts), ShouldBeNil)
  200. So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4")
  201. So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]")
  202. })
  203. Convey("Map from invalid data source", func() {
  204. So(ini.MapTo(&testStruct{}, "hi"), ShouldNotBeNil)
  205. })
  206. Convey("Map to wrong types and gain default values", func() {
  207. f, err := ini.Load([]byte(_INVALID_DATA_CONF_STRUCT))
  208. So(err, ShouldBeNil)
  209. t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
  210. So(err, ShouldBeNil)
  211. dv := &defaultValue{"Joe", 10, true, nil, 1.25, t, []string{"HangZhou", "Boston"}}
  212. So(f.MapTo(dv), ShouldBeNil)
  213. So(dv.Name, ShouldEqual, "Joe")
  214. So(dv.Age, ShouldEqual, 10)
  215. So(dv.Male, ShouldBeTrue)
  216. So(dv.Money, ShouldEqual, 1.25)
  217. So(dv.Born.String(), ShouldEqual, t.String())
  218. So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston")
  219. })
  220. })
  221. Convey("Map to struct in strict mode", t, func() {
  222. f, err := ini.Load([]byte(`
  223. name=bruce
  224. age=a30`))
  225. So(err, ShouldBeNil)
  226. type Strict struct {
  227. Name string `ini:"name"`
  228. Age int `ini:"age"`
  229. }
  230. s := new(Strict)
  231. So(f.Section("").StrictMapTo(s), ShouldNotBeNil)
  232. })
  233. Convey("Map slice in strict mode", t, func() {
  234. f, err := ini.Load([]byte(`
  235. names=alice, bruce`))
  236. So(err, ShouldBeNil)
  237. type Strict struct {
  238. Names []string `ini:"names"`
  239. }
  240. s := new(Strict)
  241. So(f.Section("").StrictMapTo(s), ShouldBeNil)
  242. So(fmt.Sprint(s.Names), ShouldEqual, "[alice bruce]")
  243. })
  244. }
  245. func Test_ReflectFromStruct(t *testing.T) {
  246. Convey("Reflect from struct", t, func() {
  247. type Embeded struct {
  248. Dates []time.Time `delim:"|" comment:"Time data"`
  249. Places []string
  250. Years []int
  251. Numbers []int64
  252. Ages []uint
  253. Populations []uint64
  254. Coordinates []float64
  255. None []int
  256. }
  257. type Author struct {
  258. Name string `ini:"NAME"`
  259. Male bool
  260. Optional *bool
  261. Age int `comment:"Author's age"`
  262. Height uint
  263. GPA float64
  264. Date time.Time
  265. NeverMind string `ini:"-"`
  266. *Embeded `ini:"infos" comment:"Embeded section"`
  267. }
  268. t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
  269. So(err, ShouldBeNil)
  270. a := &Author{"Unknwon", true, nil, 21, 100, 2.8, t, "",
  271. &Embeded{
  272. []time.Time{t, t},
  273. []string{"HangZhou", "Boston"},
  274. []int{1993, 1994},
  275. []int64{10010, 10086},
  276. []uint{18, 19},
  277. []uint64{12345678, 98765432},
  278. []float64{192.168, 10.11},
  279. []int{},
  280. }}
  281. cfg := ini.Empty()
  282. So(ini.ReflectFrom(cfg, a), ShouldBeNil)
  283. var buf bytes.Buffer
  284. _, err = cfg.WriteTo(&buf)
  285. So(err, ShouldBeNil)
  286. So(buf.String(), ShouldEqual, `NAME = Unknwon
  287. Male = true
  288. Optional =
  289. ; Author's age
  290. Age = 21
  291. Height = 100
  292. GPA = 2.8
  293. Date = 1993-10-07T20:17:05Z
  294. ; Embeded section
  295. [infos]
  296. ; Time data
  297. Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z
  298. Places = HangZhou,Boston
  299. Years = 1993,1994
  300. Numbers = 10010,10086
  301. Ages = 18,19
  302. Populations = 12345678,98765432
  303. Coordinates = 192.168,10.11
  304. None =
  305. `)
  306. Convey("Reflect from non-point struct", func() {
  307. So(ini.ReflectFrom(cfg, Author{}), ShouldNotBeNil)
  308. })
  309. Convey("Reflect from struct with omitempty", func() {
  310. cfg := ini.Empty()
  311. type SpecialStruct struct {
  312. FirstName string `ini:"first_name"`
  313. LastName string `ini:"last_name"`
  314. JustOmitMe string `ini:"omitempty"`
  315. LastLogin time.Time `ini:"last_login,omitempty"`
  316. LastLogin2 time.Time `ini:",omitempty"`
  317. NotEmpty int `ini:"omitempty"`
  318. }
  319. So(ini.ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil)
  320. var buf bytes.Buffer
  321. _, err = cfg.WriteTo(&buf)
  322. So(buf.String(), ShouldEqual, `first_name = John
  323. last_name = Doe
  324. omitempty = 9
  325. `)
  326. })
  327. })
  328. }
  329. // Inspired by https://github.com/go-ini/ini/issues/196
  330. func TestMapToAndReflectFromStructWithShadows(t *testing.T) {
  331. Convey("Map to struct and then reflect with shadows should generate original config content", t, func() {
  332. type include struct {
  333. Paths []string `ini:"path,omitempty,allowshadow"`
  334. }
  335. cfg, err := ini.LoadSources(ini.LoadOptions{
  336. AllowShadows: true,
  337. }, []byte(`
  338. [include]
  339. path = /tmp/gpm-profiles/test5.profile
  340. path = /tmp/gpm-profiles/test1.profile`))
  341. So(err, ShouldBeNil)
  342. sec := cfg.Section("include")
  343. inc := new(include)
  344. err = sec.MapTo(inc)
  345. So(err, ShouldBeNil)
  346. err = sec.ReflectFrom(inc)
  347. So(err, ShouldBeNil)
  348. var buf bytes.Buffer
  349. _, err = cfg.WriteTo(&buf)
  350. So(err, ShouldBeNil)
  351. So(buf.String(), ShouldEqual, `[include]
  352. path = /tmp/gpm-profiles/test5.profile
  353. path = /tmp/gpm-profiles/test1.profile
  354. `)
  355. })
  356. }
  357. type testMapper struct {
  358. PackageName string
  359. }
  360. func Test_NameGetter(t *testing.T) {
  361. Convey("Test name mappers", t, func() {
  362. So(ini.MapToWithMapper(&testMapper{}, ini.TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil)
  363. cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
  364. So(err, ShouldBeNil)
  365. So(cfg, ShouldNotBeNil)
  366. cfg.NameMapper = ini.AllCapsUnderscore
  367. tg := new(testMapper)
  368. So(cfg.MapTo(tg), ShouldBeNil)
  369. So(tg.PackageName, ShouldEqual, "ini")
  370. })
  371. }
  372. type testDurationStruct struct {
  373. Duration time.Duration `ini:"Duration"`
  374. }
  375. func Test_Duration(t *testing.T) {
  376. Convey("Duration less than 16m50s", t, func() {
  377. ds := new(testDurationStruct)
  378. So(ini.MapTo(ds, []byte("Duration=16m49s")), ShouldBeNil)
  379. dur, err := time.ParseDuration("16m49s")
  380. So(err, ShouldBeNil)
  381. So(ds.Duration.Seconds(), ShouldEqual, dur.Seconds())
  382. })
  383. }