github.com/GoogleCloudPlatform/testgrid@v0.0.174/config/yamlcfg/yaml2proto_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package yamlcfg 18 19 import ( 20 "bytes" 21 "io/ioutil" 22 "os" 23 "reflect" 24 "testing" 25 26 "github.com/GoogleCloudPlatform/testgrid/pb/config" 27 "github.com/google/go-cmp/cmp" 28 ) 29 30 func TestYaml2Proto_IsExternal_And_UseKuberClient_False(t *testing.T) { 31 fakeDefault := `default_test_group: 32 name: default 33 default_dashboard_tab: 34 name: default` 35 yaml := `test_groups: 36 - name: testgroup_1 37 dashboards: 38 - name: dash_1` 39 40 defaults, err := LoadDefaults([]byte(fakeDefault)) 41 if err != nil { 42 t.Fatalf("Convert Error: %v\n Results: %v", err, defaults) 43 } 44 45 var cfg config.Configuration 46 47 if err := Update(&cfg, []byte(yaml), &defaults, false); err != nil { 48 t.Errorf("Convert Error: %v\n", err) 49 } 50 51 for _, testgroup := range cfg.TestGroups { 52 if !testgroup.IsExternal { 53 t.Errorf("IsExternal should always be true!") 54 } 55 56 if !testgroup.UseKubernetesClient { 57 t.Errorf("UseKubernetesClient should always be true!") 58 } 59 } 60 } 61 62 func TestUpdateDefaults_Validity(t *testing.T) { 63 tests := []struct { 64 name string 65 yaml string 66 expectError bool 67 expectedMissing string 68 }{ 69 { 70 name: "Empty file; returns error", 71 yaml: "", 72 expectError: true, 73 expectedMissing: "DefaultTestGroup", 74 }, 75 { 76 name: "Only test group; returns error", 77 yaml: `default_test_group: 78 name: default`, 79 expectError: true, 80 expectedMissing: "DefaultDashboardTab", 81 }, 82 { 83 name: "Malformed YAML; returns error", 84 yaml: "{{{", 85 expectError: true, 86 expectedMissing: "", 87 }, 88 { 89 name: "Set Default", 90 yaml: `default_test_group: 91 name: default 92 default_dashboard_tab: 93 name: default`, 94 expectedMissing: "", 95 }, 96 } 97 98 for _, test := range tests { 99 t.Run(test.name, func(t *testing.T) { 100 _, err := LoadDefaults([]byte(test.yaml)) 101 102 if test.expectError { 103 if err == nil { 104 t.Errorf("Expected error, but got none") 105 } else if test.expectedMissing != "" { 106 e, isMissingFieldError := err.(MissingFieldError) 107 if test.expectedMissing != "" && !isMissingFieldError { 108 t.Errorf("Expected a MissingFieldError, but got %v", err) 109 } else if e.Field != test.expectedMissing { 110 t.Errorf("Unexpected Missing field; got %s, expected %s", e.Field, test.expectedMissing) 111 } 112 } 113 } else { 114 if err != nil { 115 t.Errorf("Unexpected Error: %v", err) 116 } 117 } 118 }) 119 } 120 } 121 122 func TestUpdate_DefaultInherits(t *testing.T) { 123 defaultYaml := `default_test_group: 124 num_columns_recent: 10 125 ignore_skip: true 126 ignore_pending: true 127 default_dashboard_tab: 128 num_columns_recent: 20` 129 130 tests := []struct { 131 name string 132 yaml string 133 expectedTestGroup int32 134 expectedDashTab int32 135 strict bool 136 }{ 137 { 138 name: "Default Settings", 139 yaml: `dashboards: 140 - name: dash_1 141 dashboard_tab: 142 - name: tab_1 143 test_groups: 144 - name: testgroup_1`, 145 expectedTestGroup: 10, 146 expectedDashTab: 20, 147 strict: false, 148 }, 149 { 150 name: "DashboardTab Inheritance", 151 yaml: `dashboards: 152 - name: dash_1 153 dashboard_tab: 154 - name: tab_1 155 num_columns_recent: 3 156 test_groups: 157 - name: testgroup_1`, 158 expectedTestGroup: 10, 159 expectedDashTab: 3, 160 strict: false, 161 }, 162 { 163 name: "TestGroup Inheritance", 164 yaml: `dashboards: 165 - name: dash_1 166 dashboard_tab: 167 - name: tab_1 168 test_groups: 169 - name: testgroup_1 170 num_columns_recent: 4`, 171 expectedTestGroup: 4, 172 expectedDashTab: 20, 173 strict: false, 174 }, 175 } 176 177 for _, test := range tests { 178 t.Run(test.name, func(t *testing.T) { 179 var cfg config.Configuration 180 defaults, err := LoadDefaults([]byte(defaultYaml)) 181 if err != nil { 182 t.Fatalf("Unexpected error with default yaml: %v", err) 183 } 184 185 if err := Update(&cfg, []byte(test.yaml), &defaults, test.strict); err != nil { 186 t.Fatalf("Unexpected error with Update: %v", err) 187 } 188 189 if cfg.TestGroups[0].NumColumnsRecent != test.expectedTestGroup { 190 t.Errorf("Wrong inheritance for TestGroup: got %d, expected %d", 191 cfg.TestGroups[0].NumColumnsRecent, test.expectedTestGroup) 192 } 193 194 if cfg.TestGroups[0].IgnorePending != true { 195 t.Error("Wrong inheritance for TestGroup.IgnorePending: got false, expected true") 196 } 197 198 if cfg.TestGroups[0].IgnoreSkip != true { 199 t.Error("Wrong inheritance for TestGroup.IgnoreSkip: got false, expected true") 200 } 201 202 if cfg.Dashboards[0].DashboardTab[0].NumColumnsRecent != test.expectedDashTab { 203 t.Errorf("Wrong inheritance for Dashboard Tab: got %d, expected %d", 204 cfg.Dashboards[0].DashboardTab[0].NumColumnsRecent, test.expectedDashTab) 205 } 206 207 }) 208 } 209 } 210 211 func Test_UpdateErrors(t *testing.T) { 212 tests := []struct { 213 name string 214 yaml string 215 strict bool 216 }{ 217 { 218 name: "Imbedded defaults fails", 219 yaml: `default_test_group: 220 num_columns_recent: 5 221 default_dashboard_tab: 222 num_columns_recent: 6 223 dashboards: 224 - name: dash_1 225 dashboard_tab: 226 - name: tab_1 227 test_groups: 228 - name: testgroup_1`, 229 strict: true, 230 }, 231 { 232 name: "Invalid field inside dashboard fails", 233 yaml: `dashboards: 234 - name: dash_1 235 dashboard_tab: 236 - name: tab_1 237 - garbage: garbage 238 test_groups: 239 - name: testgroup_1`, 240 strict: true, 241 }, 242 } 243 for _, test := range tests { 244 t.Run(test.name, func(t *testing.T) { 245 var cfg config.Configuration 246 if err := Update(&cfg, []byte(test.yaml), nil, test.strict); err == nil { 247 t.Fatalf("expected an error") 248 } 249 }) 250 } 251 } 252 253 func Test_MarshalYAML(t *testing.T) { 254 tests := []struct { 255 name string 256 input *config.Configuration 257 expected []byte 258 }{ 259 { 260 name: "Nil input; error", 261 input: nil, 262 }, 263 { 264 name: "Empty input; error", 265 input: &config.Configuration{}, 266 }, 267 { 268 name: "Dashboard Tab & Group", 269 input: &config.Configuration{ 270 Dashboards: []*config.Dashboard{ 271 { 272 Name: "dash_1", 273 DashboardTab: []*config.DashboardTab{ 274 { 275 Name: "tab_1", 276 TestGroupName: "testgroup_1", 277 }, 278 }, 279 }, 280 }, 281 TestGroups: []*config.TestGroup{ 282 { 283 Name: "testgroup_1", 284 }, 285 }, 286 }, 287 expected: []byte(`dashboards: 288 - dashboard_tab: 289 - name: tab_1 290 test_group_name: testgroup_1 291 name: dash_1 292 test_groups: 293 - days_of_results: 1 294 gcs_prefix: fake path 295 name: testgroup_1 296 num_columns_recent: 1 297 `), 298 }, 299 { 300 name: "reject empty column headers", 301 input: &config.Configuration{ 302 TestGroups: []*config.TestGroup{ 303 { 304 Name: "test_group", 305 ColumnHeader: []*config.TestGroup_ColumnHeader{ 306 {}, 307 }, 308 }, 309 }, 310 Dashboards: []*config.Dashboard{ 311 { 312 Name: "dash", 313 DashboardTab: []*config.DashboardTab{ 314 { 315 Name: "tab", 316 TestGroupName: "test_group", 317 }, 318 }, 319 }, 320 }, 321 }, 322 }, 323 { 324 name: "reject multiple column header values", 325 input: &config.Configuration{ 326 TestGroups: []*config.TestGroup{ 327 { 328 Name: "test_group", 329 ColumnHeader: []*config.TestGroup_ColumnHeader{ 330 { 331 ConfigurationValue: "yay", 332 Label: "lab", 333 }, 334 }, 335 }, 336 }, 337 Dashboards: []*config.Dashboard{ 338 { 339 Name: "dash", 340 DashboardTab: []*config.DashboardTab{ 341 { 342 Name: "tab", 343 TestGroupName: "test_group", 344 }, 345 }, 346 }, 347 }, 348 }, 349 }, 350 { 351 name: "column headers configuration_value work", 352 input: &config.Configuration{ 353 TestGroups: []*config.TestGroup{ 354 { 355 Name: "test_group", 356 ColumnHeader: []*config.TestGroup_ColumnHeader{ 357 { 358 ConfigurationValue: "yay", 359 }, 360 { 361 Label: "lab", 362 }, 363 { 364 Property: "prop", 365 }, 366 }, 367 }, 368 }, 369 Dashboards: []*config.Dashboard{ 370 { 371 Name: "dash", 372 DashboardTab: []*config.DashboardTab{ 373 { 374 Name: "tab", 375 TestGroupName: "test_group", 376 }, 377 }, 378 }, 379 }, 380 }, 381 expected: []byte(`dashboards: 382 - dashboard_tab: 383 - name: tab 384 test_group_name: test_group 385 name: dash 386 test_groups: 387 - column_header: 388 - configuration_value: yay 389 - label: lab 390 - property: prop 391 days_of_results: 1 392 gcs_prefix: fake path 393 name: test_group 394 num_columns_recent: 1 395 `), 396 }, 397 { 398 name: "name elements work correctly", 399 input: &config.Configuration{ 400 TestGroups: []*config.TestGroup{ 401 { 402 Name: "test_group", 403 TestNameConfig: &config.TestNameConfig{ 404 NameFormat: "labels:%s target_config:%s build_target:%s tags:%s test_property:%s", 405 NameElements: []*config.TestNameConfig_NameElement{ 406 { 407 Labels: "labels", 408 }, 409 { 410 TargetConfig: "target config", 411 }, 412 { 413 BuildTarget: true, 414 }, 415 { 416 Tags: "tags", 417 }, 418 { 419 TestProperty: "test property", 420 }, 421 }, 422 }, 423 }, 424 }, 425 Dashboards: []*config.Dashboard{ 426 { 427 Name: "dash", 428 DashboardTab: []*config.DashboardTab{ 429 { 430 Name: "tab", 431 TestGroupName: "test_group", 432 }, 433 }, 434 }, 435 }, 436 }, 437 expected: []byte(`dashboards: 438 - dashboard_tab: 439 - name: tab 440 test_group_name: test_group 441 name: dash 442 test_groups: 443 - days_of_results: 1 444 gcs_prefix: fake path 445 name: test_group 446 num_columns_recent: 1 447 test_name_config: 448 name_elements: 449 - labels: labels 450 - target_config: target config 451 - build_target: true 452 - tags: tags 453 - test_property: test property 454 name_format: labels:%s target_config:%s build_target:%s tags:%s test_property:%s 455 `), 456 }, 457 { 458 name: "reject unbalanced name format", 459 input: &config.Configuration{ 460 TestGroups: []*config.TestGroup{ 461 { 462 Name: "test_group", 463 TestNameConfig: &config.TestNameConfig{ 464 NameFormat: "one:%s two:%s", 465 NameElements: []*config.TestNameConfig_NameElement{ 466 { 467 Labels: "labels", 468 }, 469 { 470 TargetConfig: "target config", 471 }, 472 { 473 BuildTarget: true, 474 }, 475 }, 476 }, 477 }, 478 }, 479 Dashboards: []*config.Dashboard{ 480 { 481 Name: "dash", 482 DashboardTab: []*config.DashboardTab{ 483 { 484 Name: "tab", 485 TestGroupName: "test_group", 486 }, 487 }, 488 }, 489 }, 490 }, 491 }, 492 { 493 name: "basic group values work", 494 input: &config.Configuration{ 495 TestGroups: []*config.TestGroup{ 496 { 497 Name: "test_group", 498 AlertStaleResultsHours: 5, 499 CodeSearchPath: "github.com/kubernetes/example", 500 IgnorePending: true, 501 IgnoreSkip: true, 502 IsExternal: true, 503 NumFailuresToAlert: 4, 504 NumPassesToDisableAlert: 6, 505 TestsNamePolicy: 2, 506 UseKubernetesClient: true, 507 }, 508 }, 509 Dashboards: []*config.Dashboard{ 510 { 511 Name: "dash", 512 DashboardTab: []*config.DashboardTab{ 513 { 514 Name: "tab", 515 TestGroupName: "test_group", 516 }, 517 }, 518 }, 519 }, 520 }, 521 expected: []byte(`dashboards: 522 - dashboard_tab: 523 - name: tab 524 test_group_name: test_group 525 name: dash 526 test_groups: 527 - alert_stale_results_hours: 5 528 code_search_path: github.com/kubernetes/example 529 days_of_results: 1 530 gcs_prefix: fake path 531 ignore_pending: true 532 ignore_skip: true 533 is_external: true 534 name: test_group 535 num_columns_recent: 1 536 num_failures_to_alert: 4 537 num_passes_to_disable_alert: 6 538 tests_name_policy: 2 539 use_kubernetes_client: true 540 `), 541 }, 542 { 543 name: "basic dashboard values work", 544 input: &config.Configuration{ 545 TestGroups: []*config.TestGroup{ 546 { 547 Name: "test_group", 548 }, 549 }, 550 Dashboards: []*config.Dashboard{ 551 { 552 Name: "dash", 553 DashboardTab: []*config.DashboardTab{ 554 { 555 Name: "tab", 556 TestGroupName: "test_group", 557 AttachBugTemplate: &config.LinkTemplate{ 558 Url: "yes", 559 Options: []*config.LinkOptionsTemplate{}, 560 }, 561 CodeSearchPath: "find", 562 CodeSearchUrlTemplate: &config.LinkTemplate{ 563 Url: "woo", 564 }, 565 FileBugTemplate: &config.LinkTemplate{ 566 Url: "bar", 567 Options: []*config.LinkOptionsTemplate{ 568 { 569 Key: "title", 570 Value: "yay <test-name>", 571 }, 572 { 573 Key: "body", 574 Value: "woo <test-url>", 575 }, 576 }, 577 }, 578 NumColumnsRecent: 10, 579 OpenTestTemplate: &config.LinkTemplate{ 580 Url: "foo", 581 }, 582 OpenBugTemplate: &config.LinkTemplate{ 583 Url: "ugh", 584 }, 585 ResultsText: "wee", 586 ResultsUrlTemplate: &config.LinkTemplate{ 587 Url: "soup", 588 }, 589 }, 590 }, 591 }, 592 }, 593 }, 594 expected: []byte(`dashboards: 595 - dashboard_tab: 596 - attach_bug_template: 597 url: "yes" 598 code_search_path: find 599 code_search_url_template: 600 url: woo 601 file_bug_template: 602 options: 603 - key: title 604 value: yay <test-name> 605 - key: body 606 value: woo <test-url> 607 url: bar 608 name: tab 609 num_columns_recent: 10 610 open_bug_template: 611 url: ugh 612 open_test_template: 613 url: foo 614 results_text: wee 615 results_url_template: 616 url: soup 617 test_group_name: test_group 618 name: dash 619 test_groups: 620 - days_of_results: 1 621 gcs_prefix: fake path 622 name: test_group 623 num_columns_recent: 1 624 `), 625 }, 626 } 627 628 for _, test := range tests { 629 t.Run(test.name, func(t *testing.T) { 630 // Add required TestGroup fields, not validating them in these tests. 631 if len(test.input.GetTestGroups()) != 0 { 632 test.input.GetTestGroups()[0].DaysOfResults = 1 633 test.input.GetTestGroups()[0].NumColumnsRecent = 1 634 test.input.GetTestGroups()[0].GcsPrefix = "fake path" 635 } 636 result, err := MarshalYAML(test.input) 637 if test.expected == nil && err == nil { 638 t.Errorf("Expected error, but got none") 639 } 640 if !bytes.Equal(result, test.expected) { 641 t.Errorf("Expected: %s\n, Got: %s\nError: %v\n", string(test.expected), string(result), err) 642 } 643 }) 644 } 645 } 646 647 func Test_ReadConfig(t *testing.T) { 648 tests := []struct { 649 name string 650 files map[string]string 651 useDir bool 652 expected config.Configuration 653 expectFailure bool 654 }{ 655 { 656 name: "Reads file", 657 files: map[string]string{ 658 "1*.yaml": "dashboards:\n- name: Foo\n", 659 }, 660 expected: config.Configuration{ 661 Dashboards: []*config.Dashboard{ 662 {Name: "Foo"}, 663 }, 664 }, 665 }, 666 { 667 name: "Reads files in directory", 668 files: map[string]string{ 669 "1*.yaml": "dashboards:\n- name: Foo\n", 670 "2*.yaml": "dashboards:\n- name: Bar\n", 671 }, 672 useDir: true, 673 expected: config.Configuration{ 674 Dashboards: []*config.Dashboard{ 675 {Name: "Foo"}, 676 {Name: "Bar"}, 677 }, 678 }, 679 }, 680 { 681 name: "Invalid YAML: fails", 682 files: map[string]string{ 683 "1*.yaml": "gibberish", 684 }, 685 expectFailure: true, 686 }, 687 { 688 name: "Won't read non-YAML", 689 files: map[string]string{ 690 "1*.yml": "dashboards:\n- name: Foo\n", 691 "2*.txt": "dashboards:\n- name: Bar\n", 692 }, 693 expected: config.Configuration{ 694 Dashboards: []*config.Dashboard{ 695 {Name: "Foo"}, 696 }, 697 }, 698 }, 699 } 700 701 for _, test := range tests { 702 t.Run(test.name, func(t *testing.T) { 703 inputs := make([]string, 0) 704 directory, err := ioutil.TempDir("", "") 705 if err != nil { 706 t.Fatalf("Error in creating temporary dir: %v", err) 707 } 708 defer os.RemoveAll(directory) 709 710 for fileName, fileContents := range test.files { 711 file, err := ioutil.TempFile(directory, fileName) 712 if err != nil { 713 t.Fatalf("Error in creating temporary file %s: %v", fileName, err) 714 } 715 if _, err := file.WriteString(fileContents); err != nil { 716 t.Fatalf("Error in writing temporary file %s: %v", fileName, err) 717 } 718 inputs = append(inputs, file.Name()) 719 if err := file.Close(); err != nil { 720 t.Fatalf("Error in closing temporary file %s: %v", fileName, err) 721 } 722 } 723 724 var result config.Configuration 725 var readErr error 726 if test.useDir { 727 result, readErr = ReadConfig([]string{directory}, "", false) 728 } else { 729 result, readErr = ReadConfig(inputs, "", false) 730 } 731 732 if test.expectFailure && readErr == nil { 733 t.Error("Expected error, but got none") 734 } 735 if !test.expectFailure && readErr != nil { 736 t.Errorf("Unexpected error: %v", readErr) 737 } 738 if !test.expectFailure && !reflect.DeepEqual(result, test.expected) { 739 t.Errorf("Mismatched results: got %v, expected %v", result, test.expected) 740 } 741 }) 742 } 743 } 744 745 func Test_getDefaults(t *testing.T) { 746 tests := []struct { 747 name string 748 paths []string 749 want []string 750 err bool 751 }{ 752 { 753 name: "empty paths", 754 }, 755 { 756 name: "simple case", 757 paths: []string{"foo/config.yaml", "foo/default.yaml"}, 758 want: []string{"foo/default.yaml"}, 759 err: false, 760 }, 761 { 762 name: "no defaults", 763 paths: []string{"foo/config.yaml"}, 764 err: false, 765 }, 766 { 767 name: "two defaults", 768 paths: []string{"foo/config.yaml", "foo/default.yml", "foo/default.yaml"}, 769 err: true, 770 }, 771 { 772 name: "multiple defaults", 773 paths: []string{"foo/default.yaml", "bar/default.yaml"}, 774 want: []string{"foo/default.yaml", "bar/default.yaml"}, 775 err: false, 776 }, 777 { 778 name: "subdirs", 779 paths: []string{"foo/bar/default.yaml"}, 780 want: []string{"foo/bar/default.yaml"}, 781 err: false, 782 }, 783 } 784 785 for _, test := range tests { 786 t.Run(test.name, func(t *testing.T) { 787 got, err := getDefaults(test.paths) 788 if test.err && err == nil { 789 t.Fatalf("expected error, but no error was received") 790 } 791 if err != nil && !test.err { 792 t.Fatalf("expected no error, but received error %v", err) 793 } 794 if diff := cmp.Diff(test.want, got); diff != "" { 795 t.Fatalf("returned with incorrect value, %v", diff) 796 } 797 }) 798 } 799 } 800 801 func Test_PathDefault(t *testing.T) { 802 overallDefaults := DefaultConfiguration{ 803 DefaultTestGroup: &config.TestGroup{ 804 NumColumnsRecent: 5, 805 }, 806 } 807 localDefaults := DefaultConfiguration{ 808 DefaultTestGroup: &config.TestGroup{ 809 NumColumnsRecent: 10, 810 }, 811 } 812 813 tests := []struct { 814 name string 815 path string 816 defaultFiles map[string]DefaultConfiguration 817 defaults DefaultConfiguration 818 want DefaultConfiguration 819 }{ 820 { 821 name: "empty works", 822 }, 823 { 824 name: "basically works", 825 path: "foo/config.yaml", 826 defaultFiles: map[string]DefaultConfiguration{ 827 "foo": localDefaults, 828 }, 829 defaults: overallDefaults, 830 want: localDefaults, 831 }, 832 { 833 name: "path not in map uses overall defaults", 834 path: "config.yaml", 835 defaultFiles: map[string]DefaultConfiguration{ 836 "foo": localDefaults, 837 }, 838 defaults: overallDefaults, 839 want: overallDefaults, 840 }, 841 { 842 name: "path in subdirectory of local default uses overall defaults", 843 path: "foo/bar/config.yaml", 844 defaultFiles: map[string]DefaultConfiguration{ 845 "foo": localDefaults, 846 }, 847 defaults: overallDefaults, 848 want: overallDefaults, 849 }, 850 } 851 852 for _, test := range tests { 853 t.Run(test.name, func(t *testing.T) { 854 if got := pathDefault(test.path, test.defaultFiles, test.defaults); test.want != got { 855 t.Fatalf("pathDefault(%s) incorrect; got %v, want %v", test.path, got, test.want) 856 } 857 }) 858 } 859 }