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