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 28type testNested struct { 29 Cities []string `delim:"|"` 30 Visits []time.Time 31 Years []int 32 Numbers []int64 33 Ages []uint 34 Populations []uint64 35 Coordinates []float64 36 Flags []bool 37 Note string 38 Unused int `ini:"-"` 39} 40 41type TestEmbeded struct { 42 GPA float64 43} 44 45type testStruct struct { 46 Name string `ini:"NAME"` 47 Age int 48 Male bool 49 Money float64 50 Born time.Time 51 Time time.Duration `ini:"Duration"` 52 Others testNested 53 OthersPtr *testNested 54 NilPtr *testNested 55 *TestEmbeded `ini:"grade"` 56 Unused int `ini:"-"` 57 Unsigned uint 58 Omitted bool `ini:"omitthis,omitempty"` 59 Shadows []string `ini:",,allowshadow"` 60 ShadowInts []int `ini:"Shadows,,allowshadow"` 61 BoolPtr *bool 62 BoolPtrNil *bool 63 FloatPtr *float64 64 FloatPtrNil *float64 65 IntPtr *int 66 IntPtrNil *int 67 UintPtr *uint 68 UintPtrNil *uint 69 StringPtr *string 70 StringPtrNil *string 71 TimePtr *time.Time 72 TimePtrNil *time.Time 73 DurationPtr *time.Duration 74 DurationPtrNil *time.Duration 75} 76 77const _CONF_DATA_STRUCT = ` 78NAME = Unknwon 79Age = 21 80Male = true 81Money = 1.25 82Born = 1993-10-07T20:17:05Z 83Duration = 2h45m 84Unsigned = 3 85omitthis = true 86Shadows = 1, 2 87Shadows = 3, 4 88BoolPtr = false 89FloatPtr = 0 90IntPtr = 0 91UintPtr = 0 92StringPtr = "" 93TimePtr = 0001-01-01T00:00:00Z 94DurationPtr = 0s 95 96[Others] 97Cities = HangZhou|Boston 98Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z 99Years = 1993,1994 100Numbers = 10010,10086 101Ages = 18,19 102Populations = 12345678,98765432 103Coordinates = 192.168,10.11 104Flags = true,false 105Note = Hello world! 106 107[OthersPtr] 108Cities = HangZhou|Boston 109Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z 110Years = 1993,1994 111Numbers = 10010,10086 112Ages = 18,19 113Populations = 12345678,98765432 114Coordinates = 192.168,10.11 115Flags = true,false 116Note = Hello world! 117 118[grade] 119GPA = 2.8 120 121[foo.bar] 122Here = there 123When = then 124` 125 126type unsupport struct { 127 Byte byte 128} 129 130type unsupport2 struct { 131 Others struct { 132 Cities byte 133 } 134} 135 136type Unsupport3 struct { 137 Cities byte 138} 139 140type unsupport4 struct { 141 *Unsupport3 `ini:"Others"` 142} 143 144type defaultValue struct { 145 Name string 146 Age int 147 Male bool 148 Optional *bool 149 Money float64 150 Born time.Time 151 Cities []string 152} 153 154type fooBar struct { 155 Here, When string 156} 157 158const _INVALID_DATA_CONF_STRUCT = ` 159Name = 160Age = age 161Male = 123 162Money = money 163Born = nil 164Cities = 165` 166 167func Test_MapToStruct(t *testing.T) { 168 Convey("Map to struct", t, func() { 169 Convey("Map file to struct", func() { 170 ts := new(testStruct) 171 So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil) 172 173 So(ts.Name, ShouldEqual, "Unknwon") 174 So(ts.Age, ShouldEqual, 21) 175 So(ts.Male, ShouldBeTrue) 176 So(ts.Money, ShouldEqual, 1.25) 177 So(ts.Unsigned, ShouldEqual, 3) 178 179 t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z") 180 So(err, ShouldBeNil) 181 So(ts.Born.String(), ShouldEqual, t.String()) 182 183 dur, err := time.ParseDuration("2h45m") 184 So(err, ShouldBeNil) 185 So(ts.Time.Seconds(), ShouldEqual, dur.Seconds()) 186 187 So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston") 188 So(ts.Others.Visits[0].String(), ShouldEqual, t.String()) 189 So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]") 190 So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]") 191 So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]") 192 So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]") 193 So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]") 194 So(fmt.Sprint(ts.Others.Flags), ShouldEqual, "[true false]") 195 So(ts.Others.Note, ShouldEqual, "Hello world!") 196 So(ts.TestEmbeded.GPA, ShouldEqual, 2.8) 197 198 So(strings.Join(ts.OthersPtr.Cities, ","), ShouldEqual, "HangZhou,Boston") 199 So(ts.OthersPtr.Visits[0].String(), ShouldEqual, t.String()) 200 So(fmt.Sprint(ts.OthersPtr.Years), ShouldEqual, "[1993 1994]") 201 So(fmt.Sprint(ts.OthersPtr.Numbers), ShouldEqual, "[10010 10086]") 202 So(fmt.Sprint(ts.OthersPtr.Ages), ShouldEqual, "[18 19]") 203 So(fmt.Sprint(ts.OthersPtr.Populations), ShouldEqual, "[12345678 98765432]") 204 So(fmt.Sprint(ts.OthersPtr.Coordinates), ShouldEqual, "[192.168 10.11]") 205 So(fmt.Sprint(ts.OthersPtr.Flags), ShouldEqual, "[true false]") 206 So(ts.OthersPtr.Note, ShouldEqual, "Hello world!") 207 208 So(ts.NilPtr, ShouldBeNil) 209 210 So(*ts.BoolPtr, ShouldEqual, false) 211 So(ts.BoolPtrNil, ShouldEqual, nil) 212 So(*ts.FloatPtr, ShouldEqual, 0) 213 So(ts.FloatPtrNil, ShouldEqual, nil) 214 So(*ts.IntPtr, ShouldEqual, 0) 215 So(ts.IntPtrNil, ShouldEqual, nil) 216 So(*ts.UintPtr, ShouldEqual, 0) 217 So(ts.UintPtrNil, ShouldEqual, nil) 218 So(*ts.StringPtr, ShouldEqual, "") 219 So(ts.StringPtrNil, ShouldEqual, nil) 220 So(*ts.TimePtr, ShouldNotEqual, nil) 221 So(ts.TimePtrNil, ShouldEqual, nil) 222 So(*ts.DurationPtr, ShouldEqual, 0) 223 So(ts.DurationPtrNil, ShouldEqual, nil) 224 225 }) 226 227 Convey("Map section to struct", func() { 228 foobar := new(fooBar) 229 f, err := ini.Load([]byte(_CONF_DATA_STRUCT)) 230 So(err, ShouldBeNil) 231 232 So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil) 233 So(foobar.Here, ShouldEqual, "there") 234 So(foobar.When, ShouldEqual, "then") 235 }) 236 237 Convey("Map to non-pointer struct", func() { 238 f, err := ini.Load([]byte(_CONF_DATA_STRUCT)) 239 So(err, ShouldBeNil) 240 So(f, ShouldNotBeNil) 241 242 So(f.MapTo(testStruct{}), ShouldNotBeNil) 243 }) 244 245 Convey("Map to unsupported type", func() { 246 f, err := ini.Load([]byte(_CONF_DATA_STRUCT)) 247 So(err, ShouldBeNil) 248 So(f, ShouldNotBeNil) 249 250 f.NameMapper = func(raw string) string { 251 if raw == "Byte" { 252 return "NAME" 253 } 254 return raw 255 } 256 So(f.MapTo(&unsupport{}), ShouldNotBeNil) 257 So(f.MapTo(&unsupport2{}), ShouldNotBeNil) 258 So(f.MapTo(&unsupport4{}), ShouldNotBeNil) 259 }) 260 261 Convey("Map to omitempty field", func() { 262 ts := new(testStruct) 263 So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil) 264 265 So(ts.Omitted, ShouldEqual, true) 266 }) 267 268 Convey("Map with shadows", func() { 269 f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true}, []byte(_CONF_DATA_STRUCT)) 270 So(err, ShouldBeNil) 271 ts := new(testStruct) 272 So(f.MapTo(ts), ShouldBeNil) 273 274 So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4") 275 So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]") 276 }) 277 278 Convey("Map from invalid data source", func() { 279 So(ini.MapTo(&testStruct{}, "hi"), ShouldNotBeNil) 280 }) 281 282 Convey("Map to wrong types and gain default values", func() { 283 f, err := ini.Load([]byte(_INVALID_DATA_CONF_STRUCT)) 284 So(err, ShouldBeNil) 285 286 t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z") 287 So(err, ShouldBeNil) 288 dv := &defaultValue{"Joe", 10, true, nil, 1.25, t, []string{"HangZhou", "Boston"}} 289 So(f.MapTo(dv), ShouldBeNil) 290 So(dv.Name, ShouldEqual, "Joe") 291 So(dv.Age, ShouldEqual, 10) 292 So(dv.Male, ShouldBeTrue) 293 So(dv.Money, ShouldEqual, 1.25) 294 So(dv.Born.String(), ShouldEqual, t.String()) 295 So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston") 296 }) 297 }) 298 299 Convey("Map to struct in strict mode", t, func() { 300 f, err := ini.Load([]byte(` 301name=bruce 302age=a30`)) 303 So(err, ShouldBeNil) 304 305 type Strict struct { 306 Name string `ini:"name"` 307 Age int `ini:"age"` 308 } 309 s := new(Strict) 310 311 So(f.Section("").StrictMapTo(s), ShouldNotBeNil) 312 }) 313 314 Convey("Map slice in strict mode", t, func() { 315 f, err := ini.Load([]byte(` 316names=alice, bruce`)) 317 So(err, ShouldBeNil) 318 319 type Strict struct { 320 Names []string `ini:"names"` 321 } 322 s := new(Strict) 323 324 So(f.Section("").StrictMapTo(s), ShouldBeNil) 325 So(fmt.Sprint(s.Names), ShouldEqual, "[alice bruce]") 326 }) 327} 328 329func Test_ReflectFromStruct(t *testing.T) { 330 Convey("Reflect from struct", t, func() { 331 type Embeded struct { 332 Dates []time.Time `delim:"|" comment:"Time data"` 333 Places []string 334 Years []int 335 Numbers []int64 336 Ages []uint 337 Populations []uint64 338 Coordinates []float64 339 Flags []bool 340 None []int 341 } 342 type Author struct { 343 Name string `ini:"NAME"` 344 Male bool 345 Optional *bool 346 Age int `comment:"Author's age"` 347 Height uint 348 GPA float64 349 Date time.Time 350 NeverMind string `ini:"-"` 351 *Embeded `ini:"infos" comment:"Embeded section"` 352 } 353 354 t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z") 355 So(err, ShouldBeNil) 356 a := &Author{"Unknwon", true, nil, 21, 100, 2.8, t, "", 357 &Embeded{ 358 []time.Time{t, t}, 359 []string{"HangZhou", "Boston"}, 360 []int{1993, 1994}, 361 []int64{10010, 10086}, 362 []uint{18, 19}, 363 []uint64{12345678, 98765432}, 364 []float64{192.168, 10.11}, 365 []bool{true, false}, 366 []int{}, 367 }} 368 cfg := ini.Empty() 369 So(ini.ReflectFrom(cfg, a), ShouldBeNil) 370 371 var buf bytes.Buffer 372 _, err = cfg.WriteTo(&buf) 373 So(err, ShouldBeNil) 374 So(buf.String(), ShouldEqual, `NAME = Unknwon 375Male = true 376Optional = 377; Author's age 378Age = 21 379Height = 100 380GPA = 2.8 381Date = 1993-10-07T20:17:05Z 382 383; Embeded section 384[infos] 385; Time data 386Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z 387Places = HangZhou,Boston 388Years = 1993,1994 389Numbers = 10010,10086 390Ages = 18,19 391Populations = 12345678,98765432 392Coordinates = 192.168,10.11 393Flags = true,false 394None = 395 396`) 397 398 Convey("Reflect from non-point struct", func() { 399 So(ini.ReflectFrom(cfg, Author{}), ShouldNotBeNil) 400 }) 401 402 Convey("Reflect from struct with omitempty", func() { 403 cfg := ini.Empty() 404 type SpecialStruct struct { 405 FirstName string `ini:"first_name"` 406 LastName string `ini:"last_name"` 407 JustOmitMe string `ini:"omitempty"` 408 LastLogin time.Time `ini:"last_login,omitempty"` 409 LastLogin2 time.Time `ini:",omitempty"` 410 NotEmpty int `ini:"omitempty"` 411 } 412 413 So(ini.ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil) 414 415 var buf bytes.Buffer 416 _, err = cfg.WriteTo(&buf) 417 So(buf.String(), ShouldEqual, `first_name = John 418last_name = Doe 419omitempty = 9 420 421`) 422 }) 423 }) 424} 425 426// Inspired by https://github.com/go-ini/ini/issues/196 427func TestMapToAndReflectFromStructWithShadows(t *testing.T) { 428 Convey("Map to struct and then reflect with shadows should generate original config content", t, func() { 429 type include struct { 430 Paths []string `ini:"path,omitempty,allowshadow"` 431 } 432 433 cfg, err := ini.LoadSources(ini.LoadOptions{ 434 AllowShadows: true, 435 }, []byte(` 436[include] 437path = /tmp/gpm-profiles/test5.profile 438path = /tmp/gpm-profiles/test1.profile`)) 439 So(err, ShouldBeNil) 440 441 sec := cfg.Section("include") 442 inc := new(include) 443 err = sec.MapTo(inc) 444 So(err, ShouldBeNil) 445 446 err = sec.ReflectFrom(inc) 447 So(err, ShouldBeNil) 448 449 var buf bytes.Buffer 450 _, err = cfg.WriteTo(&buf) 451 So(err, ShouldBeNil) 452 So(buf.String(), ShouldEqual, `[include] 453path = /tmp/gpm-profiles/test5.profile 454path = /tmp/gpm-profiles/test1.profile 455 456`) 457 }) 458} 459 460type testMapper struct { 461 PackageName string 462} 463 464func Test_NameGetter(t *testing.T) { 465 Convey("Test name mappers", t, func() { 466 So(ini.MapToWithMapper(&testMapper{}, ini.TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil) 467 468 cfg, err := ini.Load([]byte("PACKAGE_NAME=ini")) 469 So(err, ShouldBeNil) 470 So(cfg, ShouldNotBeNil) 471 472 cfg.NameMapper = ini.AllCapsUnderscore 473 tg := new(testMapper) 474 So(cfg.MapTo(tg), ShouldBeNil) 475 So(tg.PackageName, ShouldEqual, "ini") 476 }) 477} 478 479type testDurationStruct struct { 480 Duration time.Duration `ini:"Duration"` 481} 482 483func Test_Duration(t *testing.T) { 484 Convey("Duration less than 16m50s", t, func() { 485 ds := new(testDurationStruct) 486 So(ini.MapTo(ds, []byte("Duration=16m49s")), ShouldBeNil) 487 488 dur, err := time.ParseDuration("16m49s") 489 So(err, ShouldBeNil) 490 So(ds.Duration.Seconds(), ShouldEqual, dur.Seconds()) 491 }) 492} 493