github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/plugins/approve/approvers/owners_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 approvers
    18  
    19  import (
    20  	"path/filepath"
    21  	"reflect"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/sirupsen/logrus"
    26  
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  
    29  	"sigs.k8s.io/prow/pkg/layeredsets"
    30  	"sigs.k8s.io/prow/pkg/plugins/ownersconfig"
    31  )
    32  
    33  const (
    34  	TestSeed = int64(0)
    35  )
    36  
    37  type FakeRepo struct {
    38  	approversMap                 map[string]layeredsets.String
    39  	leafApproversMap             map[string]sets.Set[string]
    40  	noParentOwnersMap            map[string]bool
    41  	autoApproveUnownedSubfolders map[string]bool
    42  }
    43  
    44  func (f FakeRepo) Filenames() ownersconfig.Filenames {
    45  	return ownersconfig.FakeFilenames
    46  }
    47  
    48  func (f FakeRepo) Approvers(path string) layeredsets.String {
    49  	ret := f.approversMap[path]
    50  	if ret.Len() > 0 || path == "" {
    51  		return ret
    52  	}
    53  
    54  	p := filepath.Dir(path)
    55  	if p == "." {
    56  		p = ""
    57  	}
    58  	return f.Approvers(p)
    59  }
    60  
    61  func (f FakeRepo) LeafApprovers(path string) sets.Set[string] {
    62  	ret := f.leafApproversMap[path]
    63  
    64  	if ret.Len() > 0 || path == "" {
    65  		return ret
    66  	}
    67  
    68  	p := filepath.Dir(path)
    69  	if p == "." {
    70  		p = ""
    71  	}
    72  	return f.LeafApprovers(p)
    73  }
    74  
    75  func (f FakeRepo) FindApproverOwnersForFile(path string) string {
    76  	for dir := path; dir != "."; dir = filepath.Dir(dir) {
    77  		if _, ok := f.leafApproversMap[dir]; ok {
    78  			return dir
    79  		}
    80  	}
    81  	return ""
    82  }
    83  
    84  func (f FakeRepo) IsNoParentOwners(path string) bool {
    85  	return f.noParentOwnersMap[path]
    86  }
    87  
    88  func (f FakeRepo) IsAutoApproveUnownedSubfolders(ownerFilePath string) bool {
    89  	return f.autoApproveUnownedSubfolders[ownerFilePath]
    90  }
    91  
    92  func canonicalize(path string) string {
    93  	if path == "." {
    94  		return ""
    95  	}
    96  	return strings.TrimSuffix(path, "/")
    97  }
    98  
    99  func createFakeRepo(leafApproversMap map[string]sets.Set[string], modify ...func(*FakeRepo)) FakeRepo {
   100  	// github doesn't use / at the root
   101  	a := map[string]layeredsets.String{}
   102  	for dir, approvers := range leafApproversMap {
   103  		leafApproversMap[dir] = setToLower(approvers)
   104  		a[dir] = setToLowerMulti(approvers)
   105  		startingPath := dir
   106  		for {
   107  			dir = canonicalize(filepath.Dir(dir))
   108  			if parentApprovers, ok := leafApproversMap[dir]; ok {
   109  				a[startingPath] = a[startingPath].Union(setToLowerMulti(parentApprovers))
   110  			}
   111  			if dir == "" {
   112  				break
   113  			}
   114  		}
   115  	}
   116  
   117  	fr := FakeRepo{approversMap: a, leafApproversMap: leafApproversMap}
   118  	for _, m := range modify {
   119  		m(&fr)
   120  	}
   121  	return fr
   122  }
   123  
   124  func setToLower(s sets.Set[string]) sets.Set[string] {
   125  	lowered := sets.New[string]()
   126  	for _, elem := range sets.List(s) {
   127  		lowered.Insert(strings.ToLower(elem))
   128  	}
   129  	return lowered
   130  }
   131  
   132  func setToLowerMulti(s sets.Set[string]) layeredsets.String {
   133  	lowered := layeredsets.NewString()
   134  	for _, elem := range sets.List(s) {
   135  		lowered.Insert(0, strings.ToLower(elem))
   136  	}
   137  	return lowered
   138  }
   139  
   140  func TestCreateFakeRepo(t *testing.T) {
   141  	rootApprovers := sets.New[string]("Alice", "Bob")
   142  	aApprovers := sets.New[string]("Art", "Anne")
   143  	bApprovers := sets.New[string]("Bill", "Ben", "Barbara")
   144  	cApprovers := sets.New[string]("Chris", "Carol")
   145  	eApprovers := sets.New[string]("Eve", "Erin")
   146  	edcApprovers := eApprovers.Union(cApprovers)
   147  	FakeRepoMap := map[string]sets.Set[string]{
   148  		"":        rootApprovers,
   149  		"a":       aApprovers,
   150  		"b":       bApprovers,
   151  		"c":       cApprovers,
   152  		"a/combo": edcApprovers,
   153  	}
   154  	fakeRepo := createFakeRepo(FakeRepoMap)
   155  
   156  	tests := []struct {
   157  		testName              string
   158  		ownersFile            string
   159  		expectedLeafApprovers sets.Set[string]
   160  		expectedApprovers     sets.Set[string]
   161  	}{
   162  		{
   163  			testName:              "Root Owners",
   164  			ownersFile:            "",
   165  			expectedApprovers:     rootApprovers,
   166  			expectedLeafApprovers: rootApprovers,
   167  		},
   168  		{
   169  			testName:              "A Owners",
   170  			ownersFile:            "a",
   171  			expectedLeafApprovers: aApprovers,
   172  			expectedApprovers:     aApprovers.Union(rootApprovers),
   173  		},
   174  		{
   175  			testName:              "B Owners",
   176  			ownersFile:            "b",
   177  			expectedLeafApprovers: bApprovers,
   178  			expectedApprovers:     bApprovers.Union(rootApprovers),
   179  		},
   180  		{
   181  			testName:              "C Owners",
   182  			ownersFile:            "c",
   183  			expectedLeafApprovers: cApprovers,
   184  			expectedApprovers:     cApprovers.Union(rootApprovers),
   185  		},
   186  		{
   187  			testName:              "Combo Owners",
   188  			ownersFile:            "a/combo",
   189  			expectedLeafApprovers: edcApprovers,
   190  			expectedApprovers:     edcApprovers.Union(aApprovers).Union(rootApprovers),
   191  		},
   192  	}
   193  
   194  	for _, test := range tests {
   195  		calculatedLeafApprovers := fakeRepo.LeafApprovers(test.ownersFile)
   196  		calculatedApprovers := fakeRepo.Approvers(test.ownersFile)
   197  
   198  		test.expectedLeafApprovers = setToLower(test.expectedLeafApprovers)
   199  		if !calculatedLeafApprovers.Equal(test.expectedLeafApprovers) {
   200  			t.Errorf("Failed for test %v.  Expected Leaf Approvers: %v. Actual Leaf Approvers %v", test.testName, test.expectedLeafApprovers, calculatedLeafApprovers)
   201  		}
   202  
   203  		test.expectedApprovers = setToLower(test.expectedApprovers)
   204  		if !calculatedApprovers.Set().Equal(test.expectedApprovers) {
   205  			t.Errorf("Failed for test %v.  Expected Approvers: %v. Actual Approvers %v", test.testName, test.expectedApprovers, calculatedApprovers)
   206  		}
   207  	}
   208  }
   209  
   210  func TestGetLeafApprovers(t *testing.T) {
   211  	rootApprovers := sets.New[string]("Alice", "Bob")
   212  	aApprovers := sets.New[string]("Art", "Anne")
   213  	bApprovers := sets.New[string]("Bill", "Ben", "Barbara")
   214  	dApprovers := sets.New[string]("David", "Dan", "Debbie")
   215  	FakeRepoMap := map[string]sets.Set[string]{
   216  		"":    rootApprovers,
   217  		"a":   aApprovers,
   218  		"b":   bApprovers,
   219  		"a/d": dApprovers,
   220  	}
   221  
   222  	tests := []struct {
   223  		testName    string
   224  		filenames   []string
   225  		expectedMap map[string]sets.Set[string]
   226  	}{
   227  		{
   228  			testName:    "Empty PR",
   229  			filenames:   []string{},
   230  			expectedMap: map[string]sets.Set[string]{},
   231  		},
   232  		{
   233  			testName:    "Single Root File PR",
   234  			filenames:   []string{"kubernetes.go"},
   235  			expectedMap: map[string]sets.Set[string]{"": setToLower(rootApprovers)},
   236  		},
   237  		{
   238  			testName:    "Internal Node File PR",
   239  			filenames:   []string{"a/test.go"},
   240  			expectedMap: map[string]sets.Set[string]{"a": setToLower(aApprovers)},
   241  		},
   242  		{
   243  			testName:  "Two Leaf File PR",
   244  			filenames: []string{"a/d/test.go", "b/test.go"},
   245  			expectedMap: map[string]sets.Set[string]{
   246  				"a/d": setToLower(dApprovers),
   247  				"b":   setToLower(bApprovers)},
   248  		},
   249  		{
   250  			testName:  "Leaf and Parent 2 File PR",
   251  			filenames: []string{"a/test.go", "a/d/test.go"},
   252  			expectedMap: map[string]sets.Set[string]{
   253  				"a": setToLower(aApprovers),
   254  			},
   255  		},
   256  	}
   257  
   258  	for _, test := range tests {
   259  		testOwners := Owners{
   260  			filenames: test.filenames,
   261  			repo:      createFakeRepo(FakeRepoMap),
   262  			seed:      TestSeed,
   263  			log:       logrus.WithField("plugin", "some_plugin"),
   264  		}
   265  		oMap := testOwners.GetLeafApprovers()
   266  		if !reflect.DeepEqual(test.expectedMap, oMap) {
   267  			t.Errorf("Failed for test %v.  Expected Owners: %v. Actual Owners %v", test.testName, test.expectedMap, oMap)
   268  		}
   269  	}
   270  }
   271  func TestGetOwnersSet(t *testing.T) {
   272  	rootApprovers := sets.New[string]("Alice", "Bob")
   273  	aApprovers := sets.New[string]("Art", "Anne")
   274  	bApprovers := sets.New[string]("Bill", "Ben", "Barbara")
   275  	dApprovers := sets.New[string]("David", "Dan", "Debbie")
   276  	FakeRepoMap := map[string]sets.Set[string]{
   277  		"":    rootApprovers,
   278  		"a":   aApprovers,
   279  		"b":   bApprovers,
   280  		"a/d": dApprovers,
   281  	}
   282  
   283  	tests := []struct {
   284  		testName            string
   285  		filenames           []string
   286  		expectedOwnersFiles sets.Set[string]
   287  	}{
   288  		{
   289  			testName:            "Empty PR",
   290  			filenames:           []string{},
   291  			expectedOwnersFiles: sets.New[string](),
   292  		},
   293  		{
   294  			testName:            "Single Root File PR",
   295  			filenames:           []string{"kubernetes.go"},
   296  			expectedOwnersFiles: sets.New[string](""),
   297  		},
   298  		{
   299  			testName:            "Multiple Root File PR",
   300  			filenames:           []string{"test.go", "kubernetes.go"},
   301  			expectedOwnersFiles: sets.New[string](""),
   302  		},
   303  		{
   304  			testName:            "Internal Node File PR",
   305  			filenames:           []string{"a/test.go"},
   306  			expectedOwnersFiles: sets.New[string]("a"),
   307  		},
   308  		{
   309  			testName:            "Two Leaf File PR",
   310  			filenames:           []string{"a/test.go", "b/test.go"},
   311  			expectedOwnersFiles: sets.New[string]("a", "b"),
   312  		},
   313  		{
   314  			testName:            "Leaf and Parent 2 File PR",
   315  			filenames:           []string{"a/test.go", "a/c/test.go"},
   316  			expectedOwnersFiles: sets.New[string]("a"),
   317  		},
   318  	}
   319  
   320  	for _, test := range tests {
   321  		testOwners := Owners{
   322  			filenames: test.filenames,
   323  			repo:      createFakeRepo(FakeRepoMap),
   324  			seed:      TestSeed,
   325  			log:       logrus.WithField("plugin", "some_plugin"),
   326  		}
   327  		oSet := testOwners.GetOwnersSet()
   328  		if !oSet.Equal(test.expectedOwnersFiles) {
   329  			t.Errorf("Failed for test %v.  Expected Owners: %v. Actual Owners %v", test.testName, test.expectedOwnersFiles, oSet)
   330  		}
   331  	}
   332  }
   333  
   334  func TestGetSuggestedApprovers(t *testing.T) {
   335  	var rootApprovers = sets.New[string]("Alice", "Bob")
   336  	var aApprovers = sets.New[string]("Art", "Anne")
   337  	var bApprovers = sets.New[string]("Bill", "Ben", "Barbara")
   338  	var dApprovers = sets.New[string]("David", "Dan", "Debbie")
   339  	var eApprovers = sets.New[string]("Eve", "Erin")
   340  	var edcApprovers = eApprovers.Union(dApprovers)
   341  	var FakeRepoMap = map[string]sets.Set[string]{
   342  		"":        rootApprovers,
   343  		"a":       aApprovers,
   344  		"b":       bApprovers,
   345  		"a/d":     dApprovers,
   346  		"a/combo": edcApprovers,
   347  	}
   348  	tests := []struct {
   349  		testName  string
   350  		filenames []string
   351  		// need at least one person from each set
   352  		expectedOwners []sets.Set[string]
   353  	}{
   354  		{
   355  			testName:       "Empty PR",
   356  			filenames:      []string{},
   357  			expectedOwners: []sets.Set[string]{},
   358  		},
   359  		{
   360  			testName:       "Single Root File PR",
   361  			filenames:      []string{"kubernetes.go"},
   362  			expectedOwners: []sets.Set[string]{setToLower(rootApprovers)},
   363  		},
   364  		{
   365  			testName:       "Internal Node File PR",
   366  			filenames:      []string{"a/test.go"},
   367  			expectedOwners: []sets.Set[string]{setToLower(aApprovers)},
   368  		},
   369  		{
   370  			testName:       "Multiple Files Internal Node File PR",
   371  			filenames:      []string{"a/test.go", "a/test1.go"},
   372  			expectedOwners: []sets.Set[string]{setToLower(aApprovers)},
   373  		},
   374  		{
   375  			testName:       "Two Leaf File PR",
   376  			filenames:      []string{"a/test.go", "b/test.go"},
   377  			expectedOwners: []sets.Set[string]{setToLower(aApprovers), setToLower(bApprovers)},
   378  		},
   379  		{
   380  			testName:       "Leaf and Parent 2 File PR",
   381  			filenames:      []string{"a/test.go", "a/d/test.go"},
   382  			expectedOwners: []sets.Set[string]{setToLower(aApprovers)},
   383  		},
   384  		{
   385  			testName:       "Combo and B",
   386  			filenames:      []string{"a/combo/test.go", "b/test.go"},
   387  			expectedOwners: []sets.Set[string]{setToLower(edcApprovers), setToLower(bApprovers)},
   388  		},
   389  		{
   390  			testName:       "Lowest Leaf",
   391  			filenames:      []string{"a/combo/test.go"},
   392  			expectedOwners: []sets.Set[string]{setToLower(edcApprovers)},
   393  		},
   394  	}
   395  
   396  	for _, test := range tests {
   397  		testOwners := Owners{
   398  			filenames: test.filenames,
   399  			repo:      createFakeRepo(FakeRepoMap),
   400  			seed:      TestSeed,
   401  			log:       logrus.WithField("plugin", "some_plugin"),
   402  		}
   403  		suggested := testOwners.GetSuggestedApprovers(testOwners.GetReverseMap(testOwners.GetLeafApprovers()), testOwners.GetShuffledApprovers())
   404  		for _, ownersSet := range test.expectedOwners {
   405  			if ownersSet.Intersection(suggested).Len() == 0 {
   406  				t.Errorf("Failed for test %v.  Didn't find an approver from: %v. Actual Owners %v", test.testName, ownersSet, suggested)
   407  				t.Errorf("%v", test.filenames)
   408  			}
   409  		}
   410  	}
   411  }
   412  
   413  func TestGetAllPotentialApprovers(t *testing.T) {
   414  	rootApprovers := sets.New[string]("Alice", "Bob")
   415  	aApprovers := sets.New[string]("Art", "Anne")
   416  	bApprovers := sets.New[string]("Bill", "Ben", "Barbara")
   417  	cApprovers := sets.New[string]("Chris", "Carol")
   418  	dApprovers := sets.New[string]("David", "Dan", "Debbie")
   419  	eApprovers := sets.New[string]("Eve", "Erin")
   420  	edcApprovers := eApprovers.Union(dApprovers).Union(cApprovers)
   421  	FakeRepoMap := map[string]sets.Set[string]{
   422  		"":        rootApprovers,
   423  		"a":       aApprovers,
   424  		"b":       bApprovers,
   425  		"c":       cApprovers,
   426  		"a/d":     dApprovers,
   427  		"a/combo": edcApprovers,
   428  	}
   429  	tests := []struct {
   430  		testName  string
   431  		filenames []string
   432  		// use an array because we expected output of this function to be sorted
   433  		expectedApprovers []string
   434  	}{
   435  		{
   436  			testName:          "Empty PR",
   437  			filenames:         []string{},
   438  			expectedApprovers: []string{},
   439  		},
   440  		{
   441  			testName:          "Single Root File PR",
   442  			filenames:         []string{"kubernetes.go"},
   443  			expectedApprovers: sets.List(setToLower(rootApprovers)),
   444  		},
   445  		{
   446  			testName:          "Internal Node File PR",
   447  			filenames:         []string{"a/test.go"},
   448  			expectedApprovers: sets.List(setToLower(aApprovers)),
   449  		},
   450  		{
   451  			testName:          "One Leaf One Internal Node File PR",
   452  			filenames:         []string{"a/test.go", "b/test.go"},
   453  			expectedApprovers: sets.List(setToLower(aApprovers.Union(bApprovers))),
   454  		},
   455  		{
   456  			testName:          "Two Leaf Files PR",
   457  			filenames:         []string{"a/d/test.go", "c/test.go"},
   458  			expectedApprovers: sets.List(setToLower(dApprovers.Union(cApprovers))),
   459  		},
   460  		{
   461  			testName:          "Leaf and Parent 2 File PR",
   462  			filenames:         []string{"a/test.go", "a/combo/test.go"},
   463  			expectedApprovers: sets.List(setToLower(aApprovers)),
   464  		},
   465  		{
   466  			testName:          "Two Leafs",
   467  			filenames:         []string{"a/d/test.go", "b/test.go"},
   468  			expectedApprovers: sets.List(setToLower(dApprovers.Union(bApprovers))),
   469  		},
   470  		{
   471  			testName:          "Lowest Leaf",
   472  			filenames:         []string{"a/combo/test.go"},
   473  			expectedApprovers: sets.List(setToLower(edcApprovers)),
   474  		},
   475  		{
   476  			testName:          "Root And Everything Else PR",
   477  			filenames:         []string{"a/combo/test.go", "b/test.go", "c/test.go", "d/test.go"},
   478  			expectedApprovers: sets.List(setToLower(rootApprovers)),
   479  		},
   480  	}
   481  
   482  	for _, test := range tests {
   483  		testOwners := Owners{
   484  			filenames: test.filenames,
   485  			repo:      createFakeRepo(FakeRepoMap),
   486  			seed:      TestSeed,
   487  			log:       logrus.WithField("plugin", "some_plugin"),
   488  		}
   489  		all := testOwners.GetAllPotentialApprovers()
   490  		if !reflect.DeepEqual(all, test.expectedApprovers) {
   491  			t.Errorf("Failed for test %v.  Didn't correct approvers list.  Expected: %v. Found %v", test.testName, test.expectedApprovers, all)
   492  		}
   493  	}
   494  }
   495  
   496  func TestFindMostCoveringApprover(t *testing.T) {
   497  	rootApprovers := sets.New[string]("Alice", "Bob")
   498  	aApprovers := sets.New[string]("Art", "Anne")
   499  	bApprovers := sets.New[string]("Bill", "Ben", "Barbara")
   500  	cApprovers := sets.New[string]("Chris", "Carol")
   501  	dApprovers := sets.New[string]("David", "Dan", "Debbie")
   502  	eApprovers := sets.New[string]("Eve", "Erin")
   503  	edcApprovers := eApprovers.Union(dApprovers).Union(cApprovers)
   504  	FakeRepoMap := map[string]sets.Set[string]{
   505  		"":        rootApprovers,
   506  		"a":       aApprovers,
   507  		"b":       bApprovers,
   508  		"c":       cApprovers,
   509  		"a/d":     dApprovers,
   510  		"a/combo": edcApprovers,
   511  	}
   512  	tests := []struct {
   513  		testName   string
   514  		filenames  []string
   515  		unapproved sets.Set[string]
   516  		// because most covering could be two or more people
   517  		expectedMostCovering sets.Set[string]
   518  	}{
   519  		{
   520  			testName:             "Empty PR",
   521  			filenames:            []string{},
   522  			unapproved:           sets.Set[string]{},
   523  			expectedMostCovering: sets.New[string](""),
   524  		},
   525  		{
   526  			testName:             "Single Root File PR",
   527  			filenames:            []string{"kubernetes.go"},
   528  			unapproved:           sets.New[string](""),
   529  			expectedMostCovering: setToLower(rootApprovers),
   530  		},
   531  		{
   532  			testName:             "Internal Node File PR",
   533  			filenames:            []string{"a/test.go"},
   534  			unapproved:           sets.New[string]("a"),
   535  			expectedMostCovering: setToLower(aApprovers),
   536  		},
   537  		{
   538  			testName:             "Combo and Intersecting Leaf PR",
   539  			filenames:            []string{"a/combo/test.go", "a/d/test.go"},
   540  			unapproved:           sets.New[string]("a/combo", "a/d"),
   541  			expectedMostCovering: setToLower(edcApprovers.Intersection(dApprovers)),
   542  		},
   543  		{
   544  			testName:             "Three Leaf PR Only B Approved",
   545  			filenames:            []string{"a/combo/test.go", "c/test.go", "b/test.go"},
   546  			unapproved:           sets.New[string]("a/combo", "c/"),
   547  			expectedMostCovering: setToLower(edcApprovers.Intersection(cApprovers)),
   548  		},
   549  		{
   550  			testName:             "Three Leaf PR Only B Left Unapproved",
   551  			filenames:            []string{"a/combo/test.go", "a/d/test.go", "b/test.go"},
   552  			unapproved:           sets.New[string]("b"),
   553  			expectedMostCovering: setToLower(bApprovers),
   554  		},
   555  		{
   556  			testName:             "Leaf and Parent 2 File PR",
   557  			filenames:            []string{"a/test.go", "a/d/test.go"},
   558  			unapproved:           sets.New[string]("a", "a/d"),
   559  			expectedMostCovering: setToLower(aApprovers.Union(dApprovers)),
   560  		},
   561  	}
   562  
   563  	for _, test := range tests {
   564  		testOwners := Owners{
   565  			filenames: test.filenames,
   566  			repo:      createFakeRepo(FakeRepoMap),
   567  			seed:      TestSeed,
   568  			log:       logrus.WithField("plugin", "some_plugin"),
   569  		}
   570  		bestPerson := findMostCoveringApprover(testOwners.GetAllPotentialApprovers(), nil, testOwners.GetReverseMap(testOwners.GetLeafApprovers()), test.unapproved)
   571  		if test.expectedMostCovering.Intersection(sets.New[string](bestPerson)).Len() != 1 {
   572  			t.Errorf("Failed for test %v.  Didn't correct approvers list.  Expected: %v. Found %v", test.testName, test.expectedMostCovering, bestPerson)
   573  		}
   574  
   575  	}
   576  }
   577  
   578  func TestGetReverseMap(t *testing.T) {
   579  	rootApprovers := sets.New[string]("Alice", "Bob")
   580  	aApprovers := sets.New[string]("Art", "Anne")
   581  	cApprovers := sets.New[string]("Chris", "Carol")
   582  	dApprovers := sets.New[string]("David", "Dan", "Debbie")
   583  	eApprovers := sets.New[string]("Eve", "Erin")
   584  	edcApprovers := eApprovers.Union(dApprovers).Union(cApprovers)
   585  	FakeRepoMap := map[string]sets.Set[string]{
   586  		"":        rootApprovers,
   587  		"a":       aApprovers,
   588  		"c":       cApprovers,
   589  		"a/d":     dApprovers,
   590  		"a/combo": edcApprovers,
   591  	}
   592  	tests := []struct {
   593  		testName       string
   594  		filenames      []string
   595  		expectedRevMap map[string]sets.Set[string] // people -> files they can approve
   596  	}{
   597  		{
   598  			testName:       "Empty PR",
   599  			filenames:      []string{},
   600  			expectedRevMap: map[string]sets.Set[string]{},
   601  		},
   602  		{
   603  			testName:  "Single Root File PR",
   604  			filenames: []string{"kubernetes.go"},
   605  			expectedRevMap: map[string]sets.Set[string]{
   606  				"alice": sets.New[string](""),
   607  				"bob":   sets.New[string](""),
   608  			},
   609  		},
   610  		{
   611  			testName:  "Two Leaf PRs",
   612  			filenames: []string{"a/combo/test.go", "a/d/test.go"},
   613  			expectedRevMap: map[string]sets.Set[string]{
   614  				"david":  sets.New[string]("a/d", "a/combo"),
   615  				"dan":    sets.New[string]("a/d", "a/combo"),
   616  				"debbie": sets.New[string]("a/d", "a/combo"),
   617  				"eve":    sets.New[string]("a/combo"),
   618  				"erin":   sets.New[string]("a/combo"),
   619  				"chris":  sets.New[string]("a/combo"),
   620  				"carol":  sets.New[string]("a/combo"),
   621  			},
   622  		},
   623  	}
   624  
   625  	for _, test := range tests {
   626  		testOwners := Owners{
   627  			filenames: test.filenames,
   628  			repo:      createFakeRepo(FakeRepoMap),
   629  			seed:      TestSeed,
   630  			log:       logrus.WithField("plugin", "some_plugin"),
   631  		}
   632  		calculatedRevMap := testOwners.GetReverseMap(testOwners.GetLeafApprovers())
   633  		if !reflect.DeepEqual(calculatedRevMap, test.expectedRevMap) {
   634  			t.Errorf("Failed for test %v.  Didn't find correct reverse map.", test.testName)
   635  			t.Errorf("Person \t\t Expected \t\tFound ")
   636  			// printing the calculated vs expected in a nicer way for debugging
   637  			for k, v := range test.expectedRevMap {
   638  				if calcVal, ok := calculatedRevMap[k]; ok {
   639  					t.Errorf("%v\t\t%v\t\t%v ", k, v, calcVal)
   640  				} else {
   641  					t.Errorf("%v\t\t%v", k, v)
   642  				}
   643  			}
   644  		}
   645  	}
   646  }
   647  
   648  func TestGetShuffledApprovers(t *testing.T) {
   649  	rootApprovers := sets.New[string]("Alice", "Bob")
   650  	aApprovers := sets.New[string]("Art", "Anne")
   651  	bApprovers := sets.New[string]("Bill", "Ben", "Barbara")
   652  	cApprovers := sets.New[string]("Chris", "Carol")
   653  	dApprovers := sets.New[string]("David", "Dan", "Debbie")
   654  	eApprovers := sets.New[string]("Eve", "Erin")
   655  	edcApprovers := eApprovers.Union(dApprovers).Union(cApprovers)
   656  	FakeRepoMap := map[string]sets.Set[string]{
   657  		"":        rootApprovers,
   658  		"a":       aApprovers,
   659  		"b":       bApprovers,
   660  		"c":       cApprovers,
   661  		"a/d":     dApprovers,
   662  		"a/combo": edcApprovers,
   663  	}
   664  	tests := []struct {
   665  		testName      string
   666  		filenames     []string
   667  		seed          int64
   668  		expectedOrder []string
   669  	}{
   670  		{
   671  			testName:      "Empty PR",
   672  			filenames:     []string{},
   673  			seed:          0,
   674  			expectedOrder: []string{},
   675  		},
   676  		{
   677  			testName:      "Single Root File PR Approved",
   678  			filenames:     []string{"kubernetes.go"},
   679  			seed:          0,
   680  			expectedOrder: []string{"bob", "alice"},
   681  		},
   682  		{
   683  			testName:      "Combo And B PR",
   684  			filenames:     []string{"a/combo/test.go", "b/test.go"},
   685  			seed:          0,
   686  			expectedOrder: []string{"erin", "bill", "carol", "barbara", "dan", "debbie", "ben", "david", "eve", "chris"},
   687  		},
   688  		{
   689  			testName:      "Combo and D, Seed 0",
   690  			filenames:     []string{"a/combo/test.go", "a/d/test.go"},
   691  			seed:          0,
   692  			expectedOrder: []string{"erin", "dan", "dan", "carol", "david", "debbie", "chris", "debbie", "eve", "david"},
   693  		},
   694  		{
   695  			testName:      "Combo and D, Seed 2",
   696  			filenames:     []string{"a/combo/test.go", "a/d/test.go"},
   697  			seed:          2,
   698  			expectedOrder: []string{"dan", "carol", "debbie", "dan", "erin", "chris", "eve", "david", "debbie", "david"},
   699  		},
   700  	}
   701  
   702  	for _, test := range tests {
   703  		testOwners := Owners{
   704  			filenames: test.filenames,
   705  			repo:      createFakeRepo(FakeRepoMap),
   706  			seed:      test.seed,
   707  			log:       logrus.WithField("plugin", "some_plugin"),
   708  		}
   709  		calculated := testOwners.GetShuffledApprovers()
   710  		if !reflect.DeepEqual(test.expectedOrder, calculated) {
   711  			t.Errorf("Failed for test %v.  Expected unapproved files: %v. Found %v", test.testName, test.expectedOrder, calculated)
   712  		}
   713  	}
   714  }
   715  
   716  func TestRemoveSubdirs(t *testing.T) {
   717  	tests := []struct {
   718  		testName       string
   719  		directories    sets.Set[string]
   720  		noParentOwners map[string]bool
   721  
   722  		expected sets.Set[string]
   723  	}{
   724  		{
   725  			testName:    "Empty PR",
   726  			directories: sets.New[string](),
   727  			expected:    sets.New[string](),
   728  		},
   729  		{
   730  			testName:    "Root and One Level Below PR",
   731  			directories: sets.New[string]("", "a/"),
   732  			expected:    sets.New[string](""),
   733  		},
   734  		{
   735  			testName:    "Two Separate Branches",
   736  			directories: sets.New[string]("a/", "c/"),
   737  			expected:    sets.New[string]("a/", "c/"),
   738  		},
   739  		{
   740  			testName:    "Lots of Branches and Leaves",
   741  			directories: sets.New[string]("a", "a/combo", "a/d", "b", "c"),
   742  			expected:    sets.New[string]("a", "b", "c"),
   743  		},
   744  		{
   745  			testName:       "NoParentOwners",
   746  			directories:    sets.New[string]("a", "a/combo"),
   747  			noParentOwners: map[string]bool{"a/combo": true},
   748  			expected:       sets.New[string]("a", "a/combo"),
   749  		},
   750  		{
   751  			testName:       "NoParentOwners in relative path",
   752  			directories:    sets.New[string]("a", "a/b/combo"),
   753  			noParentOwners: map[string]bool{"a/b": true},
   754  			expected:       sets.New[string]("a", "a/b/combo"),
   755  		},
   756  		{
   757  			testName:       "NoParentOwners with child",
   758  			directories:    sets.New[string]("a", "a/b", "a/b/combo"),
   759  			noParentOwners: map[string]bool{"a/b": true},
   760  			expected:       sets.New[string]("a", "a/b"),
   761  		},
   762  	}
   763  
   764  	for _, test := range tests {
   765  		if test.noParentOwners == nil {
   766  			test.noParentOwners = map[string]bool{}
   767  		}
   768  		o := &Owners{repo: FakeRepo{noParentOwnersMap: test.noParentOwners}}
   769  		o.removeSubdirs(test.directories)
   770  		if !reflect.DeepEqual(test.expected, test.directories) {
   771  			t.Errorf("Failed to remove subdirectories for test %v.  Expected files: %q. Found %q", test.testName, sets.List(test.expected), sets.List(test.directories))
   772  
   773  		}
   774  	}
   775  }