struct_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  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. Flags []bool
  33. Note string
  34. Unused int `ini:"-"`
  35. }
  36. type TestEmbeded struct {
  37. GPA float64
  38. }
  39. type testStruct struct {
  40. Name string `ini:"NAME"`
  41. Age int
  42. Male 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. BoolPtr *bool
  56. BoolPtrNil *bool
  57. FloatPtr *float64
  58. FloatPtrNil *float64
  59. IntPtr *int
  60. IntPtrNil *int
  61. UintPtr *uint
  62. UintPtrNil *uint
  63. StringPtr *string
  64. StringPtrNil *string
  65. TimePtr *time.Time
  66. TimePtrNil *time.Time
  67. DurationPtr *time.Duration
  68. DurationPtrNil *time.Duration
  69. }
  70. const _CONF_DATA_STRUCT = `
  71. NAME = Unknwon
  72. Age = 21
  73. Male = true
  74. Money = 1.25
  75. Born = 1993-10-07T20:17:05Z
  76. Duration = 2h45m
  77. Unsigned = 3
  78. omitthis = true
  79. Shadows = 1, 2
  80. Shadows = 3, 4
  81. BoolPtr = false
  82. FloatPtr = 0
  83. IntPtr = 0
  84. UintPtr = 0
  85. StringPtr = ""
  86. TimePtr = 0001-01-01T00:00:00Z
  87. DurationPtr = 0s
  88. [Others]
  89. Cities = HangZhou|Boston
  90. Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
  91. Years = 1993,1994
  92. Numbers = 10010,10086
  93. Ages = 18,19
  94. Populations = 12345678,98765432
  95. Coordinates = 192.168,10.11
  96. Flags = true,false
  97. Note = Hello world!
  98. [OthersPtr]
  99. Cities = HangZhou|Boston
  100. Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
  101. Years = 1993,1994
  102. Numbers = 10010,10086
  103. Ages = 18,19
  104. Populations = 12345678,98765432
  105. Coordinates = 192.168,10.11
  106. Flags = true,false
  107. Note = Hello world!
  108. [grade]
  109. GPA = 2.8
  110. [foo.bar]
  111. Here = there
  112. When = then
  113. `
  114. type unsupport struct {
  115. Byte byte
  116. }
  117. type unsupport2 struct {
  118. Others struct {
  119. Cities byte
  120. }
  121. }
  122. type Unsupport3 struct {
  123. Cities byte
  124. }
  125. type unsupport4 struct {
  126. *Unsupport3 `ini:"Others"`
  127. }
  128. type defaultValue struct {
  129. Name string
  130. Age int
  131. Male bool
  132. Optional *bool
  133. Money float64
  134. Born time.Time
  135. Cities []string
  136. }
  137. type fooBar struct {
  138. Here, When string
  139. }
  140. const _INVALID_DATA_CONF_STRUCT = `
  141. Name =
  142. Age = age
  143. Male = 123
  144. Money = money
  145. Born = nil
  146. Cities =
  147. `
  148. func Test_MapToStruct(t *testing.T) {
  149. Convey("Map to struct", t, func() {
  150. Convey("Map file to struct", func() {
  151. ts := new(testStruct)
  152. So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
  153. So(ts.Name, ShouldEqual, "Unknwon")
  154. So(ts.Age, ShouldEqual, 21)
  155. So(ts.Male, ShouldBeTrue)
  156. So(ts.Money, ShouldEqual, 1.25)
  157. So(ts.Unsigned, ShouldEqual, 3)
  158. t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
  159. So(err, ShouldBeNil)
  160. So(ts.Born.String(), ShouldEqual, t.String())
  161. dur, err := time.ParseDuration("2h45m")
  162. So(err, ShouldBeNil)
  163. So(ts.Time.Seconds(), ShouldEqual, dur.Seconds())
  164. So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston")
  165. So(ts.Others.Visits[0].String(), ShouldEqual, t.String())
  166. So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]")
  167. So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]")
  168. So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]")
  169. So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]")
  170. So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]")
  171. So(fmt.Sprint(ts.Others.Flags), ShouldEqual, "[true false]")
  172. So(ts.Others.Note, ShouldEqual, "Hello world!")
  173. So(ts.TestEmbeded.GPA, ShouldEqual, 2.8)
  174. So(strings.Join(ts.OthersPtr.Cities, ","), ShouldEqual, "HangZhou,Boston")
  175. So(ts.OthersPtr.Visits[0].String(), ShouldEqual, t.String())
  176. So(fmt.Sprint(ts.OthersPtr.Years), ShouldEqual, "[1993 1994]")
  177. So(fmt.Sprint(ts.OthersPtr.Numbers), ShouldEqual, "[10010 10086]")
  178. So(fmt.Sprint(ts.OthersPtr.Ages), ShouldEqual, "[18 19]")
  179. So(fmt.Sprint(ts.OthersPtr.Populations), ShouldEqual, "[12345678 98765432]")
  180. So(fmt.Sprint(ts.OthersPtr.Coordinates), ShouldEqual, "[192.168 10.11]")
  181. So(fmt.Sprint(ts.OthersPtr.Flags), ShouldEqual, "[true false]")
  182. So(ts.OthersPtr.Note, ShouldEqual, "Hello world!")
  183. So(ts.NilPtr, ShouldBeNil)
  184. So(*ts.BoolPtr, ShouldEqual, false)
  185. So(ts.BoolPtrNil, ShouldEqual, nil)
  186. So(*ts.FloatPtr, ShouldEqual, 0)
  187. So(ts.FloatPtrNil, ShouldEqual, nil)
  188. So(*ts.IntPtr, ShouldEqual, 0)
  189. So(ts.IntPtrNil, ShouldEqual, nil)
  190. So(*ts.UintPtr, ShouldEqual, 0)
  191. So(ts.UintPtrNil, ShouldEqual, nil)
  192. So(*ts.StringPtr, ShouldEqual, "")
  193. So(ts.StringPtrNil, ShouldEqual, nil)
  194. So(*ts.TimePtr, ShouldNotEqual, nil)
  195. So(ts.TimePtrNil, ShouldEqual, nil)
  196. So(*ts.DurationPtr, ShouldEqual, 0)
  197. So(ts.DurationPtrNil, ShouldEqual, nil)
  198. })
  199. Convey("Map section to struct", func() {
  200. foobar := new(fooBar)
  201. f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
  202. So(err, ShouldBeNil)
  203. So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil)
  204. So(foobar.Here, ShouldEqual, "there")
  205. So(foobar.When, ShouldEqual, "then")
  206. })
  207. Convey("Map to non-pointer struct", func() {
  208. f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
  209. So(err, ShouldBeNil)
  210. So(f, ShouldNotBeNil)
  211. So(f.MapTo(testStruct{}), ShouldNotBeNil)
  212. })
  213. Convey("Map to unsupported type", func() {
  214. f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
  215. So(err, ShouldBeNil)
  216. So(f, ShouldNotBeNil)
  217. f.NameMapper = func(raw string) string {
  218. if raw == "Byte" {
  219. return "NAME"
  220. }
  221. return raw
  222. }
  223. So(f.MapTo(&unsupport{}), ShouldNotBeNil)
  224. So(f.MapTo(&unsupport2{}), ShouldNotBeNil)
  225. So(f.MapTo(&unsupport4{}), ShouldNotBeNil)
  226. })
  227. Convey("Map to omitempty field", func() {
  228. ts := new(testStruct)
  229. So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
  230. So(ts.Omitted, ShouldEqual, true)
  231. })
  232. Convey("Map with shadows", func() {
  233. f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true}, []byte(_CONF_DATA_STRUCT))
  234. So(err, ShouldBeNil)
  235. ts := new(testStruct)
  236. So(f.MapTo(ts), ShouldBeNil)
  237. So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4")
  238. So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]")
  239. })
  240. Convey("Map from invalid data source", func() {
  241. So(ini.MapTo(&testStruct{}, "hi"), ShouldNotBeNil)
  242. })
  243. Convey("Map to wrong types and gain default values", func() {
  244. f, err := ini.Load([]byte(_INVALID_DATA_CONF_STRUCT))
  245. So(err, ShouldBeNil)
  246. t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
  247. So(err, ShouldBeNil)
  248. dv := &defaultValue{"Joe", 10, true, nil, 1.25, t, []string{"HangZhou", "Boston"}}
  249. So(f.MapTo(dv), ShouldBeNil)
  250. So(dv.Name, ShouldEqual, "Joe")
  251. So(dv.Age, ShouldEqual, 10)
  252. So(dv.Male, ShouldBeTrue)
  253. So(dv.Money, ShouldEqual, 1.25)
  254. So(dv.Born.String(), ShouldEqual, t.String())
  255. So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston")
  256. })
  257. })
  258. Convey("Map to struct in strict mode", t, func() {
  259. f, err := ini.Load([]byte(`
  260. name=bruce
  261. age=a30`))
  262. So(err, ShouldBeNil)
  263. type Strict struct {
  264. Name string `ini:"name"`
  265. Age int `ini:"age"`
  266. }
  267. s := new(Strict)
  268. So(f.Section("").StrictMapTo(s), ShouldNotBeNil)
  269. })
  270. Convey("Map slice in strict mode", t, func() {
  271. f, err := ini.Load([]byte(`
  272. names=alice, bruce`))
  273. So(err, ShouldBeNil)
  274. type Strict struct {
  275. Names []string `ini:"names"`
  276. }
  277. s := new(Strict)
  278. So(f.Section("").StrictMapTo(s), ShouldBeNil)
  279. So(fmt.Sprint(s.Names), ShouldEqual, "[alice bruce]")
  280. })
  281. }
  282. func Test_ReflectFromStruct(t *testing.T) {
  283. Convey("Reflect from struct", t, func() {
  284. type Embeded struct {
  285. Dates []time.Time `delim:"|" comment:"Time data"`
  286. Places []string
  287. Years []int
  288. Numbers []int64
  289. Ages []uint
  290. Populations []uint64
  291. Coordinates []float64
  292. Flags []bool
  293. None []int
  294. }
  295. type Author struct {
  296. Name string `ini:"NAME"`
  297. Male bool
  298. Optional *bool
  299. Age int `comment:"Author's age"`
  300. Height uint
  301. GPA float64
  302. Date time.Time
  303. NeverMind string `ini:"-"`
  304. *Embeded `ini:"infos" comment:"Embeded section"`
  305. }
  306. t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
  307. So(err, ShouldBeNil)
  308. a := &Author{"Unknwon", true, nil, 21, 100, 2.8, t, "",
  309. &Embeded{
  310. []time.Time{t, t},
  311. []string{"HangZhou", "Boston"},
  312. []int{1993, 1994},
  313. []int64{10010, 10086},
  314. []uint{18, 19},
  315. []uint64{12345678, 98765432},
  316. []float64{192.168, 10.11},
  317. []bool{true, false},
  318. []int{},
  319. }}
  320. cfg := ini.Empty()
  321. So(ini.ReflectFrom(cfg, a), ShouldBeNil)
  322. var buf bytes.Buffer
  323. _, err = cfg.WriteTo(&buf)
  324. So(err, ShouldBeNil)
  325. So(buf.String(), ShouldEqual, `NAME = Unknwon
  326. Male = true
  327. Optional =
  328. ; Author's age
  329. Age = 21
  330. Height = 100
  331. GPA = 2.8
  332. Date = 1993-10-07T20:17:05Z
  333. ; Embeded section
  334. [infos]
  335. ; Time data
  336. Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z
  337. Places = HangZhou,Boston
  338. Years = 1993,1994
  339. Numbers = 10010,10086
  340. Ages = 18,19
  341. Populations = 12345678,98765432
  342. Coordinates = 192.168,10.11
  343. Flags = true,false
  344. None =
  345. `)
  346. Convey("Reflect from non-point struct", func() {
  347. So(ini.ReflectFrom(cfg, Author{}), ShouldNotBeNil)
  348. })
  349. Convey("Reflect from struct with omitempty", func() {
  350. cfg := ini.Empty()
  351. type SpecialStruct struct {
  352. FirstName string `ini:"first_name"`
  353. LastName string `ini:"last_name"`
  354. JustOmitMe string `ini:"omitempty"`
  355. LastLogin time.Time `ini:"last_login,omitempty"`
  356. LastLogin2 time.Time `ini:",omitempty"`
  357. NotEmpty int `ini:"omitempty"`
  358. }
  359. So(ini.ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil)
  360. var buf bytes.Buffer
  361. _, err = cfg.WriteTo(&buf)
  362. So(buf.String(), ShouldEqual, `first_name = John
  363. last_name = Doe
  364. omitempty = 9
  365. `)
  366. })
  367. })
  368. }
  369. // Inspired by https://github.com/go-ini/ini/issues/196
  370. func TestMapToAndReflectFromStructWithShadows(t *testing.T) {
  371. Convey("Map to struct and then reflect with shadows should generate original config content", t, func() {
  372. type include struct {
  373. Paths []string `ini:"path,omitempty,allowshadow"`
  374. }
  375. cfg, err := ini.LoadSources(ini.LoadOptions{
  376. AllowShadows: true,
  377. }, []byte(`
  378. [include]
  379. path = /tmp/gpm-profiles/test5.profile
  380. path = /tmp/gpm-profiles/test1.profile`))
  381. So(err, ShouldBeNil)
  382. sec := cfg.Section("include")
  383. inc := new(include)
  384. err = sec.MapTo(inc)
  385. So(err, ShouldBeNil)
  386. err = sec.ReflectFrom(inc)
  387. So(err, ShouldBeNil)
  388. var buf bytes.Buffer
  389. _, err = cfg.WriteTo(&buf)
  390. So(err, ShouldBeNil)
  391. So(buf.String(), ShouldEqual, `[include]
  392. path = /tmp/gpm-profiles/test5.profile
  393. path = /tmp/gpm-profiles/test1.profile
  394. `)
  395. })
  396. }
  397. type testMapper struct {
  398. PackageName string
  399. }
  400. func Test_NameGetter(t *testing.T) {
  401. Convey("Test name mappers", t, func() {
  402. So(ini.MapToWithMapper(&testMapper{}, ini.TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil)
  403. cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
  404. So(err, ShouldBeNil)
  405. So(cfg, ShouldNotBeNil)
  406. cfg.NameMapper = ini.AllCapsUnderscore
  407. tg := new(testMapper)
  408. So(cfg.MapTo(tg), ShouldBeNil)
  409. So(tg.PackageName, ShouldEqual, "ini")
  410. })
  411. }
  412. type testDurationStruct struct {
  413. Duration time.Duration `ini:"Duration"`
  414. }
  415. func Test_Duration(t *testing.T) {
  416. Convey("Duration less than 16m50s", t, func() {
  417. ds := new(testDurationStruct)
  418. So(ini.MapTo(ds, []byte("Duration=16m49s")), ShouldBeNil)
  419. dur, err := time.ParseDuration("16m49s")
  420. So(err, ShouldBeNil)
  421. So(ds.Duration.Seconds(), ShouldEqual, dur.Seconds())
  422. })
  423. }