struct_test.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  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. Money float64
  43. Born time.Time
  44. Time time.Duration `ini:"Duration"`
  45. Others testNested
  46. *TestEmbeded `ini:"grade"`
  47. Unused int `ini:"-"`
  48. Unsigned uint
  49. Omitted bool `ini:"omitthis,omitempty"`
  50. Shadows []string `ini:",,allowshadow"`
  51. ShadowInts []int `ini:"Shadows,,allowshadow"`
  52. }
  53. const _CONF_DATA_STRUCT = `
  54. NAME = Unknwon
  55. Age = 21
  56. Male = true
  57. Money = 1.25
  58. Born = 1993-10-07T20:17:05Z
  59. Duration = 2h45m
  60. Unsigned = 3
  61. omitthis = true
  62. Shadows = 1, 2
  63. Shadows = 3, 4
  64. [Others]
  65. Cities = HangZhou|Boston
  66. Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
  67. Years = 1993,1994
  68. Numbers = 10010,10086
  69. Ages = 18,19
  70. Populations = 12345678,98765432
  71. Coordinates = 192.168,10.11
  72. Note = Hello world!
  73. [grade]
  74. GPA = 2.8
  75. [foo.bar]
  76. Here = there
  77. When = then
  78. `
  79. type unsupport struct {
  80. Byte byte
  81. }
  82. type unsupport2 struct {
  83. Others struct {
  84. Cities byte
  85. }
  86. }
  87. type Unsupport3 struct {
  88. Cities byte
  89. }
  90. type unsupport4 struct {
  91. *Unsupport3 `ini:"Others"`
  92. }
  93. type defaultValue struct {
  94. Name string
  95. Age int
  96. Male bool
  97. Money float64
  98. Born time.Time
  99. Cities []string
  100. }
  101. type fooBar struct {
  102. Here, When string
  103. }
  104. const _INVALID_DATA_CONF_STRUCT = `
  105. Name =
  106. Age = age
  107. Male = 123
  108. Money = money
  109. Born = nil
  110. Cities =
  111. `
  112. func Test_MapToStruct(t *testing.T) {
  113. Convey("Map to struct", t, func() {
  114. Convey("Map file to struct", func() {
  115. ts := new(testStruct)
  116. So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
  117. So(ts.Name, ShouldEqual, "Unknwon")
  118. So(ts.Age, ShouldEqual, 21)
  119. So(ts.Male, ShouldBeTrue)
  120. So(ts.Money, ShouldEqual, 1.25)
  121. So(ts.Unsigned, ShouldEqual, 3)
  122. t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
  123. So(err, ShouldBeNil)
  124. So(ts.Born.String(), ShouldEqual, t.String())
  125. dur, err := time.ParseDuration("2h45m")
  126. So(err, ShouldBeNil)
  127. So(ts.Time.Seconds(), ShouldEqual, dur.Seconds())
  128. So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston")
  129. So(ts.Others.Visits[0].String(), ShouldEqual, t.String())
  130. So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]")
  131. So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]")
  132. So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]")
  133. So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]")
  134. So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]")
  135. So(ts.Others.Note, ShouldEqual, "Hello world!")
  136. So(ts.TestEmbeded.GPA, ShouldEqual, 2.8)
  137. })
  138. Convey("Map section to struct", func() {
  139. foobar := new(fooBar)
  140. f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
  141. So(err, ShouldBeNil)
  142. So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil)
  143. So(foobar.Here, ShouldEqual, "there")
  144. So(foobar.When, ShouldEqual, "then")
  145. })
  146. Convey("Map to non-pointer struct", func() {
  147. f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
  148. So(err, ShouldBeNil)
  149. So(f, ShouldNotBeNil)
  150. So(f.MapTo(testStruct{}), ShouldNotBeNil)
  151. })
  152. Convey("Map to unsupported type", func() {
  153. f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
  154. So(err, ShouldBeNil)
  155. So(f, ShouldNotBeNil)
  156. f.NameMapper = func(raw string) string {
  157. if raw == "Byte" {
  158. return "NAME"
  159. }
  160. return raw
  161. }
  162. So(f.MapTo(&unsupport{}), ShouldNotBeNil)
  163. So(f.MapTo(&unsupport2{}), ShouldNotBeNil)
  164. So(f.MapTo(&unsupport4{}), ShouldNotBeNil)
  165. })
  166. Convey("Map to omitempty field", func() {
  167. ts := new(testStruct)
  168. So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
  169. So(ts.Omitted, ShouldEqual, true)
  170. })
  171. Convey("Map with shadows", func() {
  172. f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true}, []byte(_CONF_DATA_STRUCT))
  173. So(err, ShouldBeNil)
  174. ts := new(testStruct)
  175. So(f.MapTo(ts), ShouldBeNil)
  176. So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4")
  177. So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]")
  178. })
  179. Convey("Map from invalid data source", func() {
  180. So(ini.MapTo(&testStruct{}, "hi"), ShouldNotBeNil)
  181. })
  182. Convey("Map to wrong types and gain default values", func() {
  183. f, err := ini.Load([]byte(_INVALID_DATA_CONF_STRUCT))
  184. So(err, ShouldBeNil)
  185. t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
  186. So(err, ShouldBeNil)
  187. dv := &defaultValue{"Joe", 10, true, 1.25, t, []string{"HangZhou", "Boston"}}
  188. So(f.MapTo(dv), ShouldBeNil)
  189. So(dv.Name, ShouldEqual, "Joe")
  190. So(dv.Age, ShouldEqual, 10)
  191. So(dv.Male, ShouldBeTrue)
  192. So(dv.Money, ShouldEqual, 1.25)
  193. So(dv.Born.String(), ShouldEqual, t.String())
  194. So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston")
  195. })
  196. })
  197. Convey("Map to struct in strict mode", t, func() {
  198. f, err := ini.Load([]byte(`
  199. name=bruce
  200. age=a30`))
  201. So(err, ShouldBeNil)
  202. type Strict struct {
  203. Name string `ini:"name"`
  204. Age int `ini:"age"`
  205. }
  206. s := new(Strict)
  207. So(f.Section("").StrictMapTo(s), ShouldNotBeNil)
  208. })
  209. Convey("Map slice in strict mode", t, func() {
  210. f, err := ini.Load([]byte(`
  211. names=alice, bruce`))
  212. So(err, ShouldBeNil)
  213. type Strict struct {
  214. Names []string `ini:"names"`
  215. }
  216. s := new(Strict)
  217. So(f.Section("").StrictMapTo(s), ShouldBeNil)
  218. So(fmt.Sprint(s.Names), ShouldEqual, "[alice bruce]")
  219. })
  220. }
  221. func Test_ReflectFromStruct(t *testing.T) {
  222. Convey("Reflect from struct", t, func() {
  223. type Embeded struct {
  224. Dates []time.Time `delim:"|" comment:"Time data"`
  225. Places []string
  226. Years []int
  227. Numbers []int64
  228. Ages []uint
  229. Populations []uint64
  230. Coordinates []float64
  231. None []int
  232. }
  233. type Author struct {
  234. Name string `ini:"NAME"`
  235. Male bool
  236. Age int `comment:"Author's age"`
  237. Height uint
  238. GPA float64
  239. Date time.Time
  240. NeverMind string `ini:"-"`
  241. *Embeded `ini:"infos" comment:"Embeded section"`
  242. }
  243. t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
  244. So(err, ShouldBeNil)
  245. a := &Author{"Unknwon", true, 21, 100, 2.8, t, "",
  246. &Embeded{
  247. []time.Time{t, t},
  248. []string{"HangZhou", "Boston"},
  249. []int{1993, 1994},
  250. []int64{10010, 10086},
  251. []uint{18, 19},
  252. []uint64{12345678, 98765432},
  253. []float64{192.168, 10.11},
  254. []int{},
  255. }}
  256. cfg := ini.Empty()
  257. So(ini.ReflectFrom(cfg, a), ShouldBeNil)
  258. var buf bytes.Buffer
  259. _, err = cfg.WriteTo(&buf)
  260. So(err, ShouldBeNil)
  261. So(buf.String(), ShouldEqual, `NAME = Unknwon
  262. Male = true
  263. ; Author's age
  264. Age = 21
  265. Height = 100
  266. GPA = 2.8
  267. Date = 1993-10-07T20:17:05Z
  268. ; Embeded section
  269. [infos]
  270. ; Time data
  271. Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z
  272. Places = HangZhou,Boston
  273. Years = 1993,1994
  274. Numbers = 10010,10086
  275. Ages = 18,19
  276. Populations = 12345678,98765432
  277. Coordinates = 192.168,10.11
  278. None =
  279. `)
  280. Convey("Reflect from non-point struct", func() {
  281. So(ini.ReflectFrom(cfg, Author{}), ShouldNotBeNil)
  282. })
  283. Convey("Reflect from struct with omitempty", func() {
  284. cfg := ini.Empty()
  285. type SpecialStruct struct {
  286. FirstName string `ini:"first_name"`
  287. LastName string `ini:"last_name"`
  288. JustOmitMe string `ini:"omitempty"`
  289. LastLogin time.Time `ini:"last_login,omitempty"`
  290. LastLogin2 time.Time `ini:",omitempty"`
  291. NotEmpty int `ini:"omitempty"`
  292. }
  293. So(ini.ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil)
  294. var buf bytes.Buffer
  295. _, err = cfg.WriteTo(&buf)
  296. So(buf.String(), ShouldEqual, `first_name = John
  297. last_name = Doe
  298. omitempty = 9
  299. `)
  300. })
  301. })
  302. }
  303. type testMapper struct {
  304. PackageName string
  305. }
  306. func Test_NameGetter(t *testing.T) {
  307. Convey("Test name mappers", t, func() {
  308. So(ini.MapToWithMapper(&testMapper{}, ini.TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil)
  309. cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
  310. So(err, ShouldBeNil)
  311. So(cfg, ShouldNotBeNil)
  312. cfg.NameMapper = ini.AllCapsUnderscore
  313. tg := new(testMapper)
  314. So(cfg.MapTo(tg), ShouldBeNil)
  315. So(tg.PackageName, ShouldEqual, "ini")
  316. })
  317. }
  318. type testDurationStruct struct {
  319. Duration time.Duration `ini:"Duration"`
  320. }
  321. func Test_Duration(t *testing.T) {
  322. Convey("Duration less than 16m50s", t, func() {
  323. ds := new(testDurationStruct)
  324. So(ini.MapTo(ds, []byte("Duration=16m49s")), ShouldBeNil)
  325. dur, err := time.ParseDuration("16m49s")
  326. So(err, ShouldBeNil)
  327. So(ds.Duration.Seconds(), ShouldEqual, dur.Seconds())
  328. })
  329. }