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  }