@@ -18,6 +18,7 @@ package repoowners
1818
1919import (
2020 "fmt"
21+ "github.com/stretchr/testify/assert"
2122 "path/filepath"
2223 "reflect"
2324 "regexp"
@@ -58,6 +59,7 @@ reviewers:
5859- jakub
5960required_reviewers:
6061- ben
62+ minimum_reviewers: 2
6163labels:
6264- src-code` ),
6365 "src/dir/conformance/OWNERS" : []byte (`options:
@@ -331,6 +333,7 @@ func TestLoadRepoOwners(t *testing.T) {
331333 extraBranchesAndFiles map [string ]map [string ][]byte
332334
333335 expectedApprovers , expectedReviewers , expectedRequiredReviewers , expectedLabels map [string ]map [string ]sets.String
336+ expectedMinRequiredReviewers map [string ]map [string ]int
334337
335338 expectedOptions map [string ]dirOptions
336339 }{
@@ -354,6 +357,9 @@ func TestLoadRepoOwners(t *testing.T) {
354357 "" : patternAll ("EVERYTHING" ),
355358 "src/dir" : patternAll ("src-code" ),
356359 },
360+ expectedMinRequiredReviewers : map [string ]map [string ]int {
361+ "src/dir" : {"" : 2 },
362+ },
357363 expectedOptions : map [string ]dirOptions {
358364 "src/dir/conformance" : {
359365 NoParentOwners : true ,
@@ -381,6 +387,9 @@ func TestLoadRepoOwners(t *testing.T) {
381387 "" : patternAll ("EVERYTHING" ),
382388 "src/dir" : patternAll ("src-code" ),
383389 },
390+ expectedMinRequiredReviewers : map [string ]map [string ]int {
391+ "src/dir" : {"" : 2 },
392+ },
384393 expectedOptions : map [string ]dirOptions {
385394 "src/dir/conformance" : {
386395 NoParentOwners : true ,
@@ -411,6 +420,9 @@ func TestLoadRepoOwners(t *testing.T) {
411420 "src/dir" : patternAll ("src-code" ),
412421 "docs/file.md" : patternAll ("docs" ),
413422 },
423+ expectedMinRequiredReviewers : map [string ]map [string ]int {
424+ "src/dir" : {"" : 2 },
425+ },
414426 expectedOptions : map [string ]dirOptions {
415427 "src/dir/conformance" : {
416428 NoParentOwners : true ,
@@ -444,6 +456,9 @@ func TestLoadRepoOwners(t *testing.T) {
444456 "" : patternAll ("EVERYTHING" ),
445457 "src/dir" : patternAll ("src-code" ),
446458 },
459+ expectedMinRequiredReviewers : map [string ]map [string ]int {
460+ "src/dir" : {"" : 2 },
461+ },
447462 expectedOptions : map [string ]dirOptions {
448463 "src/dir/conformance" : {
449464 NoParentOwners : true ,
@@ -476,6 +491,9 @@ func TestLoadRepoOwners(t *testing.T) {
476491 "" : patternAll ("EVERYTHING" ),
477492 "src/dir" : patternAll ("src-code" ),
478493 },
494+ expectedMinRequiredReviewers : map [string ]map [string ]int {
495+ "src/dir" : {"" : 2 },
496+ },
479497 expectedOptions : map [string ]dirOptions {
480498 "src/dir/conformance" : {
481499 NoParentOwners : true ,
@@ -503,6 +521,9 @@ func TestLoadRepoOwners(t *testing.T) {
503521 "" : patternAll ("EVERYTHING" ),
504522 "src/dir" : patternAll ("src-code" ),
505523 },
524+ expectedMinRequiredReviewers : map [string ]map [string ]int {
525+ "src/dir" : {"" : 2 },
526+ },
506527 expectedOptions : map [string ]dirOptions {
507528 "src/dir/conformance" : {
508529 NoParentOwners : true ,
@@ -544,7 +565,7 @@ func TestLoadRepoOwners(t *testing.T) {
544565 continue
545566 }
546567
547- check := func (field string , expected map [string ]map [string ]sets.String , got map [string ]map [* regexp.Regexp ]sets.String ) {
568+ checkStringSet := func (field string , expected map [string ]map [string ]sets.String , got map [string ]map [* regexp.Regexp ]sets.String ) {
548569 converted := map [string ]map [string ]sets.String {}
549570 for path , m := range got {
550571 converted [path ] = map [string ]sets.String {}
@@ -560,10 +581,29 @@ func TestLoadRepoOwners(t *testing.T) {
560581 t .Errorf ("Expected %s to be:\n %+v\n got:\n %+v." , field , expected , converted )
561582 }
562583 }
563- check ("approvers" , test .expectedApprovers , ro .approvers )
564- check ("reviewers" , test .expectedReviewers , ro .reviewers )
565- check ("required_reviewers" , test .expectedRequiredReviewers , ro .requiredReviewers )
566- check ("labels" , test .expectedLabels , ro .labels )
584+
585+ checkInt := func (field string , expected map [string ]map [string ]int , got map [string ]map [* regexp.Regexp ]int ) {
586+ converted := map [string ]map [string ]int {}
587+ for path , m := range got {
588+ converted [path ] = map [string ]int {}
589+ for re , s := range m {
590+ var pattern string
591+ if re != nil {
592+ pattern = re .String ()
593+ }
594+ converted [path ][pattern ] = s
595+ }
596+ }
597+ if ! reflect .DeepEqual (expected , converted ) {
598+ t .Errorf ("Expected %s to be:\n %+v\n got:\n %+v." , field , expected , converted )
599+ }
600+ }
601+
602+ checkStringSet ("approvers" , test .expectedApprovers , ro .approvers )
603+ checkStringSet ("reviewers" , test .expectedReviewers , ro .reviewers )
604+ checkStringSet ("required_reviewers" , test .expectedRequiredReviewers , ro .requiredReviewers )
605+ checkStringSet ("labels" , test .expectedLabels , ro .labels )
606+ checkInt ("min_required_reviewers" , test .expectedMinRequiredReviewers , ro .minimumReviewers )
567607 if ! reflect .DeepEqual (test .expectedOptions , ro .options ) {
568608 t .Errorf ("Expected options to be:\n %#v\n got:\n %#v." , test .expectedOptions , ro .options )
569609 }
@@ -858,3 +898,117 @@ func TestExpandAliases(t *testing.T) {
858898 }
859899 }
860900}
901+
902+ func TestMinimumReviewersForFile (t * testing.T ) {
903+ const (
904+ // No min reviewers
905+ baseDir = ""
906+ // Min reviewers set to 1
907+ secondDir = "a"
908+ // Min reviewers set to 2
909+ thirdDir = "a/b"
910+ // Min reviewers set to 3
911+ fourthDir = "a/b/c"
912+ // Dir with NoParentOwners - no min reviewers set, should not inherit from parent
913+ noParentDir = "a/b/noparent"
914+ // Dir with regex filters
915+ filterDir = "filtered"
916+ )
917+
918+ goFileRegex := regexp .MustCompile (`\.go$` )
919+ mdFileRegex := regexp .MustCompile (`\.md$` )
920+ // Overlapping regexes - both match main.go
921+ mainFileRegex := regexp .MustCompile (`^main` )
922+ allFilesRegex := regexp .MustCompile (`.*` )
923+
924+ ro := & RepoOwners {
925+ minimumReviewers : map [string ]map [* regexp.Regexp ]int {
926+ secondDir : {nil : 1 },
927+ thirdDir : {nil : 2 },
928+ fourthDir : {nil : 3 },
929+ filterDir : {
930+ goFileRegex : 3 ,
931+ mdFileRegex : 1 ,
932+ nil : 2 , // default
933+ },
934+ // Dir with overlapping regex patterns - should take max
935+ "overlap" : {
936+ mainFileRegex : 2 , // matches main.go
937+ allFilesRegex : 4 , // also matches main.go - should take this (max)
938+ nil : 1 , // default
939+ },
940+ },
941+ options : map [string ]dirOptions {
942+ noParentDir : {
943+ NoParentOwners : true ,
944+ },
945+ },
946+ }
947+ testCases := []struct {
948+ name string
949+ filePath string
950+ expectedRequiredApprovers int
951+ }{
952+ {
953+ name : "Modified Base Dir" ,
954+ filePath : filepath .Join (baseDir , "main.go" ),
955+ expectedRequiredApprovers : 1 ,
956+ },
957+ {
958+ name : "Modified Second Dir" ,
959+ filePath : filepath .Join (secondDir , "main.go" ),
960+ expectedRequiredApprovers : ro.minimumReviewers [secondDir ][nil ],
961+ },
962+ {
963+ name : "Modified Third Dir" ,
964+ filePath : filepath .Join (thirdDir , "main.go" ),
965+ expectedRequiredApprovers : ro.minimumReviewers [thirdDir ][nil ],
966+ },
967+ {
968+ name : "Modified Nested Dir Without OWNERS (default to fourth dir)" ,
969+ filePath : filepath .Join (fourthDir , "d" , "main.go" ),
970+ expectedRequiredApprovers : ro.minimumReviewers [fourthDir ][nil ],
971+ },
972+ {
973+ name : "Modified Nonexistent Dir (default to Base Dir)" ,
974+ filePath : filepath .Join ("nonexistent" , "main.go" ),
975+ expectedRequiredApprovers : 1 ,
976+ },
977+ {
978+ name : "NoParentOwners stops inheritance - should return default 1" ,
979+ filePath : filepath .Join (noParentDir , "main.go" ),
980+ expectedRequiredApprovers : 1 ,
981+ },
982+ {
983+ name : "Regex filter matches .go file" ,
984+ filePath : filepath .Join (filterDir , "main.go" ),
985+ expectedRequiredApprovers : 3 ,
986+ },
987+ {
988+ name : "Regex filter matches .md file" ,
989+ filePath : filepath .Join (filterDir , "README.md" ),
990+ expectedRequiredApprovers : 1 ,
991+ },
992+ {
993+ name : "Regex filter no match uses default" ,
994+ filePath : filepath .Join (filterDir , "data.txt" ),
995+ expectedRequiredApprovers : 2 ,
996+ },
997+ {
998+ name : "Overlapping regex patterns takes max value" ,
999+ filePath : filepath .Join ("overlap" , "main.go" ),
1000+ expectedRequiredApprovers : 4 , // both mainFileRegex(2) and allFilesRegex(4) match, should take max
1001+ },
1002+ {
1003+ name : "Overlapping regex - only one matches" ,
1004+ filePath : filepath .Join ("overlap" , "other.go" ),
1005+ expectedRequiredApprovers : 4 , // only allFilesRegex matches
1006+ },
1007+ }
1008+ for _ , tc := range testCases {
1009+ t .Run (tc .name , func (t * testing.T ) {
1010+ actual := ro .MinimumReviewersForFile (tc .filePath )
1011+ assert .Equal (t , tc .expectedRequiredApprovers , actual )
1012+ })
1013+ }
1014+ }
0 commit comments