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