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
15package ini_test
16
17import (
18	"bytes"
19	"fmt"
20	"strings"
21	"testing"
22	"time"
23
24	. "github.com/smartystreets/goconvey/convey"
25	"gopkg.in/ini.v1"
26)
27
28func TestKey_AddShadow(t *testing.T) {
29	Convey("Add shadow to a key", t, func() {
30		f, err := ini.ShadowLoad([]byte(`
31[notes]
32-: note1`))
33		So(err, ShouldBeNil)
34		So(f, ShouldNotBeNil)
35
36		k, err := f.Section("").NewKey("NAME", "ini")
37		So(err, ShouldBeNil)
38		So(k, ShouldNotBeNil)
39
40		So(k.AddShadow("ini.v1"), ShouldBeNil)
41		So(k.ValueWithShadows(), ShouldResemble, []string{"ini", "ini.v1"})
42
43		Convey("Add shadow to boolean key", func() {
44			k, err := f.Section("").NewBooleanKey("published")
45			So(err, ShouldBeNil)
46			So(k, ShouldNotBeNil)
47			So(k.AddShadow("beta"), ShouldNotBeNil)
48		})
49
50		Convey("Add shadow to auto-increment key", func() {
51			So(f.Section("notes").Key("#1").AddShadow("beta"), ShouldNotBeNil)
52		})
53	})
54
55	Convey("Shadow is not allowed", t, func() {
56		f := ini.Empty()
57		So(f, ShouldNotBeNil)
58
59		k, err := f.Section("").NewKey("NAME", "ini")
60		So(err, ShouldBeNil)
61		So(k, ShouldNotBeNil)
62
63		So(k.AddShadow("ini.v1"), ShouldNotBeNil)
64	})
65}
66
67// Helpers for slice tests.
68func float64sEqual(values []float64, expected ...float64) {
69	So(values, ShouldHaveLength, len(expected))
70	for i, v := range expected {
71		So(values[i], ShouldEqual, v)
72	}
73}
74
75func intsEqual(values []int, expected ...int) {
76	So(values, ShouldHaveLength, len(expected))
77	for i, v := range expected {
78		So(values[i], ShouldEqual, v)
79	}
80}
81
82func int64sEqual(values []int64, expected ...int64) {
83	So(values, ShouldHaveLength, len(expected))
84	for i, v := range expected {
85		So(values[i], ShouldEqual, v)
86	}
87}
88
89func uintsEqual(values []uint, expected ...uint) {
90	So(values, ShouldHaveLength, len(expected))
91	for i, v := range expected {
92		So(values[i], ShouldEqual, v)
93	}
94}
95
96func uint64sEqual(values []uint64, expected ...uint64) {
97	So(values, ShouldHaveLength, len(expected))
98	for i, v := range expected {
99		So(values[i], ShouldEqual, v)
100	}
101}
102
103func timesEqual(values []time.Time, expected ...time.Time) {
104	So(values, ShouldHaveLength, len(expected))
105	for i, v := range expected {
106		So(values[i].String(), ShouldEqual, v.String())
107	}
108}
109
110func TestKey_Helpers(t *testing.T) {
111	Convey("Getting and setting values", t, func() {
112		f, err := ini.Load(_FULL_CONF)
113		So(err, ShouldBeNil)
114		So(f, ShouldNotBeNil)
115
116		Convey("Get string representation", func() {
117			sec := f.Section("")
118			So(sec, ShouldNotBeNil)
119			So(sec.Key("NAME").Value(), ShouldEqual, "ini")
120			So(sec.Key("NAME").String(), ShouldEqual, "ini")
121			So(sec.Key("NAME").Validate(func(in string) string {
122				return in
123			}), ShouldEqual, "ini")
124			So(sec.Key("NAME").Comment, ShouldEqual, "; Package name")
125			So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
126
127			Convey("With ValueMapper", func() {
128				f.ValueMapper = func(in string) string {
129					if in == "gopkg.in/%(NAME)s.%(VERSION)s" {
130						return "github.com/go-ini/ini"
131					}
132					return in
133				}
134				So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "github.com/go-ini/ini")
135			})
136		})
137
138		Convey("Get values in non-default section", func() {
139			sec := f.Section("author")
140			So(sec, ShouldNotBeNil)
141			So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
142			So(sec.Key("GITHUB").String(), ShouldEqual, "https://github.com/Unknwon")
143
144			sec = f.Section("package")
145			So(sec, ShouldNotBeNil)
146			So(sec.Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
147		})
148
149		Convey("Get auto-increment key names", func() {
150			keys := f.Section("features").Keys()
151			for i, k := range keys {
152				So(k.Name(), ShouldEqual, fmt.Sprintf("#%d", i+1))
153			}
154		})
155
156		Convey("Get parent-keys that are available to the child section", func() {
157			parentKeys := f.Section("package.sub").ParentKeys()
158			for _, k := range parentKeys {
159				So(k.Name(), ShouldEqual, "CLONE_URL")
160			}
161		})
162
163		Convey("Get overwrite value", func() {
164			So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
165		})
166
167		Convey("Get sections", func() {
168			sections := f.Sections()
169			for i, name := range []string{ini.DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"} {
170				So(sections[i].Name(), ShouldEqual, name)
171			}
172		})
173
174		Convey("Get parent section value", func() {
175			So(f.Section("package.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
176			So(f.Section("package.fake.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
177		})
178
179		Convey("Get multiple line value", func() {
180			So(f.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n")
181		})
182
183		Convey("Get values with type", func() {
184			sec := f.Section("types")
185			v1, err := sec.Key("BOOL").Bool()
186			So(err, ShouldBeNil)
187			So(v1, ShouldBeTrue)
188
189			v1, err = sec.Key("BOOL_FALSE").Bool()
190			So(err, ShouldBeNil)
191			So(v1, ShouldBeFalse)
192
193			v2, err := sec.Key("FLOAT64").Float64()
194			So(err, ShouldBeNil)
195			So(v2, ShouldEqual, 1.25)
196
197			v3, err := sec.Key("INT").Int()
198			So(err, ShouldBeNil)
199			So(v3, ShouldEqual, 10)
200
201			v4, err := sec.Key("INT").Int64()
202			So(err, ShouldBeNil)
203			So(v4, ShouldEqual, 10)
204
205			v5, err := sec.Key("UINT").Uint()
206			So(err, ShouldBeNil)
207			So(v5, ShouldEqual, 3)
208
209			v6, err := sec.Key("UINT").Uint64()
210			So(err, ShouldBeNil)
211			So(v6, ShouldEqual, 3)
212
213			t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
214			So(err, ShouldBeNil)
215			v7, err := sec.Key("TIME").Time()
216			So(err, ShouldBeNil)
217			So(v7.String(), ShouldEqual, t.String())
218
219			Convey("Must get values with type", func() {
220				So(sec.Key("STRING").MustString("404"), ShouldEqual, "str")
221				So(sec.Key("BOOL").MustBool(), ShouldBeTrue)
222				So(sec.Key("FLOAT64").MustFloat64(), ShouldEqual, 1.25)
223				So(sec.Key("INT").MustInt(), ShouldEqual, 10)
224				So(sec.Key("INT").MustInt64(), ShouldEqual, 10)
225				So(sec.Key("UINT").MustUint(), ShouldEqual, 3)
226				So(sec.Key("UINT").MustUint64(), ShouldEqual, 3)
227				So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String())
228
229				dur, err := time.ParseDuration("2h45m")
230				So(err, ShouldBeNil)
231				So(sec.Key("DURATION").MustDuration().Seconds(), ShouldEqual, dur.Seconds())
232
233				Convey("Must get values with default value", func() {
234					So(sec.Key("STRING_404").MustString("404"), ShouldEqual, "404")
235					So(sec.Key("BOOL_404").MustBool(true), ShouldBeTrue)
236					So(sec.Key("FLOAT64_404").MustFloat64(2.5), ShouldEqual, 2.5)
237					So(sec.Key("INT_404").MustInt(15), ShouldEqual, 15)
238					So(sec.Key("INT64_404").MustInt64(15), ShouldEqual, 15)
239					So(sec.Key("UINT_404").MustUint(6), ShouldEqual, 6)
240					So(sec.Key("UINT64_404").MustUint64(6), ShouldEqual, 6)
241
242					t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
243					So(err, ShouldBeNil)
244					So(sec.Key("TIME_404").MustTime(t).String(), ShouldEqual, t.String())
245
246					So(sec.Key("DURATION_404").MustDuration(dur).Seconds(), ShouldEqual, dur.Seconds())
247
248					Convey("Must should set default as key value", func() {
249						So(sec.Key("STRING_404").String(), ShouldEqual, "404")
250						So(sec.Key("BOOL_404").String(), ShouldEqual, "true")
251						So(sec.Key("FLOAT64_404").String(), ShouldEqual, "2.5")
252						So(sec.Key("INT_404").String(), ShouldEqual, "15")
253						So(sec.Key("INT64_404").String(), ShouldEqual, "15")
254						So(sec.Key("UINT_404").String(), ShouldEqual, "6")
255						So(sec.Key("UINT64_404").String(), ShouldEqual, "6")
256						So(sec.Key("TIME_404").String(), ShouldEqual, "2014-01-01T20:17:05Z")
257						So(sec.Key("DURATION_404").String(), ShouldEqual, "2h45m0s")
258					})
259				})
260			})
261		})
262
263		Convey("Get value with candidates", func() {
264			sec := f.Section("types")
265			So(sec.Key("STRING").In("", []string{"str", "arr", "types"}), ShouldEqual, "str")
266			So(sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
267			So(sec.Key("INT").InInt(0, []int{10, 20, 30}), ShouldEqual, 10)
268			So(sec.Key("INT").InInt64(0, []int64{10, 20, 30}), ShouldEqual, 10)
269			So(sec.Key("UINT").InUint(0, []uint{3, 6, 9}), ShouldEqual, 3)
270			So(sec.Key("UINT").InUint64(0, []uint64{3, 6, 9}), ShouldEqual, 3)
271
272			zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
273			So(err, ShouldBeNil)
274			t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
275			So(err, ShouldBeNil)
276			So(sec.Key("TIME").InTime(zt, []time.Time{t, time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
277
278			Convey("Get value with candidates and default value", func() {
279				So(sec.Key("STRING_404").In("str", []string{"str", "arr", "types"}), ShouldEqual, "str")
280				So(sec.Key("FLOAT64_404").InFloat64(1.25, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
281				So(sec.Key("INT_404").InInt(10, []int{10, 20, 30}), ShouldEqual, 10)
282				So(sec.Key("INT64_404").InInt64(10, []int64{10, 20, 30}), ShouldEqual, 10)
283				So(sec.Key("UINT_404").InUint(3, []uint{3, 6, 9}), ShouldEqual, 3)
284				So(sec.Key("UINT_404").InUint64(3, []uint64{3, 6, 9}), ShouldEqual, 3)
285				So(sec.Key("TIME_404").InTime(t, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
286			})
287		})
288
289		Convey("Get values in range", func() {
290			sec := f.Section("types")
291			So(sec.Key("FLOAT64").RangeFloat64(0, 1, 2), ShouldEqual, 1.25)
292			So(sec.Key("INT").RangeInt(0, 10, 20), ShouldEqual, 10)
293			So(sec.Key("INT").RangeInt64(0, 10, 20), ShouldEqual, 10)
294
295			minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
296			So(err, ShouldBeNil)
297			midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z")
298			So(err, ShouldBeNil)
299			maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z")
300			So(err, ShouldBeNil)
301			t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
302			So(err, ShouldBeNil)
303			So(sec.Key("TIME").RangeTime(t, minT, maxT).String(), ShouldEqual, t.String())
304
305			Convey("Get value in range with default value", func() {
306				So(sec.Key("FLOAT64").RangeFloat64(5, 0, 1), ShouldEqual, 5)
307				So(sec.Key("INT").RangeInt(7, 0, 5), ShouldEqual, 7)
308				So(sec.Key("INT").RangeInt64(7, 0, 5), ShouldEqual, 7)
309				So(sec.Key("TIME").RangeTime(t, minT, midT).String(), ShouldEqual, t.String())
310			})
311		})
312
313		Convey("Get values into slice", func() {
314			sec := f.Section("array")
315			So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de")
316			So(len(sec.Key("STRINGS_404").Strings(",")), ShouldEqual, 0)
317
318			vals1 := sec.Key("FLOAT64S").Float64s(",")
319			float64sEqual(vals1, 1.1, 2.2, 3.3)
320
321			vals2 := sec.Key("INTS").Ints(",")
322			intsEqual(vals2, 1, 2, 3)
323
324			vals3 := sec.Key("INTS").Int64s(",")
325			int64sEqual(vals3, 1, 2, 3)
326
327			vals4 := sec.Key("UINTS").Uints(",")
328			uintsEqual(vals4, 1, 2, 3)
329
330			vals5 := sec.Key("UINTS").Uint64s(",")
331			uint64sEqual(vals5, 1, 2, 3)
332
333			t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
334			So(err, ShouldBeNil)
335			vals6 := sec.Key("TIMES").Times(",")
336			timesEqual(vals6, t, t, t)
337		})
338
339		Convey("Test string slice escapes", func() {
340			sec := f.Section("string escapes")
341			So(sec.Key("key1").Strings(","), ShouldResemble, []string{"value1", "value2", "value3"})
342			So(sec.Key("key2").Strings(","), ShouldResemble, []string{"value1, value2"})
343			So(sec.Key("key3").Strings(","), ShouldResemble, []string{`val\ue1`, "value2"})
344			So(sec.Key("key4").Strings(","), ShouldResemble, []string{`value1\`, `value\\2`})
345			So(sec.Key("key5").Strings(",,"), ShouldResemble, []string{"value1,, value2"})
346			So(sec.Key("key6").Strings(" "), ShouldResemble, []string{"aaa", "bbb and space", "ccc"})
347		})
348
349		Convey("Get valid values into slice", func() {
350			sec := f.Section("array")
351			vals1 := sec.Key("FLOAT64S").ValidFloat64s(",")
352			float64sEqual(vals1, 1.1, 2.2, 3.3)
353
354			vals2 := sec.Key("INTS").ValidInts(",")
355			intsEqual(vals2, 1, 2, 3)
356
357			vals3 := sec.Key("INTS").ValidInt64s(",")
358			int64sEqual(vals3, 1, 2, 3)
359
360			vals4 := sec.Key("UINTS").ValidUints(",")
361			uintsEqual(vals4, 1, 2, 3)
362
363			vals5 := sec.Key("UINTS").ValidUint64s(",")
364			uint64sEqual(vals5, 1, 2, 3)
365
366			t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
367			So(err, ShouldBeNil)
368			vals6 := sec.Key("TIMES").ValidTimes(",")
369			timesEqual(vals6, t, t, t)
370		})
371
372		Convey("Get values one type into slice of another type", func() {
373			sec := f.Section("array")
374			vals1 := sec.Key("STRINGS").ValidFloat64s(",")
375			So(vals1, ShouldBeEmpty)
376
377			vals2 := sec.Key("STRINGS").ValidInts(",")
378			So(vals2, ShouldBeEmpty)
379
380			vals3 := sec.Key("STRINGS").ValidInt64s(",")
381			So(vals3, ShouldBeEmpty)
382
383			vals4 := sec.Key("STRINGS").ValidUints(",")
384			So(vals4, ShouldBeEmpty)
385
386			vals5 := sec.Key("STRINGS").ValidUint64s(",")
387			So(vals5, ShouldBeEmpty)
388
389			vals6 := sec.Key("STRINGS").ValidTimes(",")
390			So(vals6, ShouldBeEmpty)
391		})
392
393		Convey("Get valid values into slice without errors", func() {
394			sec := f.Section("array")
395			vals1, err := sec.Key("FLOAT64S").StrictFloat64s(",")
396			So(err, ShouldBeNil)
397			float64sEqual(vals1, 1.1, 2.2, 3.3)
398
399			vals2, err := sec.Key("INTS").StrictInts(",")
400			So(err, ShouldBeNil)
401			intsEqual(vals2, 1, 2, 3)
402
403			vals3, err := sec.Key("INTS").StrictInt64s(",")
404			So(err, ShouldBeNil)
405			int64sEqual(vals3, 1, 2, 3)
406
407			vals4, err := sec.Key("UINTS").StrictUints(",")
408			So(err, ShouldBeNil)
409			uintsEqual(vals4, 1, 2, 3)
410
411			vals5, err := sec.Key("UINTS").StrictUint64s(",")
412			So(err, ShouldBeNil)
413			uint64sEqual(vals5, 1, 2, 3)
414
415			t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
416			So(err, ShouldBeNil)
417			vals6, err := sec.Key("TIMES").StrictTimes(",")
418			So(err, ShouldBeNil)
419			timesEqual(vals6, t, t, t)
420		})
421
422		Convey("Get invalid values into slice", func() {
423			sec := f.Section("array")
424			vals1, err := sec.Key("STRINGS").StrictFloat64s(",")
425			So(vals1, ShouldBeEmpty)
426			So(err, ShouldNotBeNil)
427
428			vals2, err := sec.Key("STRINGS").StrictInts(",")
429			So(vals2, ShouldBeEmpty)
430			So(err, ShouldNotBeNil)
431
432			vals3, err := sec.Key("STRINGS").StrictInt64s(",")
433			So(vals3, ShouldBeEmpty)
434			So(err, ShouldNotBeNil)
435
436			vals4, err := sec.Key("STRINGS").StrictUints(",")
437			So(vals4, ShouldBeEmpty)
438			So(err, ShouldNotBeNil)
439
440			vals5, err := sec.Key("STRINGS").StrictUint64s(",")
441			So(vals5, ShouldBeEmpty)
442			So(err, ShouldNotBeNil)
443
444			vals6, err := sec.Key("STRINGS").StrictTimes(",")
445			So(vals6, ShouldBeEmpty)
446			So(err, ShouldNotBeNil)
447		})
448	})
449}
450
451func TestKey_StringsWithShadows(t *testing.T) {
452	Convey("Get strings of shadows of a key", t, func() {
453		f, err := ini.ShadowLoad([]byte(""))
454		So(err, ShouldBeNil)
455		So(f, ShouldNotBeNil)
456
457		k, err := f.Section("").NewKey("NUMS", "1,2")
458		So(err, ShouldBeNil)
459		So(k, ShouldNotBeNil)
460		k, err = f.Section("").NewKey("NUMS", "4,5,6")
461		So(err, ShouldBeNil)
462		So(k, ShouldNotBeNil)
463
464		So(k.StringsWithShadows(","), ShouldResemble, []string{"1", "2", "4", "5", "6"})
465	})
466}
467
468func TestKey_SetValue(t *testing.T) {
469	Convey("Set value of key", t, func() {
470		f := ini.Empty()
471		So(f, ShouldNotBeNil)
472
473		k, err := f.Section("").NewKey("NAME", "ini")
474		So(err, ShouldBeNil)
475		So(k, ShouldNotBeNil)
476		So(k.Value(), ShouldEqual, "ini")
477
478		k.SetValue("ini.v1")
479		So(k.Value(), ShouldEqual, "ini.v1")
480	})
481}
482
483func TestKey_NestedValues(t *testing.T) {
484	Convey("Read and write nested values", t, func() {
485		f, err := ini.LoadSources(ini.LoadOptions{
486			AllowNestedValues: true,
487		}, []byte(`
488aws_access_key_id = foo
489aws_secret_access_key = bar
490region = us-west-2
491s3 =
492  max_concurrent_requests=10
493  max_queue_size=1000`))
494		So(err, ShouldBeNil)
495		So(f, ShouldNotBeNil)
496
497		So(f.Section("").Key("s3").NestedValues(), ShouldResemble, []string{"max_concurrent_requests=10", "max_queue_size=1000"})
498
499		var buf bytes.Buffer
500		_, err = f.WriteTo(&buf)
501		So(err, ShouldBeNil)
502		So(buf.String(), ShouldEqual, `aws_access_key_id     = foo
503aws_secret_access_key = bar
504region                = us-west-2
505s3                    =
506  max_concurrent_requests=10
507  max_queue_size=1000
508
509`)
510	})
511}
512
513func TestRecursiveValues(t *testing.T) {
514	Convey("Recursive values should not reflect on same key", t, func() {
515		f, err := ini.Load([]byte(`
516NAME = ini
517[package]
518NAME = %(NAME)s`))
519		So(err, ShouldBeNil)
520		So(f, ShouldNotBeNil)
521		So(f.Section("package").Key("NAME").String(), ShouldEqual, "ini")
522	})
523}
524