github.com/grailbio/base@v0.0.11/config/profile_test.go (about) 1 // Copyright 2019 GRAIL, Inc. All rights reserved. 2 // Use of this source code is governed by the Apache 2.0 3 // license that can be found in the LICENSE file. 4 5 package config 6 7 import ( 8 "strings" 9 "testing" 10 ) 11 12 type custom struct { 13 x int 14 f float64 15 } 16 17 // paramFields is a convenient structure for testing that has fields of various 18 // types that we use to test parameter handling. 19 type paramFields struct { 20 c custom 21 p *custom 22 ch chan struct{} 23 a any 24 } 25 26 func init() { 27 Register("test/custom", func(inst *Constructor[custom]) { 28 var c custom 29 inst.IntVar(&c.x, "x", -1, "the x value") 30 inst.FloatVar(&c.f, "f", 0, "the f value") 31 inst.New = func() (custom, error) { 32 return c, nil 33 } 34 }) 35 36 Default("test/default", "test/custom") 37 38 Default("test/default2", "test/default") 39 40 Register("test/custom-ptr", func(inst *Constructor[*custom]) { 41 var c custom 42 inst.IntVar(&c.x, "x", -1, "the x value") 43 inst.New = func() (*custom, error) { 44 return &c, nil 45 } 46 }) 47 48 Register("test/1", func(inst *Constructor[int]) { 49 var c custom 50 inst.InstanceVar(&c, "custom", "test/default", "the custom struct") 51 x := inst.Int("x", 123, "the x value") 52 inst.New = func() (int, error) { 53 return *x + c.x, nil 54 } 55 }) 56 57 Register("test/custom-nil", func(inst *Constructor[*custom]) { 58 inst.New = func() (*custom, error) { 59 return (*custom)(nil), nil 60 } 61 }) 62 63 Default("test/default-custom-nil", "test/custom-nil") 64 65 Register("test/untyped-nil", func(inst *Constructor[any]) { 66 inst.New = func() (any, error) { 67 return nil, nil 68 } 69 }) 70 71 Default("test/default-untyped-nil", "test/untyped-nil") 72 73 Register("test/params/empty", func(inst *Constructor[paramFields]) { 74 var pf paramFields 75 inst.InstanceVar(&pf.p, "p", "test/custom-nil", "") 76 inst.InstanceVar(&pf.ch, "ch", "", "") 77 inst.InstanceVar(&pf.a, "a", "", "") 78 inst.New = func() (paramFields, error) { 79 return pf, nil 80 } 81 }) 82 83 Register("test/params/nil", func(inst *Constructor[paramFields]) { 84 var pf paramFields 85 inst.InstanceVar(&pf.p, "p", "nil", "") 86 inst.InstanceVar(&pf.ch, "ch", "", "") 87 inst.InstanceVar(&pf.a, "a", "nil", "") 88 inst.New = func() (paramFields, error) { 89 return pf, nil 90 } 91 }) 92 93 Register("test/params/nil-instance", func(inst *Constructor[paramFields]) { 94 var pf paramFields 95 inst.InstanceVar(&pf.p, "p", "test/custom-nil", "") 96 inst.New = func() (paramFields, error) { 97 return pf, nil 98 } 99 }) 100 101 Register("test/params/empty-non-nilable-recovered", func(inst *Constructor[any]) { 102 var r any 103 func() { 104 defer func() { 105 r = recover() 106 }() 107 var pf paramFields 108 inst.InstanceVar(&pf.c, "c", "", "") 109 }() 110 inst.New = func() (any, error) { 111 return r, nil 112 } 113 }) 114 115 Register("test/params/nil-non-nilable-recovered", func(inst *Constructor[any]) { 116 var r any 117 func() { 118 defer func() { 119 r = recover() 120 }() 121 var pf paramFields 122 inst.InstanceVar(&pf.c, "c", "nil", "") 123 }() 124 inst.New = func() (any, error) { 125 return r, nil 126 } 127 }) 128 129 Register("test/chan", func(inst *Constructor[chan struct{}]) { 130 inst.New = func() (chan struct{}, error) { 131 return make(chan struct{}), nil 132 } 133 }) 134 135 Register("test/params/non-nil", func(inst *Constructor[paramFields]) { 136 var pf paramFields 137 inst.InstanceVar(&pf.c, "c", "test/custom", "") 138 inst.InstanceVar(&pf.p, "p", "test/custom-ptr", "") 139 inst.InstanceVar(&pf.ch, "ch", "test/chan", "") 140 inst.InstanceVar(&pf.a, "a", "test/custom", "") 141 inst.New = func() (paramFields, error) { 142 return pf, nil 143 } 144 }) 145 } 146 147 func TestProfileParamDefault(t *testing.T) { 148 p := New() 149 var x int 150 if err := p.Instance("test/1", &x); err != nil { 151 t.Fatal(err) 152 } 153 if got, want := x, 122; got != want { 154 t.Errorf("got %v, want %v", got, want) 155 } 156 157 p = New() 158 if err := p.Set("test/custom.x", "-100"); err != nil { 159 t.Fatal(err) 160 } 161 if err := p.Instance("test/1", &x); err != nil { 162 t.Fatal(err) 163 } 164 if got, want := x, 23; got != want { 165 t.Errorf("got %v, want %v", got, want) 166 } 167 } 168 169 func TestProfileDefaultInstance(t *testing.T) { 170 t.Run("basic", func(t *testing.T) { 171 p := New() 172 if err := p.Instance("test/default", nil); err != nil { 173 t.Fatal(err) 174 } 175 }) 176 t.Run("override-default", func(t *testing.T) { 177 p := New() 178 if err := p.Parse(strings.NewReader(` 179 instance custom13 test/custom ( 180 x = 13 181 ) 182 instance test/default custom13 183 `)); err != nil { 184 t.Fatal(err) 185 } 186 var c custom 187 if err := p.Instance("test/default", &c); err != nil { 188 t.Fatal(err) 189 } 190 if got, want := c.x, 13; got != want { 191 t.Errorf("got %v, want %v", got, want) 192 } 193 if err := p.Instance("test/default2", &c); err != nil { 194 t.Fatal(err) 195 } 196 if got, want := c.x, 13; got != want { 197 t.Errorf("got %v, want %v", got, want) 198 } 199 }) 200 t.Run("override-second-default", func(t *testing.T) { 201 p := New() 202 if err := p.Parse(strings.NewReader(` 203 instance custom13 test/custom ( 204 x = 13 205 ) 206 instance test/default2 custom13 207 `)); err != nil { 208 t.Fatal(err) 209 } 210 var c custom 211 if err := p.Instance("test/default", &c); err != nil { 212 t.Fatal(err) 213 } 214 if got, want := c.x, -1; got != want { 215 t.Errorf("got %v, want %v", got, want) 216 } 217 if err := p.Instance("test/default2", &c); err != nil { 218 t.Fatal(err) 219 } 220 if got, want := c.x, 13; got != want { 221 t.Errorf("got %v, want %v", got, want) 222 } 223 }) 224 t.Run("override-defaults-differently", func(t *testing.T) { 225 p := New() 226 if err := p.Parse(strings.NewReader(` 227 instance custom13 test/custom ( 228 x = 13 229 ) 230 instance custom132 test/custom ( 231 x = 132 232 ) 233 instance test/default custom13 234 instance test/default2 custom132 235 `)); err != nil { 236 t.Fatal(err) 237 } 238 var c custom 239 if err := p.Instance("test/default", &c); err != nil { 240 t.Fatal(err) 241 } 242 if got, want := c.x, 13; got != want { 243 t.Errorf("got %v, want %v", got, want) 244 } 245 if err := p.Instance("test/default2", &c); err != nil { 246 t.Fatal(err) 247 } 248 if got, want := c.x, 132; got != want { 249 t.Errorf("got %v, want %v", got, want) 250 } 251 }) 252 t.Run("override-default-instance-with-param", func(t *testing.T) { 253 t.Skip() // BXDS-2886 254 p := New() 255 if err := p.Parse(strings.NewReader(` 256 instance test/default test/custom ( 257 x = 13 258 ) 259 `)); err != nil { 260 t.Fatal(err) 261 } 262 var c custom 263 if err := p.Instance("test/default", &c); err != nil { 264 t.Fatal(err) 265 } 266 if got, want := c.x, 13; got != want { 267 t.Errorf("got %v, want %v", got, want) 268 } 269 if err := p.Instance("test/default2", &c); err != nil { 270 t.Fatal(err) 271 } 272 if got, want := c.x, 13; got != want { 273 t.Errorf("got %v, want %v", got, want) 274 } 275 }) 276 t.Run("set-default", func(t *testing.T) { 277 p := New() 278 if err := p.Parse(strings.NewReader(` 279 instance custom13 test/custom ( 280 x = 13 281 ) 282 `)); err != nil { 283 t.Fatal(err) 284 } 285 if err := p.Set("test/default", "custom13"); err != nil { 286 t.Fatal(err) 287 } 288 var c custom 289 if err := p.Instance("test/default", &c); err != nil { 290 t.Fatal(err) 291 } 292 if got, want := c.x, 13; got != want { 293 t.Errorf("got %v, want %v", got, want) 294 } 295 if err := p.Instance("test/default2", &c); err != nil { 296 t.Fatal(err) 297 } 298 if got, want := c.x, 13; got != want { 299 t.Errorf("got %v, want %v", got, want) 300 } 301 }) 302 t.Run("set-second-default", func(t *testing.T) { 303 p := New() 304 if err := p.Parse(strings.NewReader(` 305 instance custom13 test/custom ( 306 x = 13 307 ) 308 `)); err != nil { 309 t.Fatal(err) 310 } 311 if err := p.Set("test/default2", "custom13"); err != nil { 312 t.Fatal(err) 313 } 314 var c custom 315 if err := p.Instance("test/default", &c); err != nil { 316 t.Fatal(err) 317 } 318 if got, want := c.x, -1; got != want { 319 t.Errorf("got %v, want %v", got, want) 320 } 321 if err := p.Instance("test/default2", &c); err != nil { 322 t.Fatal(err) 323 } 324 if got, want := c.x, 13; got != want { 325 t.Errorf("got %v, want %v", got, want) 326 } 327 }) 328 t.Run("set-defaults-differently", func(t *testing.T) { 329 p := New() 330 if err := p.Parse(strings.NewReader(` 331 instance custom13 test/custom ( 332 x = 13 333 ) 334 instance custom132 test/custom ( 335 x = 132 336 ) 337 instance test/default custom13 338 instance test/default2 custom132 339 `)); err != nil { 340 t.Fatal(err) 341 } 342 if err := p.Set("test/default", "custom13"); err != nil { 343 t.Fatal(err) 344 } 345 if err := p.Set("test/default2", "custom132"); err != nil { 346 t.Fatal(err) 347 } 348 var c custom 349 if err := p.Instance("test/default", &c); err != nil { 350 t.Fatal(err) 351 } 352 if got, want := c.x, 13; got != want { 353 t.Errorf("got %v, want %v", got, want) 354 } 355 if err := p.Instance("test/default2", &c); err != nil { 356 t.Fatal(err) 357 } 358 if got, want := c.x, 132; got != want { 359 t.Errorf("got %v, want %v", got, want) 360 } 361 }) 362 } 363 364 func TestProfile(t *testing.T) { 365 p := New() 366 err := p.Parse(strings.NewReader(` 367 param test/custom ( 368 x = 999 369 ) 370 371 param test/1 ( 372 custom = test/custom 373 x = 1 374 ) 375 376 instance testx test/1 ( 377 x = 100 378 ) 379 380 instance testf test/custom ( 381 f = 1 382 ) 383 384 instance test/default testf 385 `)) 386 if err != nil { 387 t.Fatal(err) 388 } 389 390 var x int 391 if err = p.Instance("test/1", &x); err != nil { 392 t.Fatal(err) 393 } 394 if got, want := x, 1000; got != want { 395 t.Errorf("got %v, want %v", got, want) 396 } 397 398 if err = p.Instance("testx", &x); err != nil { 399 t.Fatal(err) 400 } 401 if got, want := x, 1099; got != want { 402 t.Errorf("got %v, want %v", got, want) 403 } 404 405 var str string 406 err = p.Instance("testx", &str) 407 if err == nil || !strings.Contains(err.Error(), "instance \"testx\" of type int is not assignable to provided pointer element type string") { 408 t.Error(err) 409 } 410 411 var c custom 412 if err = p.Instance("testf", &c); err != nil { 413 t.Fatal(err) 414 } 415 if got, want := c.f, 1.; got != want { 416 t.Errorf("got %v, want %v", got, want) 417 } 418 419 // Verify that test/default derives from testf. 420 if err = p.Instance("test/default", &c); err != nil { 421 t.Fatal(err) 422 } 423 if got, want := c.f, 1.; got != want { 424 t.Errorf("got %v, want %v", got, want) 425 } 426 } 427 428 // TestNilInstances verifies that we handle nil/empty instances appropriately. 429 func TestNilInstances(t *testing.T) { 430 var ( 431 mustSet = func(p *Profile, path, value string) { 432 t.Helper() 433 if err := p.Set(path, value); err != nil { 434 t.Error(err) 435 } 436 } 437 mustInstance = func(p *Profile, name string, pa any) { 438 t.Helper() 439 if err := p.Instance(name, pa); err != nil { 440 t.Fatal(err) 441 } 442 } 443 mustEqual = func(got, want any) { 444 t.Helper() 445 if got != want { 446 t.Errorf("got %v, want %v", got, want) 447 } 448 } 449 ) 450 451 var ( 452 p *Profile 453 pc *custom 454 a any 455 pf paramFields 456 ) 457 458 // Verify that top-level instances can be nil. 459 p = New() 460 mustInstance(p, "test/custom-nil", &pc) 461 mustEqual(pc, (*custom)(nil)) 462 mustInstance(p, "test/default-custom-nil", &pc) 463 mustEqual(pc, (*custom)(nil)) 464 mustInstance(p, "test/untyped-nil", &a) 465 mustEqual(a, nil) 466 mustInstance(p, "test/default-untyped-nil", &a) 467 mustEqual(a, nil) 468 469 // Verify that empty InstanceVar defaults produce nil parameters. 470 p = New() 471 mustInstance(p, "test/params/empty", &pf) 472 mustEqual(pf.p, (*custom)(nil)) 473 mustEqual(pf.ch, (chan struct{})(nil)) 474 mustEqual(pf.a, nil) 475 476 // Verify that nil InstanceVar defaults produce nil parameters. 477 p = New() 478 mustInstance(p, "test/params/nil", &pf) 479 mustEqual(pf.p, (*custom)(nil)) 480 mustEqual(pf.ch, (chan struct{})(nil)) 481 mustEqual(pf.a, nil) 482 483 // Verify that an InstanceVar default instance whose value is nil produces 484 // a nil parameter. 485 p = New() 486 mustInstance(p, "test/params/nil-instance", &pf) 487 mustEqual(pf.p, (*custom)(nil)) 488 489 // Verify that InstanceVar panics setting an empty value default for an 490 // element type that cannot be assigned nil. 491 p = New() 492 // Set c to a valid instance, so that the invalid instance error does not 493 // obscure the recovered panic value. 494 mustSet(p, "test/params/empty-non-nilable-recovered.c", "test/custom") 495 mustInstance(p, "test/params/empty-non-nilable-recovered", &a) 496 if a == nil { 497 t.Error("expected non-nil-assignable empty default instance to panic") 498 } 499 500 // Verify that InstanceVar panics setting a nil value default for an 501 // element type that cannot be assigned nil. 502 p = New() 503 // Set c to a valid instance, so that the invalid instance error does not 504 // obscure the recovered panic value. 505 mustSet(p, "test/params/nil-non-nilable-recovered.c", "test/custom") 506 mustInstance(p, "test/params/nil-non-nilable-recovered", &a) 507 if a == nil { 508 t.Error("expected non-nil-assignable nil default instance to panic") 509 } 510 511 // Verify that a non-nil-assignable parameter set to an empty instance is 512 // invalid. 513 p = New() 514 mustSet(p, "test/params/non-nil.c", "") 515 if err := p.Instance("test/params/non-nil", &pf); err == nil { 516 t.Error("non-nil-assignable set to empty instance should return non-nil error") 517 } 518 519 // Verify that a non-nil-assignable parameter set to nil is invalid. 520 p = New() 521 mustSet(p, "test/params/non-nil.c", "nil") 522 if err := p.Instance("test/params/non-nil", &pf); err == nil { 523 t.Error("non-nil-assignable set to nil should return non-nil error") 524 } 525 526 // Verify that nil-assignable parameters can be set to empty, resulting in 527 // nil parameter values. 528 p = New() 529 mustSet(p, "test/params/non-nil.p", "") 530 mustSet(p, "test/params/non-nil.ch", "") 531 mustSet(p, "test/params/non-nil.a", "") 532 mustInstance(p, "test/params/non-nil", &pf) 533 mustEqual(pf.p, (*custom)(nil)) 534 mustEqual(pf.ch, (chan struct{})(nil)) 535 mustEqual(pf.a, nil) 536 537 // Verify that nil-assignable parameters can be set to nil, resulting in 538 // nil parameter values. 539 p = New() 540 mustSet(p, "test/params/non-nil.p", "nil") 541 mustSet(p, "test/params/non-nil.ch", "nil") 542 mustSet(p, "test/params/non-nil.a", "nil") 543 mustInstance(p, "test/params/non-nil", &pf) 544 mustEqual(pf.p, (*custom)(nil)) 545 mustEqual(pf.ch, (chan struct{})(nil)) 546 mustEqual(pf.a, nil) 547 548 // Verify that a nil-assignable parameter can be set to an instance whose 549 // value is nil, resulting in a nil parameter value. 550 p = New() 551 mustSet(p, "test/params/non-nil.p", "test/custom-nil") 552 mustInstance(p, "test/params/non-nil", &pf) 553 mustEqual(pf.p, (*custom)(nil)) 554 555 // Verify that top-level instances cannot be set to empty. 556 p = New() 557 if err := p.Set("test/custom", ""); err == nil { 558 t.Error("top-level instance set to empty should return non-nil error") 559 } 560 561 // Verify that top-level instances cannot be set to nil. 562 p = New() 563 if err := p.Set("test/custom", ""); err == nil { 564 t.Error("top-level instance set to nil should return non-nil error") 565 } 566 } 567 568 func TestSetGet(t *testing.T) { 569 p := New() 570 err := p.Parse(strings.NewReader(` 571 param test/custom ( 572 x = 999 573 ) 574 575 param test/1 ( 576 custom = test/custom 577 x = 1 578 ) 579 580 instance testx test/1 ( 581 x = 100 582 ) 583 584 instance testy test/1 585 586 `)) 587 if err != nil { 588 t.Fatal(err) 589 } 590 591 var ( 592 mustGet = func(k, want string) { 593 t.Helper() 594 got, ok := p.Get(k) 595 if !ok { 596 t.Fatalf("key %v not found", k) 597 } 598 if got != want { 599 t.Fatalf("key %v: got %v, want %v", k, got, want) 600 } 601 } 602 mustSet = func(k, v string) { 603 t.Helper() 604 if err := p.Set(k, v); err != nil { 605 t.Fatalf("set %v %v: %v", k, v, err) 606 } 607 } 608 ) 609 610 mustGet("testy", "test/1") 611 mustGet("test/1.x", "1") 612 mustGet("testx.x", "100") 613 mustGet("testx.custom", "test/custom") 614 mustGet("testx.custom.x", "999") 615 mustGet("testy.x", "1") 616 617 mustSet("testx.custom.x", "1900") 618 mustGet("testx.custom.x", "1900") 619 mustSet("testx.custom.x", "-1900") 620 mustGet("testx.custom.x", "-1900") 621 mustSet("testx.custom.f", "3.14") 622 mustGet("testx.custom.f", "3.14") 623 mustSet("testx.custom.f", "-3.14") 624 mustGet("testx.custom.f", "-3.14") 625 626 mustSet("testy", "testx") 627 mustGet("testy.x", "100") 628 629 } 630 631 // TestInstanceNames verifies that InstanceNames returns the correct set of 632 // instance names. 633 func TestInstanceNames(t *testing.T) { 634 p := New() 635 names := p.InstanceNames() 636 // Because global instances can be added from anywhere, we only verify that 637 // the returned names contains the instances added by this file. 638 for _, name := range []string{ 639 "test/1", 640 "test/custom", 641 "test/default", 642 } { 643 if _, ok := names[name]; !ok { 644 t.Errorf("missing instance name=%v", name) 645 } 646 } 647 }