ini_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  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
  15. import (
  16. "bytes"
  17. "io/ioutil"
  18. "strings"
  19. "testing"
  20. "time"
  21. . "github.com/smartystreets/goconvey/convey"
  22. )
  23. func Test_Version(t *testing.T) {
  24. Convey("Get version", t, func() {
  25. So(Version(), ShouldEqual, _VERSION)
  26. })
  27. }
  28. const _CONF_DATA = `
  29. ; Package name
  30. NAME = ini
  31. ; Package version
  32. VERSION = v1
  33. ; Package import path
  34. IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
  35. # Information about package author
  36. # Bio can be written in multiple lines.
  37. [author]
  38. NAME = Unknwon ; Succeeding comment
  39. E-MAIL = fake@localhost
  40. GITHUB = https://github.com/%(NAME)s
  41. BIO = """Gopher.
  42. Coding addict.
  43. Good man.
  44. """ # Succeeding comment
  45. [package]
  46. CLONE_URL = https://%(IMPORT_PATH)s
  47. [package.sub]
  48. UNUSED_KEY = should be deleted
  49. [features]
  50. -: Support read/write comments of keys and sections
  51. -: Support auto-increment of key names
  52. -: Support load multiple files to overwrite key values
  53. [types]
  54. STRING = str
  55. BOOL = true
  56. BOOL_FALSE = false
  57. FLOAT64 = 1.25
  58. INT = 10
  59. TIME = 2015-01-01T20:17:05Z
  60. DURATION = 2h45m
  61. UINT = 3
  62. [array]
  63. STRINGS = en, zh, de
  64. FLOAT64S = 1.1, 2.2, 3.3
  65. INTS = 1, 2, 3
  66. UINTS = 1, 2, 3
  67. TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
  68. [note]
  69. empty_lines = next line is empty\
  70. ; Comment before the section
  71. [comments] ; This is a comment for the section too
  72. ; Comment before key
  73. key = "value"
  74. key2 = "value2" ; This is a comment for key2
  75. key3 = "one", "two", "three"
  76. [advance]
  77. value with quotes = "some value"
  78. value quote2 again = 'some value'
  79. true = 2+3=5
  80. "1+1=2" = true
  81. """6+1=7""" = true
  82. """` + "`" + `5+5` + "`" + `""" = 10
  83. ` + "`" + `"6+6"` + "`" + ` = 12
  84. ` + "`" + `7-2=4` + "`" + ` = false
  85. ADDRESS = ` + "`" + `404 road,
  86. NotFound, State, 50000` + "`" + `
  87. two_lines = how about \
  88. continuation lines?
  89. lots_of_lines = 1 \
  90. 2 \
  91. 3 \
  92. 4 \
  93. `
  94. func Test_Load(t *testing.T) {
  95. Convey("Load from data sources", t, func() {
  96. Convey("Load with empty data", func() {
  97. So(Empty(), ShouldNotBeNil)
  98. })
  99. Convey("Load with multiple data sources", func() {
  100. cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini", ioutil.NopCloser(bytes.NewReader([]byte(_CONF_DATA))))
  101. So(err, ShouldBeNil)
  102. So(cfg, ShouldNotBeNil)
  103. f, err := Load([]byte(_CONF_DATA), "testdata/404.ini")
  104. So(err, ShouldNotBeNil)
  105. So(f, ShouldBeNil)
  106. })
  107. Convey("Load with io.ReadCloser", func() {
  108. cfg, err := Load(ioutil.NopCloser(bytes.NewReader([]byte(_CONF_DATA))))
  109. So(err, ShouldBeNil)
  110. So(cfg, ShouldNotBeNil)
  111. So(cfg.Section("").Key("NAME").String(), ShouldEqual, "ini")
  112. })
  113. })
  114. Convey("Bad load process", t, func() {
  115. Convey("Load from invalid data sources", func() {
  116. _, err := Load(_CONF_DATA)
  117. So(err, ShouldNotBeNil)
  118. f, err := Load("testdata/404.ini")
  119. So(err, ShouldNotBeNil)
  120. So(f, ShouldBeNil)
  121. _, err = Load(1)
  122. So(err, ShouldNotBeNil)
  123. _, err = Load([]byte(""), 1)
  124. So(err, ShouldNotBeNil)
  125. })
  126. Convey("Load with bad section name", func() {
  127. _, err := Load([]byte("[]"))
  128. So(err, ShouldNotBeNil)
  129. _, err = Load([]byte("["))
  130. So(err, ShouldNotBeNil)
  131. })
  132. Convey("Load with bad keys", func() {
  133. _, err := Load([]byte(`"""name`))
  134. So(err, ShouldNotBeNil)
  135. _, err = Load([]byte(`"""name"""`))
  136. So(err, ShouldNotBeNil)
  137. _, err = Load([]byte(`""=1`))
  138. So(err, ShouldNotBeNil)
  139. _, err = Load([]byte(`=`))
  140. So(err, ShouldNotBeNil)
  141. _, err = Load([]byte(`name`))
  142. So(err, ShouldNotBeNil)
  143. })
  144. Convey("Load with bad values", func() {
  145. _, err := Load([]byte(`name="""Unknwon`))
  146. So(err, ShouldNotBeNil)
  147. })
  148. })
  149. Convey("Get section and key insensitively", t, func() {
  150. cfg, err := InsensitiveLoad([]byte(_CONF_DATA), "testdata/conf.ini")
  151. So(err, ShouldBeNil)
  152. So(cfg, ShouldNotBeNil)
  153. sec, err := cfg.GetSection("Author")
  154. So(err, ShouldBeNil)
  155. So(sec, ShouldNotBeNil)
  156. key, err := sec.GetKey("E-mail")
  157. So(err, ShouldBeNil)
  158. So(key, ShouldNotBeNil)
  159. })
  160. Convey("Load with ignoring continuation lines", t, func() {
  161. cfg, err := LoadSources(LoadOptions{IgnoreContinuation: true}, []byte(`key1=a\b\
  162. key2=c\d\`))
  163. So(err, ShouldBeNil)
  164. So(cfg, ShouldNotBeNil)
  165. So(cfg.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
  166. So(cfg.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
  167. })
  168. Convey("Load with boolean type keys", t, func() {
  169. cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, []byte(`key1=hello
  170. key2
  171. #key3
  172. key4
  173. key5`))
  174. So(err, ShouldBeNil)
  175. So(cfg, ShouldNotBeNil)
  176. So(strings.Join(cfg.Section("").KeyStrings(), ","), ShouldEqual, "key1,key2,key4,key5")
  177. So(cfg.Section("").Key("key2").MustBool(false), ShouldBeTrue)
  178. var buf bytes.Buffer
  179. cfg.WriteTo(&buf)
  180. // there is always a trailing \n at the end of the section
  181. So(buf.String(), ShouldEqual, `key1 = hello
  182. key2
  183. #key3
  184. key4
  185. key5
  186. `)
  187. })
  188. }
  189. func Test_LooseLoad(t *testing.T) {
  190. Convey("Loose load from data sources", t, func() {
  191. Convey("Loose load mixed with nonexistent file", func() {
  192. cfg, err := LooseLoad("testdata/404.ini")
  193. So(err, ShouldBeNil)
  194. So(cfg, ShouldNotBeNil)
  195. var fake struct {
  196. Name string `ini:"name"`
  197. }
  198. So(cfg.MapTo(&fake), ShouldBeNil)
  199. cfg, err = LooseLoad([]byte("name=Unknwon"), "testdata/404.ini")
  200. So(err, ShouldBeNil)
  201. So(cfg.Section("").Key("name").String(), ShouldEqual, "Unknwon")
  202. So(cfg.MapTo(&fake), ShouldBeNil)
  203. So(fake.Name, ShouldEqual, "Unknwon")
  204. })
  205. })
  206. }
  207. func Test_File_Append(t *testing.T) {
  208. Convey("Append data sources", t, func() {
  209. cfg, err := Load([]byte(""))
  210. So(err, ShouldBeNil)
  211. So(cfg, ShouldNotBeNil)
  212. So(cfg.Append([]byte(""), []byte("")), ShouldBeNil)
  213. Convey("Append bad data sources", func() {
  214. So(cfg.Append(1), ShouldNotBeNil)
  215. So(cfg.Append([]byte(""), 1), ShouldNotBeNil)
  216. })
  217. })
  218. }
  219. func Test_File_WriteTo(t *testing.T) {
  220. Convey("Write to somewhere", t, func() {
  221. var buf bytes.Buffer
  222. cfg := Empty()
  223. cfg.WriteTo(&buf)
  224. })
  225. }
  226. func Test_File_SaveTo_WriteTo(t *testing.T) {
  227. Convey("Save file", t, func() {
  228. cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
  229. So(err, ShouldBeNil)
  230. So(cfg, ShouldNotBeNil)
  231. cfg.Section("").Key("NAME").Comment = "Package name"
  232. cfg.Section("author").Comment = `Information about package author
  233. # Bio can be written in multiple lines.`
  234. cfg.Section("advanced").Key("val w/ pound").SetValue("my#password")
  235. cfg.Section("advanced").Key("longest key has a colon : yes/no").SetValue("yes")
  236. So(cfg.SaveTo("testdata/conf_out.ini"), ShouldBeNil)
  237. cfg.Section("author").Key("NAME").Comment = "This is author name"
  238. So(cfg.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil)
  239. var buf bytes.Buffer
  240. _, err = cfg.WriteToIndent(&buf, "\t")
  241. So(err, ShouldBeNil)
  242. So(buf.String(), ShouldEqual, `; Package name
  243. NAME = ini
  244. ; Package version
  245. VERSION = v1
  246. ; Package import path
  247. IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
  248. ; Information about package author
  249. # Bio can be written in multiple lines.
  250. [author]
  251. ; This is author name
  252. NAME = Unknwon
  253. E-MAIL = u@gogs.io
  254. GITHUB = https://github.com/%(NAME)s
  255. # Succeeding comment
  256. BIO = """Gopher.
  257. Coding addict.
  258. Good man.
  259. """
  260. [package]
  261. CLONE_URL = https://%(IMPORT_PATH)s
  262. [package.sub]
  263. UNUSED_KEY = should be deleted
  264. [features]
  265. - = Support read/write comments of keys and sections
  266. - = Support auto-increment of key names
  267. - = Support load multiple files to overwrite key values
  268. [types]
  269. STRING = str
  270. BOOL = true
  271. BOOL_FALSE = false
  272. FLOAT64 = 1.25
  273. INT = 10
  274. TIME = 2015-01-01T20:17:05Z
  275. DURATION = 2h45m
  276. UINT = 3
  277. [array]
  278. STRINGS = en, zh, de
  279. FLOAT64S = 1.1, 2.2, 3.3
  280. INTS = 1, 2, 3
  281. UINTS = 1, 2, 3
  282. TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
  283. [note]
  284. empty_lines = next line is empty
  285. ; Comment before the section
  286. ; This is a comment for the section too
  287. [comments]
  288. ; Comment before key
  289. key = value
  290. ; This is a comment for key2
  291. key2 = value2
  292. key3 = "one", "two", "three"
  293. [advance]
  294. value with quotes = some value
  295. value quote2 again = some value
  296. true = 2+3=5
  297. `+"`"+`1+1=2`+"`"+` = true
  298. `+"`"+`6+1=7`+"`"+` = true
  299. """`+"`"+`5+5`+"`"+`""" = 10
  300. `+"`"+`"6+6"`+"`"+` = 12
  301. `+"`"+`7-2=4`+"`"+` = false
  302. ADDRESS = """404 road,
  303. NotFound, State, 50000"""
  304. two_lines = how about continuation lines?
  305. lots_of_lines = 1 2 3 4
  306. [advanced]
  307. val w/ pound = `+"`"+`my#password`+"`"+`
  308. `+"`"+`longest key has a colon : yes/no`+"`"+` = yes
  309. `)
  310. })
  311. }
  312. func Test_File_WriteTo_SectionRaw(t *testing.T) {
  313. Convey("Write a INI with a raw section", t, func() {
  314. var buf bytes.Buffer
  315. cfg, err := LoadSources(
  316. LoadOptions{
  317. UnparseableSections: []string{"CORE_LESSON", "COMMENTS"},
  318. },
  319. "testdata/aicc.ini")
  320. So(err, ShouldBeNil)
  321. So(cfg, ShouldNotBeNil)
  322. cfg.WriteToIndent(&buf, "\t")
  323. So(buf.String(), ShouldEqual, `[Core]
  324. Lesson_Location = 87
  325. Lesson_Status = C
  326. Score = 3
  327. Time = 00:02:30
  328. [CORE_LESSON]
  329. my lesson state data – 1111111111111111111000000000000000001110000
  330. 111111111111111111100000000000111000000000 – end my lesson state data
  331. [COMMENTS]
  332. <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
  333. `)
  334. })
  335. }
  336. // Helpers for slice tests.
  337. func float64sEqual(values []float64, expected ...float64) {
  338. So(values, ShouldHaveLength, len(expected))
  339. for i, v := range expected {
  340. So(values[i], ShouldEqual, v)
  341. }
  342. }
  343. func intsEqual(values []int, expected ...int) {
  344. So(values, ShouldHaveLength, len(expected))
  345. for i, v := range expected {
  346. So(values[i], ShouldEqual, v)
  347. }
  348. }
  349. func int64sEqual(values []int64, expected ...int64) {
  350. So(values, ShouldHaveLength, len(expected))
  351. for i, v := range expected {
  352. So(values[i], ShouldEqual, v)
  353. }
  354. }
  355. func uintsEqual(values []uint, expected ...uint) {
  356. So(values, ShouldHaveLength, len(expected))
  357. for i, v := range expected {
  358. So(values[i], ShouldEqual, v)
  359. }
  360. }
  361. func uint64sEqual(values []uint64, expected ...uint64) {
  362. So(values, ShouldHaveLength, len(expected))
  363. for i, v := range expected {
  364. So(values[i], ShouldEqual, v)
  365. }
  366. }
  367. func timesEqual(values []time.Time, expected ...time.Time) {
  368. So(values, ShouldHaveLength, len(expected))
  369. for i, v := range expected {
  370. So(values[i].String(), ShouldEqual, v.String())
  371. }
  372. }