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