ini_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  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. "fmt"
  17. "strings"
  18. "testing"
  19. "time"
  20. . "github.com/smartystreets/goconvey/convey"
  21. )
  22. func Test_Version(t *testing.T) {
  23. Convey("Get version", t, func() {
  24. So(Version(), ShouldEqual, _VERSION)
  25. })
  26. }
  27. const _CONF_DATA = `
  28. ; Package name
  29. NAME = ini
  30. ; Package version
  31. VERSION = v1
  32. ; Package import path
  33. IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
  34. # Information about package author
  35. # Bio can be written in multiple lines.
  36. [author]
  37. NAME = Unknwon # Succeeding comment
  38. E-MAIL = fake@localhost
  39. GITHUB = https://github.com/%(NAME)s
  40. BIO = """Gopher.
  41. Coding addict.
  42. Good man.
  43. """ # Succeeding comment
  44. [package]
  45. CLONE_URL = https://%(IMPORT_PATH)s
  46. [package.sub]
  47. UNUSED_KEY = should be deleted
  48. [features]
  49. -: Support read/write comments of keys and sections
  50. -: Support auto-increment of key names
  51. -: Support load multiple files to overwrite key values
  52. [types]
  53. STRING = str
  54. BOOL = true
  55. BOOL_FALSE = false
  56. FLOAT64 = 1.25
  57. INT = 10
  58. TIME = 2015-01-01T20:17:05Z
  59. DURATION = 2h45m
  60. UINT = 3
  61. [array]
  62. STRINGS = en, zh, de
  63. FLOAT64S = 1.1, 2.2, 3.3
  64. INTS = 1, 2, 3
  65. UINTS = 1, 2, 3
  66. TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
  67. [note]
  68. empty_lines = next line is empty\
  69. [advance]
  70. value with quotes = "some value"
  71. value quote2 again = 'some value'
  72. true = """"2+3=5""""
  73. "1+1=2" = true
  74. """6+1=7""" = true
  75. """` + "`" + `5+5` + "`" + `""" = 10
  76. """"6+6"""" = 12
  77. ` + "`" + `7-2=4` + "`" + ` = false
  78. ADDRESS = ` + "`" + `404 road,
  79. NotFound, State, 50000` + "`" + `
  80. two_lines = how about \
  81. continuation lines?
  82. lots_of_lines = 1 \
  83. 2 \
  84. 3 \
  85. 4 \
  86. `
  87. func Test_Load(t *testing.T) {
  88. Convey("Load from data sources", t, func() {
  89. Convey("Load with empty data", func() {
  90. So(Empty(), ShouldNotBeNil)
  91. })
  92. Convey("Load with multiple data sources", func() {
  93. cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
  94. So(err, ShouldBeNil)
  95. So(cfg, ShouldNotBeNil)
  96. })
  97. })
  98. Convey("Bad load process", t, func() {
  99. Convey("Load from invalid data sources", func() {
  100. _, err := Load(_CONF_DATA)
  101. So(err, ShouldNotBeNil)
  102. _, err = Load("testdata/404.ini")
  103. So(err, ShouldNotBeNil)
  104. _, err = Load(1)
  105. So(err, ShouldNotBeNil)
  106. _, err = Load([]byte(""), 1)
  107. So(err, ShouldNotBeNil)
  108. })
  109. Convey("Load with empty section name", func() {
  110. _, err := Load([]byte("[]"))
  111. So(err, ShouldNotBeNil)
  112. })
  113. Convey("Load with bad keys", func() {
  114. _, err := Load([]byte(`"""name`))
  115. So(err, ShouldNotBeNil)
  116. _, err = Load([]byte(`"""name"""`))
  117. So(err, ShouldNotBeNil)
  118. _, err = Load([]byte(`""=1`))
  119. So(err, ShouldNotBeNil)
  120. _, err = Load([]byte(`=`))
  121. So(err, ShouldNotBeNil)
  122. _, err = Load([]byte(`name`))
  123. So(err, ShouldNotBeNil)
  124. })
  125. Convey("Load with bad values", func() {
  126. _, err := Load([]byte(`name="""Unknwon`))
  127. So(err, ShouldNotBeNil)
  128. _, err = Load([]byte(`key = "value`))
  129. So(err, ShouldNotBeNil)
  130. })
  131. })
  132. }
  133. func Test_Values(t *testing.T) {
  134. Convey("Test getting and setting values", t, func() {
  135. cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
  136. So(err, ShouldBeNil)
  137. So(cfg, ShouldNotBeNil)
  138. Convey("Get values in default section", func() {
  139. sec := cfg.Section("")
  140. So(sec, ShouldNotBeNil)
  141. So(sec.Key("NAME").Value(), ShouldEqual, "ini")
  142. So(sec.Key("NAME").String(), ShouldEqual, "ini")
  143. So(sec.Key("NAME").Validate(func(in string) string {
  144. return in
  145. }), ShouldEqual, "ini")
  146. So(sec.Key("NAME").Comment, ShouldEqual, "; Package name")
  147. So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
  148. })
  149. Convey("Get values in non-default section", func() {
  150. sec := cfg.Section("author")
  151. So(sec, ShouldNotBeNil)
  152. So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
  153. So(sec.Key("GITHUB").String(), ShouldEqual, "https://github.com/Unknwon")
  154. sec = cfg.Section("package")
  155. So(sec, ShouldNotBeNil)
  156. So(sec.Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
  157. })
  158. Convey("Get auto-increment key names", func() {
  159. keys := cfg.Section("features").Keys()
  160. for i, k := range keys {
  161. So(k.Name(), ShouldEqual, fmt.Sprintf("#%d", i+1))
  162. }
  163. })
  164. Convey("Get overwrite value", func() {
  165. So(cfg.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
  166. })
  167. Convey("Get sections", func() {
  168. sections := cfg.Sections()
  169. for i, name := range []string{DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "advance"} {
  170. So(sections[i].Name(), ShouldEqual, name)
  171. }
  172. })
  173. Convey("Get parent section value", func() {
  174. So(cfg.Section("package.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
  175. })
  176. Convey("Get multiple line value", func() {
  177. So(cfg.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n")
  178. })
  179. Convey("Get values with type", func() {
  180. sec := cfg.Section("types")
  181. v1, err := sec.Key("BOOL").Bool()
  182. So(err, ShouldBeNil)
  183. So(v1, ShouldBeTrue)
  184. v1, err = sec.Key("BOOL_FALSE").Bool()
  185. So(err, ShouldBeNil)
  186. So(v1, ShouldBeFalse)
  187. v2, err := sec.Key("FLOAT64").Float64()
  188. So(err, ShouldBeNil)
  189. So(v2, ShouldEqual, 1.25)
  190. v3, err := sec.Key("INT").Int()
  191. So(err, ShouldBeNil)
  192. So(v3, ShouldEqual, 10)
  193. v4, err := sec.Key("INT").Int64()
  194. So(err, ShouldBeNil)
  195. So(v4, ShouldEqual, 10)
  196. v5, err := sec.Key("UINT").Uint()
  197. So(err, ShouldBeNil)
  198. So(v5, ShouldEqual, 3)
  199. v6, err := sec.Key("UINT").Uint64()
  200. So(err, ShouldBeNil)
  201. So(v6, ShouldEqual, 3)
  202. t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
  203. So(err, ShouldBeNil)
  204. v7, err := sec.Key("TIME").Time()
  205. So(err, ShouldBeNil)
  206. So(v7.String(), ShouldEqual, t.String())
  207. Convey("Must get values with type", func() {
  208. So(sec.Key("STRING").MustString("404"), ShouldEqual, "str")
  209. So(sec.Key("BOOL").MustBool(), ShouldBeTrue)
  210. So(sec.Key("FLOAT64").MustFloat64(), ShouldEqual, 1.25)
  211. So(sec.Key("INT").MustInt(), ShouldEqual, 10)
  212. So(sec.Key("INT").MustInt64(), ShouldEqual, 10)
  213. So(sec.Key("UINT").MustUint(), ShouldEqual, 3)
  214. So(sec.Key("UINT").MustUint64(), ShouldEqual, 3)
  215. So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String())
  216. dur, err := time.ParseDuration("2h45m")
  217. So(err, ShouldBeNil)
  218. So(sec.Key("DURATION").MustDuration().Seconds(), ShouldEqual, dur.Seconds())
  219. Convey("Must get values with default value", func() {
  220. So(sec.Key("STRING_404").MustString("404"), ShouldEqual, "404")
  221. So(sec.Key("BOOL_404").MustBool(true), ShouldBeTrue)
  222. So(sec.Key("FLOAT64_404").MustFloat64(2.5), ShouldEqual, 2.5)
  223. So(sec.Key("INT_404").MustInt(15), ShouldEqual, 15)
  224. So(sec.Key("INT_404").MustInt64(15), ShouldEqual, 15)
  225. So(sec.Key("UINT_404").MustUint(6), ShouldEqual, 6)
  226. So(sec.Key("UINT_404").MustUint64(6), ShouldEqual, 6)
  227. t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
  228. So(err, ShouldBeNil)
  229. So(sec.Key("TIME_404").MustTime(t).String(), ShouldEqual, t.String())
  230. So(sec.Key("DURATION_404").MustDuration(dur).Seconds(), ShouldEqual, dur.Seconds())
  231. })
  232. })
  233. })
  234. Convey("Get value with candidates", func() {
  235. sec := cfg.Section("types")
  236. So(sec.Key("STRING").In("", []string{"str", "arr", "types"}), ShouldEqual, "str")
  237. So(sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
  238. So(sec.Key("INT").InInt(0, []int{10, 20, 30}), ShouldEqual, 10)
  239. So(sec.Key("INT").InInt64(0, []int64{10, 20, 30}), ShouldEqual, 10)
  240. So(sec.Key("UINT").InUint(0, []uint{3, 6, 9}), ShouldEqual, 3)
  241. So(sec.Key("UINT").InUint64(0, []uint64{3, 6, 9}), ShouldEqual, 3)
  242. zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
  243. So(err, ShouldBeNil)
  244. t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
  245. So(err, ShouldBeNil)
  246. So(sec.Key("TIME").InTime(zt, []time.Time{t, time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
  247. Convey("Get value with candidates and default value", func() {
  248. So(sec.Key("STRING_404").In("str", []string{"str", "arr", "types"}), ShouldEqual, "str")
  249. So(sec.Key("FLOAT64_404").InFloat64(1.25, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
  250. So(sec.Key("INT_404").InInt(10, []int{10, 20, 30}), ShouldEqual, 10)
  251. So(sec.Key("INT64_404").InInt64(10, []int64{10, 20, 30}), ShouldEqual, 10)
  252. So(sec.Key("UINT_404").InUint(3, []uint{3, 6, 9}), ShouldEqual, 3)
  253. So(sec.Key("UINT_404").InUint64(3, []uint64{3, 6, 9}), ShouldEqual, 3)
  254. So(sec.Key("TIME_404").InTime(t, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
  255. })
  256. })
  257. Convey("Get values in range", func() {
  258. sec := cfg.Section("types")
  259. So(sec.Key("FLOAT64").RangeFloat64(0, 1, 2), ShouldEqual, 1.25)
  260. So(sec.Key("INT").RangeInt(0, 10, 20), ShouldEqual, 10)
  261. So(sec.Key("INT").RangeInt64(0, 10, 20), ShouldEqual, 10)
  262. minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
  263. So(err, ShouldBeNil)
  264. midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z")
  265. So(err, ShouldBeNil)
  266. maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z")
  267. So(err, ShouldBeNil)
  268. t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
  269. So(err, ShouldBeNil)
  270. So(sec.Key("TIME").RangeTime(t, minT, maxT).String(), ShouldEqual, t.String())
  271. Convey("Get value in range with default value", func() {
  272. So(sec.Key("FLOAT64").RangeFloat64(5, 0, 1), ShouldEqual, 5)
  273. So(sec.Key("INT").RangeInt(7, 0, 5), ShouldEqual, 7)
  274. So(sec.Key("INT").RangeInt64(7, 0, 5), ShouldEqual, 7)
  275. So(sec.Key("TIME").RangeTime(t, minT, midT).String(), ShouldEqual, t.String())
  276. })
  277. })
  278. Convey("Get values into slice", func() {
  279. sec := cfg.Section("array")
  280. So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de")
  281. So(len(sec.Key("STRINGS_404").Strings(",")), ShouldEqual, 0)
  282. vals1 := sec.Key("FLOAT64S").Float64s(",")
  283. for i, v := range []float64{1.1, 2.2, 3.3} {
  284. So(vals1[i], ShouldEqual, v)
  285. }
  286. vals2 := sec.Key("INTS").Ints(",")
  287. for i, v := range []int{1, 2, 3} {
  288. So(vals2[i], ShouldEqual, v)
  289. }
  290. vals3 := sec.Key("INTS").Int64s(",")
  291. for i, v := range []int64{1, 2, 3} {
  292. So(vals3[i], ShouldEqual, v)
  293. }
  294. vals4 := sec.Key("UINTS").Uints(",")
  295. for i, v := range []uint{1, 2, 3} {
  296. So(vals4[i], ShouldEqual, v)
  297. }
  298. vals5 := sec.Key("UINTS").Uint64s(",")
  299. for i, v := range []uint64{1, 2, 3} {
  300. So(vals5[i], ShouldEqual, v)
  301. }
  302. t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
  303. So(err, ShouldBeNil)
  304. vals6 := sec.Key("TIMES").Times(",")
  305. for i, v := range []time.Time{t, t, t} {
  306. So(vals6[i].String(), ShouldEqual, v.String())
  307. }
  308. })
  309. Convey("Get key hash", func() {
  310. cfg.Section("").KeysHash()
  311. })
  312. Convey("Set key value", func() {
  313. k := cfg.Section("author").Key("NAME")
  314. k.SetValue("无闻")
  315. So(k.String(), ShouldEqual, "无闻")
  316. })
  317. Convey("Get key strings", func() {
  318. So(strings.Join(cfg.Section("types").KeyStrings(), ","), ShouldEqual, "STRING,BOOL,BOOL_FALSE,FLOAT64,INT,TIME,DURATION,UINT")
  319. })
  320. Convey("Delete a key", func() {
  321. cfg.Section("package.sub").DeleteKey("UNUSED_KEY")
  322. _, err := cfg.Section("package.sub").GetKey("UNUSED_KEY")
  323. So(err, ShouldNotBeNil)
  324. })
  325. Convey("Get section strings", func() {
  326. So(strings.Join(cfg.SectionStrings(), ","), ShouldEqual, "DEFAULT,author,package,package.sub,features,types,array,note,advance")
  327. })
  328. Convey("Delete a section", func() {
  329. cfg.DeleteSection("")
  330. So(cfg.SectionStrings()[0], ShouldNotEqual, DEFAULT_SECTION)
  331. })
  332. Convey("Create new sections", func() {
  333. cfg.NewSections("test", "test2")
  334. _, err := cfg.GetSection("test")
  335. So(err, ShouldBeNil)
  336. _, err = cfg.GetSection("test2")
  337. So(err, ShouldBeNil)
  338. })
  339. })
  340. Convey("Test getting and setting bad values", t, func() {
  341. cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
  342. So(err, ShouldBeNil)
  343. So(cfg, ShouldNotBeNil)
  344. Convey("Create new key with empty name", func() {
  345. k, err := cfg.Section("").NewKey("", "")
  346. So(err, ShouldNotBeNil)
  347. So(k, ShouldBeNil)
  348. })
  349. Convey("Create new section with empty name", func() {
  350. s, err := cfg.NewSection("")
  351. So(err, ShouldNotBeNil)
  352. So(s, ShouldBeNil)
  353. })
  354. Convey("Create new sections with empty name", func() {
  355. So(cfg.NewSections(""), ShouldNotBeNil)
  356. })
  357. Convey("Get section that not exists", func() {
  358. s, err := cfg.GetSection("404")
  359. So(err, ShouldNotBeNil)
  360. So(s, ShouldBeNil)
  361. s = cfg.Section("404")
  362. So(s, ShouldNotBeNil)
  363. })
  364. })
  365. }
  366. func Test_File_Append(t *testing.T) {
  367. Convey("Append data sources", t, func() {
  368. cfg, err := Load([]byte(""))
  369. So(err, ShouldBeNil)
  370. So(cfg, ShouldNotBeNil)
  371. So(cfg.Append([]byte(""), []byte("")), ShouldBeNil)
  372. Convey("Append bad data sources", func() {
  373. So(cfg.Append(1), ShouldNotBeNil)
  374. So(cfg.Append([]byte(""), 1), ShouldNotBeNil)
  375. })
  376. })
  377. }
  378. func Test_File_SaveTo(t *testing.T) {
  379. Convey("Save file", t, func() {
  380. cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
  381. So(err, ShouldBeNil)
  382. So(cfg, ShouldNotBeNil)
  383. cfg.Section("").Key("NAME").Comment = "Package name"
  384. cfg.Section("author").Comment = `Information about package author
  385. # Bio can be written in multiple lines.`
  386. cfg.Section("advanced").Key("val w/ pound").SetValue("my#password")
  387. So(cfg.SaveTo("testdata/conf_out.ini"), ShouldBeNil)
  388. cfg.Section("author").Key("NAME").Comment = "This is author name"
  389. So(cfg.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil)
  390. })
  391. }
  392. func Benchmark_Key_Value(b *testing.B) {
  393. c, _ := Load([]byte(_CONF_DATA))
  394. for i := 0; i < b.N; i++ {
  395. c.Section("").Key("NAME").Value()
  396. }
  397. }
  398. func Benchmark_Key_String(b *testing.B) {
  399. c, _ := Load([]byte(_CONF_DATA))
  400. for i := 0; i < b.N; i++ {
  401. c.Section("").Key("NAME").String()
  402. }
  403. }
  404. func Benchmark_Key_Value_NonBlock(b *testing.B) {
  405. c, _ := Load([]byte(_CONF_DATA))
  406. c.BlockMode = false
  407. for i := 0; i < b.N; i++ {
  408. c.Section("").Key("NAME").Value()
  409. }
  410. }
  411. func Benchmark_Key_String_NonBlock(b *testing.B) {
  412. c, _ := Load([]byte(_CONF_DATA))
  413. c.BlockMode = false
  414. for i := 0; i < b.N; i++ {
  415. c.Section("").Key("NAME").String()
  416. }
  417. }
  418. func Benchmark_Key_SetValue(b *testing.B) {
  419. c, _ := Load([]byte(_CONF_DATA))
  420. for i := 0; i < b.N; i++ {
  421. c.Section("").Key("NAME").SetValue("10")
  422. }
  423. }