github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/prow/repoowners/repoowners_test.go (about)

     1  /*
     2  Copyright 2017 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 repoowners
    18  
    19  import (
    20  	"fmt"
    21  	"path/filepath"
    22  	"reflect"
    23  	"regexp"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/sirupsen/logrus"
    28  	"k8s.io/apimachinery/pkg/util/sets"
    29  
    30  	prowConf "k8s.io/test-infra/prow/config"
    31  	"k8s.io/test-infra/prow/git/localgit"
    32  	"k8s.io/test-infra/prow/github/fakegithub"
    33  )
    34  
    35  var (
    36  	testFiles = map[string][]byte{
    37  		"foo": []byte(`approvers:
    38  - bob`),
    39  		"OWNERS": []byte(`approvers:
    40  - cjwagner
    41  reviewers:
    42  - Alice
    43  - bob
    44  required_reviewers:
    45  - chris
    46  labels:
    47  - EVERYTHING`),
    48  		"src/OWNERS": []byte(`approvers:
    49  - Best-Approvers`),
    50  		"src/dir/OWNERS": []byte(`approvers:
    51  - bob
    52  reviewers:
    53  - alice
    54  - CJWagner
    55  - jakub
    56  required_reviewers:
    57  - ben
    58  labels:
    59  - src-code`),
    60  		"src/dir/conformance/OWNERS": []byte(`options:
    61    no_parent_owners: true
    62  approvers:
    63  - mml`),
    64  		"docs/file.md": []byte(`---
    65  approvers:
    66  - ALICE
    67  
    68  labels:
    69  - docs
    70  ---`),
    71  	}
    72  
    73  	testFilesRe = map[string][]byte{
    74  		// regexp filtered
    75  		"re/OWNERS": []byte(`filters:
    76    ".*":
    77      labels:
    78      - re/all
    79    "\\.go$":
    80      labels:
    81      - re/go`),
    82  		"re/a/OWNERS": []byte(`filters:
    83    "\\.md$":
    84      labels:
    85      - re/md-in-a
    86    "\\.go$":
    87      labels:
    88      - re/go-in-a`),
    89  	}
    90  )
    91  
    92  // regexpAll is used to construct a default {regexp -> values} mapping for ".*"
    93  func regexpAll(values ...string) map[*regexp.Regexp]sets.String {
    94  	return map[*regexp.Regexp]sets.String{nil: sets.NewString(values...)}
    95  }
    96  
    97  // patternAll is used to construct a default {regexp string -> values} mapping for ".*"
    98  func patternAll(values ...string) map[string]sets.String {
    99  	// use "" to represent nil and distinguish it from a ".*" regexp (which shouldn't exist).
   100  	return map[string]sets.String{"": sets.NewString(values...)}
   101  }
   102  
   103  type testConfigGetter struct {
   104  	defaultBlacklist []string
   105  	repoBlacklist    map[string][]string
   106  }
   107  
   108  func (c testConfigGetter) Config() *prowConf.Config {
   109  	return &prowConf.Config{
   110  		ProwConfig: prowConf.ProwConfig{
   111  			OwnersDirBlacklist: prowConf.OwnersDirBlacklist{
   112  				Repos:   c.repoBlacklist,
   113  				Default: c.defaultBlacklist,
   114  			},
   115  		},
   116  	}
   117  }
   118  
   119  func getTestClient(
   120  	files map[string][]byte,
   121  	enableMdYaml,
   122  	skipCollab,
   123  	includeAliases bool,
   124  	ownersDirBlacklistDefault []string,
   125  	ownersDirBlacklistByRepo map[string][]string,
   126  	extraBranchesAndFiles map[string]map[string][]byte,
   127  ) (*Client, func(), error) {
   128  	testAliasesFile := map[string][]byte{
   129  		"OWNERS_ALIASES": []byte("aliases:\n  Best-approvers:\n  - carl\n  - cjwagner\n  best-reviewers:\n  - Carl\n  - BOB"),
   130  	}
   131  
   132  	localGit, git, err := localgit.New()
   133  	if err != nil {
   134  		return nil, nil, err
   135  	}
   136  	if err := localGit.MakeFakeRepo("org", "repo"); err != nil {
   137  		return nil, nil, fmt.Errorf("cannot make fake repo: %v", err)
   138  	}
   139  	if err := localGit.AddCommit("org", "repo", files); err != nil {
   140  		return nil, nil, fmt.Errorf("cannot add initial commit: %v", err)
   141  	}
   142  	if includeAliases {
   143  		if err := localGit.AddCommit("org", "repo", testAliasesFile); err != nil {
   144  			return nil, nil, fmt.Errorf("cannot add OWNERS_ALIASES commit: %v", err)
   145  		}
   146  	}
   147  	if len(extraBranchesAndFiles) > 0 {
   148  		for branch, extraFiles := range extraBranchesAndFiles {
   149  			if err := localGit.CheckoutNewBranch("org", "repo", branch); err != nil {
   150  				return nil, nil, err
   151  			}
   152  			if len(extraFiles) > 0 {
   153  				if err := localGit.AddCommit("org", "repo", extraFiles); err != nil {
   154  					return nil, nil, fmt.Errorf("cannot add commit: %v", err)
   155  				}
   156  			}
   157  		}
   158  		if err := localGit.Checkout("org", "repo", "master"); err != nil {
   159  			return nil, nil, err
   160  		}
   161  	}
   162  
   163  	return &Client{
   164  			git:    git,
   165  			ghc:    &fakegithub.FakeClient{Collaborators: []string{"cjwagner", "k8s-ci-robot", "alice", "bob", "carl", "mml", "maggie"}},
   166  			logger: logrus.WithField("client", "repoowners"),
   167  			cache:  make(map[string]cacheEntry),
   168  
   169  			mdYAMLEnabled: func(org, repo string) bool {
   170  				return enableMdYaml
   171  			},
   172  			skipCollaborators: func(org, repo string) bool {
   173  				return skipCollab
   174  			},
   175  			configGetter: testConfigGetter{
   176  				repoBlacklist:    ownersDirBlacklistByRepo,
   177  				defaultBlacklist: ownersDirBlacklistDefault,
   178  			},
   179  		},
   180  		// Clean up function
   181  		func() {
   182  			git.Clean()
   183  			localGit.Clean()
   184  		},
   185  		nil
   186  }
   187  
   188  func TestOwnersDirBlacklist(t *testing.T) {
   189  	validatorExcluded := func(t *testing.T, ro *RepoOwners) {
   190  		for dir := range ro.approvers {
   191  			if strings.Contains(dir, "src") {
   192  				t.Errorf("Expected directory %s to be excluded from the approvers map", dir)
   193  			}
   194  		}
   195  		for dir := range ro.reviewers {
   196  			if strings.Contains(dir, "src") {
   197  				t.Errorf("Expected directory %s to be excluded from the reviewers map", dir)
   198  			}
   199  		}
   200  	}
   201  
   202  	validatorIncluded := func(t *testing.T, ro *RepoOwners) {
   203  		approverFound := false
   204  		for dir := range ro.approvers {
   205  			if strings.Contains(dir, "src") {
   206  				approverFound = true
   207  				break
   208  			}
   209  		}
   210  		if !approverFound {
   211  			t.Errorf("Expected to find approvers for a path matching */src/*")
   212  		}
   213  
   214  		reviewerFound := false
   215  		for dir := range ro.reviewers {
   216  			if strings.Contains(dir, "src") {
   217  				reviewerFound = true
   218  				break
   219  			}
   220  		}
   221  		if !reviewerFound {
   222  			t.Errorf("Expected to find reviewers for a path matching */src/*")
   223  		}
   224  	}
   225  
   226  	getRepoOwnersWithBlacklist := func(t *testing.T, defaults []string, byRepo map[string][]string) *RepoOwners {
   227  		client, cleanup, err := getTestClient(testFiles, true, false, true, defaults, byRepo, nil)
   228  		if err != nil {
   229  			t.Fatalf("Error creating test client: %v.", err)
   230  		}
   231  		defer cleanup()
   232  
   233  		ro, err := client.LoadRepoOwners("org", "repo", "master")
   234  		if err != nil {
   235  			t.Fatalf("Unexpected error loading RepoOwners: %v.", err)
   236  		}
   237  
   238  		return ro.(*RepoOwners)
   239  	}
   240  
   241  	type testConf struct {
   242  		blackistDefault []string
   243  		blacklistByRepo map[string][]string
   244  		validator       func(t *testing.T, ro *RepoOwners)
   245  	}
   246  
   247  	tests := map[string]testConf{}
   248  
   249  	tests["blacklist by org"] = testConf{
   250  		blacklistByRepo: map[string][]string{
   251  			"org": {"src"},
   252  		},
   253  		validator: validatorExcluded,
   254  	}
   255  	tests["blacklist by org/repo"] = testConf{
   256  		blacklistByRepo: map[string][]string{
   257  			"org/repo": {"src"},
   258  		},
   259  		validator: validatorExcluded,
   260  	}
   261  	tests["blacklist by default"] = testConf{
   262  		blackistDefault: []string{"src"},
   263  		validator:       validatorExcluded,
   264  	}
   265  	tests["no blacklist setup"] = testConf{
   266  		validator: validatorIncluded,
   267  	}
   268  	tests["blacklist setup but not matching this repo"] = testConf{
   269  		blacklistByRepo: map[string][]string{
   270  			"not_org/not_repo": {"src"},
   271  			"not_org":          {"src"},
   272  		},
   273  		validator: validatorIncluded,
   274  	}
   275  
   276  	for name, conf := range tests {
   277  		t.Run(name, func(t *testing.T) {
   278  			ro := getRepoOwnersWithBlacklist(t, conf.blackistDefault, conf.blacklistByRepo)
   279  			conf.validator(t, ro)
   280  		})
   281  	}
   282  }
   283  
   284  func TestOwnersRegexpFiltering(t *testing.T) {
   285  	tests := map[string]sets.String{
   286  		"re/a/go.go":   sets.NewString("re/all", "re/go", "re/go-in-a"),
   287  		"re/a/md.md":   sets.NewString("re/all", "re/md-in-a"),
   288  		"re/a/txt.txt": sets.NewString("re/all"),
   289  		"re/go.go":     sets.NewString("re/all", "re/go"),
   290  		"re/txt.txt":   sets.NewString("re/all"),
   291  		"re/b/md.md":   sets.NewString("re/all"),
   292  	}
   293  
   294  	client, cleanup, err := getTestClient(testFilesRe, true, false, true, nil, nil, nil)
   295  	if err != nil {
   296  		t.Fatalf("Error creating test client: %v.", err)
   297  	}
   298  	defer cleanup()
   299  
   300  	r, err := client.LoadRepoOwners("org", "repo", "master")
   301  	if err != nil {
   302  		t.Fatalf("Unexpected error loading RepoOwners: %v.", err)
   303  	}
   304  	ro := r.(*RepoOwners)
   305  	t.Logf("labels: %#v\n\n", ro.labels)
   306  	for file, expected := range tests {
   307  		if got := ro.FindLabelsForFile(file); !got.Equal(expected) {
   308  			t.Errorf("For file %q expected labels %q, but got %q.", file, expected.List(), got.List())
   309  		}
   310  	}
   311  }
   312  
   313  func strP(str string) *string {
   314  	return &str
   315  }
   316  
   317  func TestLoadRepoOwners(t *testing.T) {
   318  	tests := []struct {
   319  		name              string
   320  		mdEnabled         bool
   321  		aliasesFileExists bool
   322  		skipCollaborators bool
   323  		// used for testing OWNERS from a branch different from master
   324  		branch                *string
   325  		extraBranchesAndFiles map[string]map[string][]byte
   326  
   327  		expectedApprovers, expectedReviewers, expectedRequiredReviewers, expectedLabels map[string]map[string]sets.String
   328  
   329  		expectedOptions map[string]dirOptions
   330  	}{
   331  		{
   332  			name: "no alias, no md",
   333  			expectedApprovers: map[string]map[string]sets.String{
   334  				"":                    patternAll("cjwagner"),
   335  				"src":                 patternAll(),
   336  				"src/dir":             patternAll("bob"),
   337  				"src/dir/conformance": patternAll("mml"),
   338  			},
   339  			expectedReviewers: map[string]map[string]sets.String{
   340  				"":        patternAll("alice", "bob"),
   341  				"src/dir": patternAll("alice", "cjwagner"),
   342  			},
   343  			expectedRequiredReviewers: map[string]map[string]sets.String{
   344  				"":        patternAll("chris"),
   345  				"src/dir": patternAll("ben"),
   346  			},
   347  			expectedLabels: map[string]map[string]sets.String{
   348  				"":        patternAll("EVERYTHING"),
   349  				"src/dir": patternAll("src-code"),
   350  			},
   351  			expectedOptions: map[string]dirOptions{
   352  				"src/dir/conformance": {
   353  					NoParentOwners: true,
   354  				},
   355  			},
   356  		},
   357  		{
   358  			name:              "alias, no md",
   359  			aliasesFileExists: true,
   360  			expectedApprovers: map[string]map[string]sets.String{
   361  				"":                    patternAll("cjwagner"),
   362  				"src":                 patternAll("carl", "cjwagner"),
   363  				"src/dir":             patternAll("bob"),
   364  				"src/dir/conformance": patternAll("mml"),
   365  			},
   366  			expectedReviewers: map[string]map[string]sets.String{
   367  				"":        patternAll("alice", "bob"),
   368  				"src/dir": patternAll("alice", "cjwagner"),
   369  			},
   370  			expectedRequiredReviewers: map[string]map[string]sets.String{
   371  				"":        patternAll("chris"),
   372  				"src/dir": patternAll("ben"),
   373  			},
   374  			expectedLabels: map[string]map[string]sets.String{
   375  				"":        patternAll("EVERYTHING"),
   376  				"src/dir": patternAll("src-code"),
   377  			},
   378  			expectedOptions: map[string]dirOptions{
   379  				"src/dir/conformance": {
   380  					NoParentOwners: true,
   381  				},
   382  			},
   383  		},
   384  		{
   385  			name:              "alias, md",
   386  			aliasesFileExists: true,
   387  			mdEnabled:         true,
   388  			expectedApprovers: map[string]map[string]sets.String{
   389  				"":                    patternAll("cjwagner"),
   390  				"src":                 patternAll("carl", "cjwagner"),
   391  				"src/dir":             patternAll("bob"),
   392  				"src/dir/conformance": patternAll("mml"),
   393  				"docs/file.md":        patternAll("alice"),
   394  			},
   395  			expectedReviewers: map[string]map[string]sets.String{
   396  				"":        patternAll("alice", "bob"),
   397  				"src/dir": patternAll("alice", "cjwagner"),
   398  			},
   399  			expectedRequiredReviewers: map[string]map[string]sets.String{
   400  				"":        patternAll("chris"),
   401  				"src/dir": patternAll("ben"),
   402  			},
   403  			expectedLabels: map[string]map[string]sets.String{
   404  				"":             patternAll("EVERYTHING"),
   405  				"src/dir":      patternAll("src-code"),
   406  				"docs/file.md": patternAll("docs"),
   407  			},
   408  			expectedOptions: map[string]dirOptions{
   409  				"src/dir/conformance": {
   410  					NoParentOwners: true,
   411  				},
   412  			},
   413  		},
   414  		{
   415  			name:   "OWNERS from non-default branch",
   416  			branch: strP("release-1.10"),
   417  			extraBranchesAndFiles: map[string]map[string][]byte{
   418  				"release-1.10": {
   419  					"src/doc/OWNERS": []byte("approvers:\n - maggie\n"),
   420  				},
   421  			},
   422  			expectedApprovers: map[string]map[string]sets.String{
   423  				"":                    patternAll("cjwagner"),
   424  				"src":                 patternAll(),
   425  				"src/dir":             patternAll("bob"),
   426  				"src/dir/conformance": patternAll("mml"),
   427  				"src/doc":             patternAll("maggie"),
   428  			},
   429  			expectedReviewers: map[string]map[string]sets.String{
   430  				"":        patternAll("alice", "bob"),
   431  				"src/dir": patternAll("alice", "cjwagner"),
   432  			},
   433  			expectedRequiredReviewers: map[string]map[string]sets.String{
   434  				"":        patternAll("chris"),
   435  				"src/dir": patternAll("ben"),
   436  			},
   437  			expectedLabels: map[string]map[string]sets.String{
   438  				"":        patternAll("EVERYTHING"),
   439  				"src/dir": patternAll("src-code"),
   440  			},
   441  			expectedOptions: map[string]dirOptions{
   442  				"src/dir/conformance": {
   443  					NoParentOwners: true,
   444  				},
   445  			},
   446  		},
   447  		{
   448  			name:   "OWNERS from master branch while release branch diverges",
   449  			branch: strP("master"),
   450  			extraBranchesAndFiles: map[string]map[string][]byte{
   451  				"release-1.10": {
   452  					"src/doc/OWNERS": []byte("approvers:\n - maggie\n"),
   453  				},
   454  			},
   455  			expectedApprovers: map[string]map[string]sets.String{
   456  				"":                    patternAll("cjwagner"),
   457  				"src":                 patternAll(),
   458  				"src/dir":             patternAll("bob"),
   459  				"src/dir/conformance": patternAll("mml"),
   460  			},
   461  			expectedReviewers: map[string]map[string]sets.String{
   462  				"":        patternAll("alice", "bob"),
   463  				"src/dir": patternAll("alice", "cjwagner"),
   464  			},
   465  			expectedRequiredReviewers: map[string]map[string]sets.String{
   466  				"":        patternAll("chris"),
   467  				"src/dir": patternAll("ben"),
   468  			},
   469  			expectedLabels: map[string]map[string]sets.String{
   470  				"":        patternAll("EVERYTHING"),
   471  				"src/dir": patternAll("src-code"),
   472  			},
   473  			expectedOptions: map[string]dirOptions{
   474  				"src/dir/conformance": {
   475  					NoParentOwners: true,
   476  				},
   477  			},
   478  		},
   479  		{
   480  			name:              "Skip collaborator checks, use only OWNERS files",
   481  			skipCollaborators: true,
   482  			expectedApprovers: map[string]map[string]sets.String{
   483  				"":                    patternAll("cjwagner"),
   484  				"src":                 patternAll("best-approvers"),
   485  				"src/dir":             patternAll("bob"),
   486  				"src/dir/conformance": patternAll("mml"),
   487  			},
   488  			expectedReviewers: map[string]map[string]sets.String{
   489  				"":        patternAll("alice", "bob"),
   490  				"src/dir": patternAll("alice", "cjwagner", "jakub"),
   491  			},
   492  			expectedRequiredReviewers: map[string]map[string]sets.String{
   493  				"":        patternAll("chris"),
   494  				"src/dir": patternAll("ben"),
   495  			},
   496  			expectedLabels: map[string]map[string]sets.String{
   497  				"":        patternAll("EVERYTHING"),
   498  				"src/dir": patternAll("src-code"),
   499  			},
   500  			expectedOptions: map[string]dirOptions{
   501  				"src/dir/conformance": {
   502  					NoParentOwners: true,
   503  				},
   504  			},
   505  		},
   506  	}
   507  
   508  	for _, test := range tests {
   509  		t.Logf("Running scenario %q", test.name)
   510  		client, cleanup, err := getTestClient(testFiles, test.mdEnabled, test.skipCollaborators, test.aliasesFileExists, nil, nil, test.extraBranchesAndFiles)
   511  		if err != nil {
   512  			t.Errorf("Error creating test client: %v.", err)
   513  			continue
   514  		}
   515  		defer cleanup()
   516  
   517  		base := "master"
   518  		if test.branch != nil {
   519  			base = *test.branch
   520  		}
   521  		r, err := client.LoadRepoOwners("org", "repo", base)
   522  		if err != nil {
   523  			t.Errorf("Unexpected error loading RepoOwners: %v.", err)
   524  			continue
   525  		}
   526  		ro := r.(*RepoOwners)
   527  
   528  		if ro.baseDir == "" {
   529  			t.Errorf("Expected 'baseDir' to be populated.")
   530  			continue
   531  		}
   532  		if (ro.RepoAliases != nil) != test.aliasesFileExists {
   533  			t.Errorf("Expected 'RepoAliases' to be poplulated: %t, but got %t.", test.aliasesFileExists, ro.RepoAliases != nil)
   534  			continue
   535  		}
   536  		if ro.enableMDYAML != test.mdEnabled {
   537  			t.Errorf("Expected 'enableMdYaml' to be: %t, but got %t.", test.mdEnabled, ro.enableMDYAML)
   538  			continue
   539  		}
   540  
   541  		check := func(field string, expected map[string]map[string]sets.String, got map[string]map[*regexp.Regexp]sets.String) {
   542  			converted := map[string]map[string]sets.String{}
   543  			for path, m := range got {
   544  				converted[path] = map[string]sets.String{}
   545  				for re, s := range m {
   546  					var pattern string
   547  					if re != nil {
   548  						pattern = re.String()
   549  					}
   550  					converted[path][pattern] = s
   551  				}
   552  			}
   553  			if !reflect.DeepEqual(expected, converted) {
   554  				t.Errorf("Expected %s to be:\n%+v\ngot:\n%+v.", field, expected, converted)
   555  			}
   556  		}
   557  		check("approvers", test.expectedApprovers, ro.approvers)
   558  		check("reviewers", test.expectedReviewers, ro.reviewers)
   559  		check("required_reviewers", test.expectedRequiredReviewers, ro.requiredReviewers)
   560  		check("labels", test.expectedLabels, ro.labels)
   561  		if !reflect.DeepEqual(test.expectedOptions, ro.options) {
   562  			t.Errorf("Expected options to be:\n%#v\ngot:\n%#v.", test.expectedOptions, ro.options)
   563  		}
   564  	}
   565  }
   566  
   567  func TestLoadRepoAliases(t *testing.T) {
   568  	tests := []struct {
   569  		name string
   570  
   571  		aliasFileExists       bool
   572  		branch                *string
   573  		extraBranchesAndFiles map[string]map[string][]byte
   574  
   575  		expectedRepoAliases RepoAliases
   576  	}{
   577  		{
   578  			name:                "No aliases file",
   579  			aliasFileExists:     false,
   580  			expectedRepoAliases: nil,
   581  		},
   582  		{
   583  			name:            "Normal aliases file",
   584  			aliasFileExists: true,
   585  			expectedRepoAliases: RepoAliases{
   586  				"best-approvers": sets.NewString("carl", "cjwagner"),
   587  				"best-reviewers": sets.NewString("carl", "bob"),
   588  			},
   589  		},
   590  		{
   591  			name: "Aliases file from non-default branch",
   592  
   593  			aliasFileExists: true,
   594  			branch:          strP("release-1.10"),
   595  			extraBranchesAndFiles: map[string]map[string][]byte{
   596  				"release-1.10": {
   597  					"OWNERS_ALIASES": []byte("aliases:\n  Best-approvers:\n  - carl\n  - cjwagner\n  best-reviewers:\n  - Carl\n  - BOB\n  - maggie"),
   598  				},
   599  			},
   600  
   601  			expectedRepoAliases: RepoAliases{
   602  				"best-approvers": sets.NewString("carl", "cjwagner"),
   603  				"best-reviewers": sets.NewString("carl", "bob", "maggie"),
   604  			},
   605  		},
   606  	}
   607  	for _, test := range tests {
   608  		client, cleanup, err := getTestClient(testFiles, false, false, test.aliasFileExists, nil, nil, test.extraBranchesAndFiles)
   609  		if err != nil {
   610  			t.Errorf("[%s] Error creating test client: %v.", test.name, err)
   611  			continue
   612  		}
   613  
   614  		branch := "master"
   615  		if test.branch != nil {
   616  			branch = *test.branch
   617  		}
   618  		got, err := client.LoadRepoAliases("org", "repo", branch)
   619  		if err != nil {
   620  			t.Errorf("[%s] Unexpected error loading RepoAliases: %v.", test.name, err)
   621  			cleanup()
   622  			continue
   623  		}
   624  		if !reflect.DeepEqual(got, test.expectedRepoAliases) {
   625  			t.Errorf("[%s] Expected RepoAliases: %#v, but got: %#v.", test.name, test.expectedRepoAliases, got)
   626  		}
   627  		cleanup()
   628  	}
   629  }
   630  
   631  const (
   632  	baseDir        = ""
   633  	leafDir        = "a/b/c"
   634  	noParentsDir   = "d"
   635  	nonExistentDir = "DELETED_DIR"
   636  )
   637  
   638  func TestGetApprovers(t *testing.T) {
   639  	ro := &RepoOwners{
   640  		approvers: map[string]map[*regexp.Regexp]sets.String{
   641  			baseDir:      regexpAll("alice", "bob"),
   642  			leafDir:      regexpAll("carl", "dave"),
   643  			noParentsDir: regexpAll("mml"),
   644  		},
   645  		options: map[string]dirOptions{
   646  			noParentsDir: {
   647  				NoParentOwners: true,
   648  			},
   649  		},
   650  	}
   651  	tests := []struct {
   652  		name               string
   653  		filePath           string
   654  		expectedOwnersPath string
   655  		expectedLeafOwners sets.String
   656  		expectedAllOwners  sets.String
   657  	}{
   658  		{
   659  			name:               "Modified Base Dir Only",
   660  			filePath:           filepath.Join(baseDir, "testFile.md"),
   661  			expectedOwnersPath: baseDir,
   662  			expectedLeafOwners: ro.approvers[baseDir][nil],
   663  			expectedAllOwners:  ro.approvers[baseDir][nil],
   664  		},
   665  		{
   666  			name:               "Modified Leaf Dir Only",
   667  			filePath:           filepath.Join(leafDir, "testFile.md"),
   668  			expectedOwnersPath: leafDir,
   669  			expectedLeafOwners: ro.approvers[leafDir][nil],
   670  			expectedAllOwners:  ro.approvers[baseDir][nil].Union(ro.approvers[leafDir][nil]),
   671  		},
   672  		{
   673  			name:               "Modified NoParentOwners Dir Only",
   674  			filePath:           filepath.Join(noParentsDir, "testFile.go"),
   675  			expectedOwnersPath: noParentsDir,
   676  			expectedLeafOwners: ro.approvers[noParentsDir][nil],
   677  			expectedAllOwners:  ro.approvers[noParentsDir][nil],
   678  		},
   679  		{
   680  			name:               "Modified Nonexistent Dir (Default to Base)",
   681  			filePath:           filepath.Join(nonExistentDir, "testFile.md"),
   682  			expectedOwnersPath: baseDir,
   683  			expectedLeafOwners: ro.approvers[baseDir][nil],
   684  			expectedAllOwners:  ro.approvers[baseDir][nil],
   685  		},
   686  	}
   687  	for testNum, test := range tests {
   688  		foundLeafApprovers := ro.LeafApprovers(test.filePath)
   689  		foundApprovers := ro.Approvers(test.filePath)
   690  		foundOwnersPath := ro.FindApproverOwnersForFile(test.filePath)
   691  		if !foundLeafApprovers.Equal(test.expectedLeafOwners) {
   692  			t.Errorf("The Leaf Approvers Found Do Not Match Expected For Test %d: %s", testNum, test.name)
   693  			t.Errorf("\tExpected Owners: %v\tFound Owners: %v ", test.expectedLeafOwners, foundLeafApprovers)
   694  		}
   695  		if !foundApprovers.Equal(test.expectedAllOwners) {
   696  			t.Errorf("The Approvers Found Do Not Match Expected For Test %d: %s", testNum, test.name)
   697  			t.Errorf("\tExpected Owners: %v\tFound Owners: %v ", test.expectedAllOwners, foundApprovers)
   698  		}
   699  		if foundOwnersPath != test.expectedOwnersPath {
   700  			t.Errorf("The Owners Path Found Does Not Match Expected For Test %d: %s", testNum, test.name)
   701  			t.Errorf("\tExpected Owners: %v\tFound Owners: %v ", test.expectedOwnersPath, foundOwnersPath)
   702  		}
   703  	}
   704  }
   705  
   706  func TestFindLabelsForPath(t *testing.T) {
   707  	tests := []struct {
   708  		name           string
   709  		path           string
   710  		expectedLabels sets.String
   711  	}{
   712  		{
   713  			name:           "base 1",
   714  			path:           "foo.txt",
   715  			expectedLabels: sets.NewString("sig/godzilla"),
   716  		}, {
   717  			name:           "base 2",
   718  			path:           "./foo.txt",
   719  			expectedLabels: sets.NewString("sig/godzilla"),
   720  		}, {
   721  			name:           "base 3",
   722  			path:           "",
   723  			expectedLabels: sets.NewString("sig/godzilla"),
   724  		}, {
   725  			name:           "base 4",
   726  			path:           ".",
   727  			expectedLabels: sets.NewString("sig/godzilla"),
   728  		}, {
   729  			name:           "leaf 1",
   730  			path:           "a/b/c/foo.txt",
   731  			expectedLabels: sets.NewString("sig/godzilla", "wg/save-tokyo"),
   732  		}, {
   733  			name:           "leaf 2",
   734  			path:           "a/b/foo.txt",
   735  			expectedLabels: sets.NewString("sig/godzilla"),
   736  		},
   737  	}
   738  
   739  	testOwners := &RepoOwners{
   740  		labels: map[string]map[*regexp.Regexp]sets.String{
   741  			baseDir: regexpAll("sig/godzilla"),
   742  			leafDir: regexpAll("wg/save-tokyo"),
   743  		},
   744  	}
   745  	for _, test := range tests {
   746  		got := testOwners.FindLabelsForFile(test.path)
   747  		if !got.Equal(test.expectedLabels) {
   748  			t.Errorf(
   749  				"[%s] Expected labels %q for path %q, but got %q.",
   750  				test.name,
   751  				test.expectedLabels.List(),
   752  				test.path,
   753  				got.List(),
   754  			)
   755  		}
   756  	}
   757  }
   758  
   759  func TestCanonicalize(t *testing.T) {
   760  	tests := []struct {
   761  		name         string
   762  		path         string
   763  		expectedPath string
   764  	}{
   765  		{
   766  			name:         "Empty String",
   767  			path:         "",
   768  			expectedPath: "",
   769  		},
   770  		{
   771  			name:         "Dot (.) as Path",
   772  			path:         ".",
   773  			expectedPath: "",
   774  		},
   775  		{
   776  			name:         "Github Style Input (No Root)",
   777  			path:         "a/b/c/d.txt",
   778  			expectedPath: "a/b/c/d.txt",
   779  		},
   780  		{
   781  			name:         "Preceding Slash and Trailing Slash",
   782  			path:         "/a/b/",
   783  			expectedPath: "/a/b",
   784  		},
   785  		{
   786  			name:         "Trailing Slash",
   787  			path:         "foo/bar/baz/",
   788  			expectedPath: "foo/bar/baz",
   789  		},
   790  	}
   791  	for _, test := range tests {
   792  		if got := canonicalize(test.path); test.expectedPath != got {
   793  			t.Errorf(
   794  				"[%s] Expected the canonical path for %v to be %v.  Found %v instead",
   795  				test.name,
   796  				test.path,
   797  				test.expectedPath,
   798  				got,
   799  			)
   800  		}
   801  	}
   802  }
   803  
   804  var (
   805  	lowerCaseAliases = []byte(`
   806  aliases:
   807    team/t1:
   808      - u1
   809      - u2
   810    team/t2:
   811      - u1
   812      - u3`)
   813  	mixedCaseAliases = []byte(`
   814  aliases:
   815    TEAM/T1:
   816      - U1
   817      - U2`)
   818  )
   819  
   820  func TestExpandAliases(t *testing.T) {
   821  	testAliases := RepoAliases{
   822  		"team/t1": sets.NewString("u1", "u2"),
   823  		"team/t2": sets.NewString("u1", "u3"),
   824  	}
   825  	tests := []struct {
   826  		name             string
   827  		unexpanded       sets.String
   828  		expectedExpanded sets.String
   829  	}{
   830  		{
   831  			name:             "No expansions.",
   832  			unexpanded:       sets.NewString("abc", "def"),
   833  			expectedExpanded: sets.NewString("abc", "def"),
   834  		},
   835  		{
   836  			name:             "One alias to be expanded",
   837  			unexpanded:       sets.NewString("abc", "team/t1"),
   838  			expectedExpanded: sets.NewString("abc", "u1", "u2"),
   839  		},
   840  		{
   841  			name:             "Duplicates inside and outside alias.",
   842  			unexpanded:       sets.NewString("u1", "team/t1"),
   843  			expectedExpanded: sets.NewString("u1", "u2"),
   844  		},
   845  		{
   846  			name:             "Duplicates in multiple aliases.",
   847  			unexpanded:       sets.NewString("u1", "team/t1", "team/t2"),
   848  			expectedExpanded: sets.NewString("u1", "u2", "u3"),
   849  		},
   850  		{
   851  			name:             "Mixed casing in aliases.",
   852  			unexpanded:       sets.NewString("Team/T1"),
   853  			expectedExpanded: sets.NewString("u1", "u2"),
   854  		},
   855  	}
   856  
   857  	for _, test := range tests {
   858  		if got := testAliases.ExpandAliases(test.unexpanded); !test.expectedExpanded.Equal(got) {
   859  			t.Errorf(
   860  				"[%s] Expected %q to expand to %q, but got %q.",
   861  				test.name,
   862  				test.unexpanded.List(),
   863  				test.expectedExpanded.List(),
   864  				got.List(),
   865  			)
   866  		}
   867  	}
   868  }