github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/nomad/structs/structs_test.go (about) 1 package structs 2 3 import ( 4 "reflect" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/hashicorp/go-multierror" 10 ) 11 12 func TestJob_Validate(t *testing.T) { 13 j := &Job{} 14 err := j.Validate() 15 mErr := err.(*multierror.Error) 16 if !strings.Contains(mErr.Errors[0].Error(), "job region") { 17 t.Fatalf("err: %s", err) 18 } 19 if !strings.Contains(mErr.Errors[1].Error(), "job ID") { 20 t.Fatalf("err: %s", err) 21 } 22 if !strings.Contains(mErr.Errors[2].Error(), "job name") { 23 t.Fatalf("err: %s", err) 24 } 25 if !strings.Contains(mErr.Errors[3].Error(), "job type") { 26 t.Fatalf("err: %s", err) 27 } 28 if !strings.Contains(mErr.Errors[4].Error(), "priority") { 29 t.Fatalf("err: %s", err) 30 } 31 if !strings.Contains(mErr.Errors[5].Error(), "datacenters") { 32 t.Fatalf("err: %s", err) 33 } 34 if !strings.Contains(mErr.Errors[6].Error(), "task groups") { 35 t.Fatalf("err: %s", err) 36 } 37 38 j = &Job{ 39 Type: JobTypeService, 40 Periodic: &PeriodicConfig{ 41 Enabled: true, 42 }, 43 } 44 err = j.Validate() 45 mErr = err.(*multierror.Error) 46 if !strings.Contains(mErr.Error(), "Periodic") { 47 t.Fatalf("err: %s", err) 48 } 49 50 j = &Job{ 51 Region: "global", 52 ID: GenerateUUID(), 53 Name: "my-job", 54 Type: JobTypeService, 55 Priority: 50, 56 Datacenters: []string{"dc1"}, 57 TaskGroups: []*TaskGroup{ 58 &TaskGroup{ 59 Name: "web", 60 RestartPolicy: &RestartPolicy{ 61 Interval: 5 * time.Minute, 62 Delay: 10 * time.Second, 63 Attempts: 10, 64 }, 65 }, 66 &TaskGroup{ 67 Name: "web", 68 RestartPolicy: &RestartPolicy{ 69 Interval: 5 * time.Minute, 70 Delay: 10 * time.Second, 71 Attempts: 10, 72 }, 73 }, 74 &TaskGroup{ 75 RestartPolicy: &RestartPolicy{ 76 Interval: 5 * time.Minute, 77 Delay: 10 * time.Second, 78 Attempts: 10, 79 }, 80 }, 81 }, 82 } 83 err = j.Validate() 84 mErr = err.(*multierror.Error) 85 if !strings.Contains(mErr.Errors[0].Error(), "2 redefines 'web' from group 1") { 86 t.Fatalf("err: %s", err) 87 } 88 if !strings.Contains(mErr.Errors[1].Error(), "group 3 missing name") { 89 t.Fatalf("err: %s", err) 90 } 91 if !strings.Contains(mErr.Errors[2].Error(), "Task group 1 validation failed") { 92 t.Fatalf("err: %s", err) 93 } 94 } 95 96 func testJob() *Job { 97 return &Job{ 98 Region: "global", 99 ID: GenerateUUID(), 100 Name: "my-job", 101 Type: JobTypeService, 102 Priority: 50, 103 AllAtOnce: false, 104 Datacenters: []string{"dc1"}, 105 Constraints: []*Constraint{ 106 &Constraint{ 107 LTarget: "$attr.kernel.name", 108 RTarget: "linux", 109 Operand: "=", 110 }, 111 }, 112 Periodic: &PeriodicConfig{ 113 Enabled: false, 114 }, 115 TaskGroups: []*TaskGroup{ 116 &TaskGroup{ 117 Name: "web", 118 Count: 10, 119 RestartPolicy: &RestartPolicy{ 120 Attempts: 3, 121 Interval: 10 * time.Minute, 122 Delay: 1 * time.Minute, 123 }, 124 Tasks: []*Task{ 125 &Task{ 126 Name: "web", 127 Driver: "exec", 128 Config: map[string]interface{}{ 129 "command": "/bin/date", 130 }, 131 Env: map[string]string{ 132 "FOO": "bar", 133 }, 134 Artifacts: []*TaskArtifact{ 135 { 136 GetterSource: "http://foo.com", 137 }, 138 }, 139 Services: []*Service{ 140 { 141 Name: "${TASK}-frontend", 142 PortLabel: "http", 143 }, 144 }, 145 Resources: &Resources{ 146 CPU: 500, 147 MemoryMB: 256, 148 Networks: []*NetworkResource{ 149 &NetworkResource{ 150 MBits: 50, 151 DynamicPorts: []Port{{Label: "http"}}, 152 }, 153 }, 154 }, 155 }, 156 }, 157 Meta: map[string]string{ 158 "elb_check_type": "http", 159 "elb_check_interval": "30s", 160 "elb_check_min": "3", 161 }, 162 }, 163 }, 164 Meta: map[string]string{ 165 "owner": "armon", 166 }, 167 } 168 } 169 170 func TestJob_Copy(t *testing.T) { 171 j := testJob() 172 c := j.Copy() 173 if !reflect.DeepEqual(j, c) { 174 t.Fatalf("Copy() returned an unequal Job; got %#v; want %#v", c, j) 175 } 176 } 177 178 func TestJob_IsPeriodic(t *testing.T) { 179 j := &Job{ 180 Type: JobTypeService, 181 Periodic: &PeriodicConfig{ 182 Enabled: true, 183 }, 184 } 185 if !j.IsPeriodic() { 186 t.Fatalf("IsPeriodic() returned false on periodic job") 187 } 188 189 j = &Job{ 190 Type: JobTypeService, 191 } 192 if j.IsPeriodic() { 193 t.Fatalf("IsPeriodic() returned true on non-periodic job") 194 } 195 } 196 197 func TestTaskGroup_Validate(t *testing.T) { 198 tg := &TaskGroup{ 199 Count: -1, 200 RestartPolicy: &RestartPolicy{ 201 Interval: 5 * time.Minute, 202 Delay: 10 * time.Second, 203 Attempts: 10, 204 Mode: RestartPolicyModeDelay, 205 }, 206 } 207 err := tg.Validate() 208 mErr := err.(*multierror.Error) 209 if !strings.Contains(mErr.Errors[0].Error(), "group name") { 210 t.Fatalf("err: %s", err) 211 } 212 if !strings.Contains(mErr.Errors[1].Error(), "count can't be negative") { 213 t.Fatalf("err: %s", err) 214 } 215 if !strings.Contains(mErr.Errors[2].Error(), "Missing tasks") { 216 t.Fatalf("err: %s", err) 217 } 218 219 tg = &TaskGroup{ 220 Name: "web", 221 Count: 1, 222 Tasks: []*Task{ 223 &Task{Name: "web"}, 224 &Task{Name: "web"}, 225 &Task{}, 226 }, 227 RestartPolicy: &RestartPolicy{ 228 Interval: 5 * time.Minute, 229 Delay: 10 * time.Second, 230 Attempts: 10, 231 Mode: RestartPolicyModeDelay, 232 }, 233 } 234 err = tg.Validate() 235 mErr = err.(*multierror.Error) 236 if !strings.Contains(mErr.Errors[0].Error(), "2 redefines 'web' from task 1") { 237 t.Fatalf("err: %s", err) 238 } 239 if !strings.Contains(mErr.Errors[1].Error(), "Task 3 missing name") { 240 t.Fatalf("err: %s", err) 241 } 242 if !strings.Contains(mErr.Errors[2].Error(), "Task 1 validation failed") { 243 t.Fatalf("err: %s", err) 244 } 245 } 246 247 func TestTask_Validate(t *testing.T) { 248 task := &Task{} 249 err := task.Validate() 250 mErr := err.(*multierror.Error) 251 if !strings.Contains(mErr.Errors[0].Error(), "task name") { 252 t.Fatalf("err: %s", err) 253 } 254 if !strings.Contains(mErr.Errors[1].Error(), "task driver") { 255 t.Fatalf("err: %s", err) 256 } 257 if !strings.Contains(mErr.Errors[2].Error(), "task resources") { 258 t.Fatalf("err: %s", err) 259 } 260 261 task = &Task{ 262 Name: "web", 263 Driver: "docker", 264 Resources: &Resources{ 265 CPU: 100, 266 DiskMB: 200, 267 MemoryMB: 100, 268 IOPS: 10, 269 }, 270 LogConfig: DefaultLogConfig(), 271 } 272 err = task.Validate() 273 if err != nil { 274 t.Fatalf("err: %s", err) 275 } 276 } 277 278 func TestTask_Validate_Services(t *testing.T) { 279 s1 := &Service{ 280 Name: "service-name", 281 PortLabel: "bar", 282 Checks: []*ServiceCheck{ 283 { 284 Name: "check-name", 285 Type: ServiceCheckTCP, 286 }, 287 { 288 Name: "check-name", 289 Type: ServiceCheckTCP, 290 }, 291 }, 292 } 293 294 s2 := &Service{ 295 Name: "service-name", 296 } 297 298 task := &Task{ 299 Name: "web", 300 Driver: "docker", 301 Resources: &Resources{ 302 CPU: 100, 303 DiskMB: 200, 304 MemoryMB: 100, 305 IOPS: 10, 306 }, 307 Services: []*Service{s1, s2}, 308 } 309 err := task.Validate() 310 if err == nil { 311 t.Fatal("expected an error") 312 } 313 if !strings.Contains(err.Error(), "referenced by services service-name does not exist") { 314 t.Fatalf("err: %s", err) 315 } 316 317 if !strings.Contains(err.Error(), "service \"service-name\" is duplicate") { 318 t.Fatalf("err: %v", err) 319 } 320 321 if !strings.Contains(err.Error(), "check \"check-name\" is duplicate") { 322 t.Fatalf("err: %v", err) 323 } 324 } 325 326 func TestTask_Validate_LogConfig(t *testing.T) { 327 task := &Task{ 328 LogConfig: DefaultLogConfig(), 329 Resources: &Resources{ 330 DiskMB: 1, 331 }, 332 } 333 334 err := task.Validate() 335 mErr := err.(*multierror.Error) 336 if !strings.Contains(mErr.Errors[3].Error(), "log storage") { 337 t.Fatalf("err: %s", err) 338 } 339 } 340 341 func TestConstraint_Validate(t *testing.T) { 342 c := &Constraint{} 343 err := c.Validate() 344 mErr := err.(*multierror.Error) 345 if !strings.Contains(mErr.Errors[0].Error(), "Missing constraint operand") { 346 t.Fatalf("err: %s", err) 347 } 348 349 c = &Constraint{ 350 LTarget: "$attr.kernel.name", 351 RTarget: "linux", 352 Operand: "=", 353 } 354 err = c.Validate() 355 if err != nil { 356 t.Fatalf("err: %v", err) 357 } 358 359 // Perform additional regexp validation 360 c.Operand = ConstraintRegex 361 c.RTarget = "(foo" 362 err = c.Validate() 363 mErr = err.(*multierror.Error) 364 if !strings.Contains(mErr.Errors[0].Error(), "missing closing") { 365 t.Fatalf("err: %s", err) 366 } 367 368 // Perform version validation 369 c.Operand = ConstraintVersion 370 c.RTarget = "~> foo" 371 err = c.Validate() 372 mErr = err.(*multierror.Error) 373 if !strings.Contains(mErr.Errors[0].Error(), "Malformed constraint") { 374 t.Fatalf("err: %s", err) 375 } 376 } 377 378 func TestResource_NetIndex(t *testing.T) { 379 r := &Resources{ 380 Networks: []*NetworkResource{ 381 &NetworkResource{Device: "eth0"}, 382 &NetworkResource{Device: "lo0"}, 383 &NetworkResource{Device: ""}, 384 }, 385 } 386 if idx := r.NetIndex(&NetworkResource{Device: "eth0"}); idx != 0 { 387 t.Fatalf("Bad: %d", idx) 388 } 389 if idx := r.NetIndex(&NetworkResource{Device: "lo0"}); idx != 1 { 390 t.Fatalf("Bad: %d", idx) 391 } 392 if idx := r.NetIndex(&NetworkResource{Device: "eth1"}); idx != -1 { 393 t.Fatalf("Bad: %d", idx) 394 } 395 } 396 397 func TestResource_Superset(t *testing.T) { 398 r1 := &Resources{ 399 CPU: 2000, 400 MemoryMB: 2048, 401 DiskMB: 10000, 402 IOPS: 100, 403 } 404 r2 := &Resources{ 405 CPU: 2000, 406 MemoryMB: 1024, 407 DiskMB: 5000, 408 IOPS: 50, 409 } 410 411 if s, _ := r1.Superset(r1); !s { 412 t.Fatalf("bad") 413 } 414 if s, _ := r1.Superset(r2); !s { 415 t.Fatalf("bad") 416 } 417 if s, _ := r2.Superset(r1); s { 418 t.Fatalf("bad") 419 } 420 if s, _ := r2.Superset(r2); !s { 421 t.Fatalf("bad") 422 } 423 } 424 425 func TestResource_Add(t *testing.T) { 426 r1 := &Resources{ 427 CPU: 2000, 428 MemoryMB: 2048, 429 DiskMB: 10000, 430 IOPS: 100, 431 Networks: []*NetworkResource{ 432 &NetworkResource{ 433 CIDR: "10.0.0.0/8", 434 MBits: 100, 435 ReservedPorts: []Port{{"ssh", 22}}, 436 }, 437 }, 438 } 439 r2 := &Resources{ 440 CPU: 2000, 441 MemoryMB: 1024, 442 DiskMB: 5000, 443 IOPS: 50, 444 Networks: []*NetworkResource{ 445 &NetworkResource{ 446 IP: "10.0.0.1", 447 MBits: 50, 448 ReservedPorts: []Port{{"web", 80}}, 449 }, 450 }, 451 } 452 453 err := r1.Add(r2) 454 if err != nil { 455 t.Fatalf("Err: %v", err) 456 } 457 458 expect := &Resources{ 459 CPU: 3000, 460 MemoryMB: 3072, 461 DiskMB: 15000, 462 IOPS: 150, 463 Networks: []*NetworkResource{ 464 &NetworkResource{ 465 CIDR: "10.0.0.0/8", 466 MBits: 150, 467 ReservedPorts: []Port{{"ssh", 22}, {"web", 80}}, 468 }, 469 }, 470 } 471 472 if !reflect.DeepEqual(expect.Networks, r1.Networks) { 473 t.Fatalf("bad: %#v %#v", expect, r1) 474 } 475 } 476 477 func TestResource_Add_Network(t *testing.T) { 478 r1 := &Resources{} 479 r2 := &Resources{ 480 Networks: []*NetworkResource{ 481 &NetworkResource{ 482 MBits: 50, 483 DynamicPorts: []Port{{"http", 0}, {"https", 0}}, 484 }, 485 }, 486 } 487 r3 := &Resources{ 488 Networks: []*NetworkResource{ 489 &NetworkResource{ 490 MBits: 25, 491 DynamicPorts: []Port{{"admin", 0}}, 492 }, 493 }, 494 } 495 496 err := r1.Add(r2) 497 if err != nil { 498 t.Fatalf("Err: %v", err) 499 } 500 err = r1.Add(r3) 501 if err != nil { 502 t.Fatalf("Err: %v", err) 503 } 504 505 expect := &Resources{ 506 Networks: []*NetworkResource{ 507 &NetworkResource{ 508 MBits: 75, 509 DynamicPorts: []Port{{"http", 0}, {"https", 0}, {"admin", 0}}, 510 }, 511 }, 512 } 513 514 if !reflect.DeepEqual(expect.Networks, r1.Networks) { 515 t.Fatalf("bad: %#v %#v", expect.Networks[0], r1.Networks[0]) 516 } 517 } 518 519 func TestEncodeDecode(t *testing.T) { 520 type FooRequest struct { 521 Foo string 522 Bar int 523 Baz bool 524 } 525 arg := &FooRequest{ 526 Foo: "test", 527 Bar: 42, 528 Baz: true, 529 } 530 buf, err := Encode(1, arg) 531 if err != nil { 532 t.Fatalf("err: %v", err) 533 } 534 535 var out FooRequest 536 err = Decode(buf[1:], &out) 537 if err != nil { 538 t.Fatalf("err: %v", err) 539 } 540 541 if !reflect.DeepEqual(arg, &out) { 542 t.Fatalf("bad: %#v %#v", arg, out) 543 } 544 } 545 546 func BenchmarkEncodeDecode(b *testing.B) { 547 job := testJob() 548 549 for i := 0; i < b.N; i++ { 550 buf, err := Encode(1, job) 551 if err != nil { 552 b.Fatalf("err: %v", err) 553 } 554 555 var out Job 556 err = Decode(buf[1:], &out) 557 if err != nil { 558 b.Fatalf("err: %v", err) 559 } 560 } 561 } 562 563 func TestInvalidServiceCheck(t *testing.T) { 564 s := Service{ 565 Name: "service-name", 566 PortLabel: "bar", 567 Checks: []*ServiceCheck{ 568 { 569 Name: "check-name", 570 Type: "lol", 571 }, 572 }, 573 } 574 if err := s.Validate(); err == nil { 575 t.Fatalf("Service should be invalid (invalid type)") 576 } 577 578 s = Service{ 579 Name: "service.name", 580 PortLabel: "bar", 581 } 582 if err := s.Validate(); err == nil { 583 t.Fatalf("Service should be invalid (contains a dot): %v", err) 584 } 585 586 s = Service{ 587 Name: "-my-service", 588 PortLabel: "bar", 589 } 590 if err := s.Validate(); err == nil { 591 t.Fatalf("Service should be invalid (begins with a hyphen): %v", err) 592 } 593 594 s = Service{ 595 Name: "abcdef0123456789-abcdef0123456789-abcdef0123456789-abcdef0123456", 596 PortLabel: "bar", 597 } 598 if err := s.Validate(); err == nil { 599 t.Fatalf("Service should be invalid (too long): %v", err) 600 } 601 602 s = Service{ 603 Name: "service-name", 604 Checks: []*ServiceCheck{ 605 { 606 Name: "check-tcp", 607 Type: ServiceCheckTCP, 608 Interval: 5 * time.Second, 609 Timeout: 2 * time.Second, 610 }, 611 { 612 Name: "check-http", 613 Type: ServiceCheckHTTP, 614 Path: "/foo", 615 Interval: 5 * time.Second, 616 Timeout: 2 * time.Second, 617 }, 618 }, 619 } 620 if err := s.Validate(); err == nil { 621 t.Fatalf("service should be invalid (tcp/http checks with no port): %v", err) 622 } 623 624 s = Service{ 625 Name: "service-name", 626 Checks: []*ServiceCheck{ 627 { 628 Name: "check-script", 629 Type: ServiceCheckScript, 630 Command: "/bin/date", 631 Interval: 5 * time.Second, 632 Timeout: 2 * time.Second, 633 }, 634 }, 635 } 636 if err := s.Validate(); err != nil { 637 t.Fatalf("un-expected error: %v", err) 638 } 639 } 640 641 func TestDistinctCheckID(t *testing.T) { 642 c1 := ServiceCheck{ 643 Name: "web-health", 644 Type: "http", 645 Path: "/health", 646 Interval: 2 * time.Second, 647 Timeout: 3 * time.Second, 648 } 649 c2 := ServiceCheck{ 650 Name: "web-health", 651 Type: "http", 652 Path: "/health1", 653 Interval: 2 * time.Second, 654 Timeout: 3 * time.Second, 655 } 656 657 c3 := ServiceCheck{ 658 Name: "web-health", 659 Type: "http", 660 Path: "/health", 661 Interval: 4 * time.Second, 662 Timeout: 3 * time.Second, 663 } 664 serviceID := "123" 665 c1Hash := c1.Hash(serviceID) 666 c2Hash := c2.Hash(serviceID) 667 c3Hash := c3.Hash(serviceID) 668 669 if c1Hash == c2Hash || c1Hash == c3Hash || c3Hash == c2Hash { 670 t.Fatalf("Checks need to be uniq c1: %s, c2: %s, c3: %s", c1Hash, c2Hash, c3Hash) 671 } 672 673 } 674 675 func TestService_InitFields(t *testing.T) { 676 job := "example" 677 taskGroup := "cache" 678 task := "redis" 679 680 s := Service{ 681 Name: "${TASK}-db", 682 } 683 684 s.InitFields(job, taskGroup, task) 685 if s.Name != "redis-db" { 686 t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name) 687 } 688 689 s.Name = "db" 690 s.InitFields(job, taskGroup, task) 691 if s.Name != "db" { 692 t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name) 693 } 694 695 s.Name = "${JOB}-${TASKGROUP}-${TASK}-db" 696 s.InitFields(job, taskGroup, task) 697 if s.Name != "example-cache-redis-db" { 698 t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name) 699 } 700 701 s.Name = "${BASE}-db" 702 s.InitFields(job, taskGroup, task) 703 if s.Name != "example-cache-redis-db" { 704 t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name) 705 } 706 707 } 708 709 func TestJob_ExpandServiceNames(t *testing.T) { 710 j := &Job{ 711 Name: "my-job", 712 TaskGroups: []*TaskGroup{ 713 &TaskGroup{ 714 Name: "web", 715 Tasks: []*Task{ 716 { 717 Name: "frontend", 718 Services: []*Service{ 719 { 720 Name: "${BASE}-default", 721 }, 722 { 723 Name: "jmx", 724 }, 725 }, 726 }, 727 }, 728 }, 729 &TaskGroup{ 730 Name: "admin", 731 Tasks: []*Task{ 732 { 733 Name: "admin-web", 734 }, 735 }, 736 }, 737 }, 738 } 739 740 j.InitFields() 741 742 service1Name := j.TaskGroups[0].Tasks[0].Services[0].Name 743 if service1Name != "my-job-web-frontend-default" { 744 t.Fatalf("Expected Service Name: %s, Actual: %s", "my-job-web-frontend-default", service1Name) 745 } 746 747 service2Name := j.TaskGroups[0].Tasks[0].Services[1].Name 748 if service2Name != "jmx" { 749 t.Fatalf("Expected Service Name: %s, Actual: %s", "jmx", service2Name) 750 } 751 752 } 753 754 func TestPeriodicConfig_EnabledInvalid(t *testing.T) { 755 // Create a config that is enabled but with no interval specified. 756 p := &PeriodicConfig{Enabled: true} 757 if err := p.Validate(); err == nil { 758 t.Fatal("Enabled PeriodicConfig with no spec or type shouldn't be valid") 759 } 760 761 // Create a config that is enabled, with a spec but no type specified. 762 p = &PeriodicConfig{Enabled: true, Spec: "foo"} 763 if err := p.Validate(); err == nil { 764 t.Fatal("Enabled PeriodicConfig with no spec type shouldn't be valid") 765 } 766 767 // Create a config that is enabled, with a spec type but no spec specified. 768 p = &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron} 769 if err := p.Validate(); err == nil { 770 t.Fatal("Enabled PeriodicConfig with no spec shouldn't be valid") 771 } 772 } 773 774 func TestPeriodicConfig_InvalidCron(t *testing.T) { 775 specs := []string{"foo", "* *", "@foo"} 776 for _, spec := range specs { 777 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec} 778 if err := p.Validate(); err == nil { 779 t.Fatal("Invalid cron spec") 780 } 781 } 782 } 783 784 func TestPeriodicConfig_ValidCron(t *testing.T) { 785 specs := []string{"0 0 29 2 *", "@hourly", "0 0-15 * * *"} 786 for _, spec := range specs { 787 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec} 788 if err := p.Validate(); err != nil { 789 t.Fatal("Passed valid cron") 790 } 791 } 792 } 793 794 func TestPeriodicConfig_NextCron(t *testing.T) { 795 from := time.Date(2009, time.November, 10, 23, 22, 30, 0, time.UTC) 796 specs := []string{"0 0 29 2 * 1980", "*/5 * * * *"} 797 expected := []time.Time{time.Time{}, time.Date(2009, time.November, 10, 23, 25, 0, 0, time.UTC)} 798 for i, spec := range specs { 799 p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec} 800 n := p.Next(from) 801 if expected[i] != n { 802 t.Fatalf("Next(%v) returned %v; want %v", from, n, expected[i]) 803 } 804 } 805 } 806 807 func TestRestartPolicy_Validate(t *testing.T) { 808 // Policy with acceptable restart options passes 809 p := &RestartPolicy{ 810 Mode: RestartPolicyModeFail, 811 Attempts: 0, 812 } 813 if err := p.Validate(); err != nil { 814 t.Fatalf("err: %v", err) 815 } 816 817 // Policy with ambiguous restart options fails 818 p = &RestartPolicy{ 819 Mode: RestartPolicyModeDelay, 820 Attempts: 0, 821 } 822 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "ambiguous") { 823 t.Fatalf("expect ambiguity error, got: %v", err) 824 } 825 826 // Bad policy mode fails 827 p = &RestartPolicy{ 828 Mode: "nope", 829 Attempts: 1, 830 } 831 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "mode") { 832 t.Fatalf("expect mode error, got: %v", err) 833 } 834 835 // Fails when attempts*delay does not fit inside interval 836 p = &RestartPolicy{ 837 Mode: RestartPolicyModeDelay, 838 Attempts: 3, 839 Delay: 5 * time.Second, 840 Interval: time.Second, 841 } 842 if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "can't restart") { 843 t.Fatalf("expect restart interval error, got: %v", err) 844 } 845 } 846 847 func TestAllocation_Index(t *testing.T) { 848 a1 := Allocation{Name: "example.cache[0]"} 849 e1 := 0 850 a2 := Allocation{Name: "ex[123]am123ple.c311ac[123]he12[1][77]"} 851 e2 := 77 852 853 if a1.Index() != e1 || a2.Index() != e2 { 854 t.Fatal() 855 } 856 } 857 858 func TestTaskArtifact_Validate_Source(t *testing.T) { 859 valid := &TaskArtifact{GetterSource: "google.com"} 860 if err := valid.Validate(); err != nil { 861 t.Fatalf("unexpected error: %v", err) 862 } 863 } 864 865 func TestTaskArtifact_Validate_Dest(t *testing.T) { 866 valid := &TaskArtifact{GetterSource: "google.com"} 867 if err := valid.Validate(); err != nil { 868 t.Fatalf("unexpected error: %v", err) 869 } 870 871 valid.RelativeDest = "local/" 872 if err := valid.Validate(); err != nil { 873 t.Fatalf("unexpected error: %v", err) 874 } 875 876 valid.RelativeDest = "local/.." 877 if err := valid.Validate(); err != nil { 878 t.Fatalf("unexpected error: %v", err) 879 } 880 881 valid.RelativeDest = "local/../.." 882 if err := valid.Validate(); err == nil { 883 t.Fatalf("expected error: %v", err) 884 } 885 } 886 887 func TestTaskArtifact_Validate_Checksum(t *testing.T) { 888 cases := []struct { 889 Input *TaskArtifact 890 Err bool 891 }{ 892 { 893 &TaskArtifact{ 894 GetterSource: "foo.com", 895 GetterOptions: map[string]string{ 896 "checksum": "no-type", 897 }, 898 }, 899 true, 900 }, 901 { 902 &TaskArtifact{ 903 GetterSource: "foo.com", 904 GetterOptions: map[string]string{ 905 "checksum": "md5:toosmall", 906 }, 907 }, 908 true, 909 }, 910 { 911 &TaskArtifact{ 912 GetterSource: "foo.com", 913 GetterOptions: map[string]string{ 914 "checksum": "invalid:type", 915 }, 916 }, 917 true, 918 }, 919 } 920 921 for i, tc := range cases { 922 err := tc.Input.Validate() 923 if (err != nil) != tc.Err { 924 t.Fatalf("case %d: %v", i, err) 925 continue 926 } 927 } 928 }