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