diff --git a/eng/common/scripts/get-codeowners.ps1 b/eng/common/scripts/get-codeowners.ps1 index 5c2bb9b50ab..afe993d50ba 100644 --- a/eng/common/scripts/get-codeowners.ps1 +++ b/eng/common/scripts/get-codeowners.ps1 @@ -168,7 +168,7 @@ if ($Test) { $azSdkToolsCodeowners = (Resolve-Path "$PSScriptRoot/../../../.github/CODEOWNERS") TestGetCodeowners -targetPath "eng/common/scripts/get-codeowners.ps1" -codeownersFileLocation $azSdkToolsCodeowners -includeNonUserAliases $true -expectReturn @("konrad-jamrozik", "weshaggard", "benbp") - $testCodeowners = (Resolve-Path "$PSScriptRoot/../../../tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/glob_path_CODEOWNERS") + $testCodeowners = (Resolve-Path "$PSScriptRoot/../../../tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/test_CODEOWNERS") TestGetCodeowners -targetPath "tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/InputDir/a.txt" -codeownersFileLocation $testCodeowners -includeNonUserAliases $true -expectReturn @("2star") exit 0 } diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/CodeownersFileTests.cs b/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/CodeownersFileTests.cs index dbfba22914f..e13bbc776b9 100644 --- a/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/CodeownersFileTests.cs +++ b/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/CodeownersFileTests.cs @@ -3,191 +3,190 @@ namespace Azure.Sdk.Tools.CodeOwnersParser.Tests; +/// +/// Please see the comment on: +/// - Azure.Sdk.Tools.CodeOwnersParser.Tests.CodeownersFileTests.testCases +/// [TestFixture] public class CodeownersFileTests { /// - /// A battery of test cases specifying behavior of new logic matching target - /// path to CODEOWNERS entries, and comparing it to existing, legacy logic. - /// - /// The logic that has changed is located in CodeownersFile.GetMatchingCodeownersEntry. - /// - /// In the test case table below, any discrepancy between legacy and new - /// matcher expected matches that doesn't pertain to wildcard matching denotes - /// a potential backward compatibility and/or existing defect in the legacy matcher. + /// A battery of test cases specifying the behavior of logic matching target + /// path to CODEOWNERS paths. /// /// For further details, please see: /// - Class comment for Azure.Sdk.Tools.CodeOwnersParser.MatchedCodeOwnerEntry + /// - Azure.Sdk.Tools.RetrieveCodeOwners.Tests.RetrieveCodeOwnersProgramTests /// - https://github.com/Azure/azure-sdk-tools/issues/2770 /// - https://github.com/Azure/azure-sdk-tools/issues/4859 /// private static readonly TestCase[] testCases = { // @formatter:off - // Path: Expected match: - // Codeowners , Target , Legacy , New - new( "/**" , "a" , false , true ), - new( "/**" , "A" , false , true ), - new( "/**" , "/a" , false , true ), - new( "/**" , "a/" , false , true ), - new( "/**" , "/a/" , false , true ), - new( "/**" , "/a/b" , false , true ), - new( "/**" , "/a/b/" , false , true ), - new( "/**" , "/a/b/c" , false , true ), - new( "/**" , "[" , false , true ), - new( "/**" , "]" , false , true ), - new( "/" , "a" , true , false ), - new( "/" , "A" , true , false ), - new( "/" , "/a" , true , false ), - new( "/" , "a/" , true , false ), - new( "/" , "/a/" , true , false ), - new( "/" , "/a/b" , true , false ), - new( "/a" , "a" , true , true ), - new( "/a" , "A" , true , false ), - new( "/a" , "/a" , true , true ), - new( "/a" , "a/" , true , false ), - new( "/a" , "/a/" , true , false ), - new( "/a" , "/a/b" , true , false ), - new( "/a" , "/a/b/" , true , false ), - new( "/a" , "/a\\ b" , true , false ), - new( "/a" , "/x/a/b" , false , false ), - new( "a" , "a" , true , false ), - new( "a" , "ab" , true , false ), - new( "a" , "ab/" , true , false ), - new( "a" , "/ab/" , true , false ), - new( "a" , "A" , true , false ), - new( "a" , "/a" , true , false ), - new( "a" , "a/" , true , false ), - new( "a" , "/a/" , true , false ), - new( "a" , "/a/b" , true , false ), - new( "a" , "/a/b/" , true , false ), - new( "a" , "/x/a/b" , false , false ), - new( "/a/" , "a" , true , false ), - new( "/a/" , "/a" , true , false ), - new( "/a/" , "a/" , true , true ), - new( "/a/" , "/a/" , true , true ), - new( "/a/" , "/a/b" , true , true ), - new( "/a/" , "/a\\ b" , true , false ), - new( "/a/" , "/a\\ b/" , true , false ), - new( "/a/" , "/a/a\\ b/" , true , true ), - new( "/a/" , "/a/b/" , true , true ), - new( "/a/" , "/A/b/" , true , false ), - new( "/a/" , "/x/a/b" , false , false ), - new( "/a/b/" , "/a" , false , false ), - new( "/a/b/" , "/a/" , false , false ), - new( "/a/b/" , "/a/b" , true , false ), - new( "/a/b/" , "/a/b/" , true , true ), - new( "/a/b/" , "/a/b/c" , true , true ), - new( "/a/b/" , "/a/b/c/" , true , true ), - new( "/a/b/" , "/a/b/c/d" , true , true ), - new( "/a/b" , "/a" , false , false ), - new( "/a/b" , "/a/" , false , false ), - new( "/a/b" , "/a/b" , true , true ), - new( "/a/b" , "/a/b/" , true , false ), - new( "/a/b" , "/a/bc" , true , false ), - new( "/a/b" , "/a/bc/" , true , false ), - new( "/a/b" , "/a/b/c" , true , false ), - new( "/a/b" , "/a/b/c/" , true , false ), - new( "/a/b" , "/a/b/c/d" , true , false ), - new( "/!a" , "!a" , true , false ), - new( "/!a" , "b" , false , false ), - new( "/a[b" , "a[b" , true , false ), - new( "/a]b" , "a]b" , true , false ), - new( "/a?b" , "a?b" , true , false ), - new( "/a?b" , "axb" , false , false ), - new( "/a" , "*" , false , false ), - new( "/*" , "*" , true , false ), - new( "/*" , "a" , false , true ), - new( "/*" , "a/" , false , false ), - new( "/*" , "/a" , false , true ), - new( "/*" , "/a/" , false , false ), - new( "/*" , "a/b" , false , false ), - new( "/*" , "/a/b" , false , false ), - new( "/*" , "[" , false , true ), - new( "/*" , "]" , false , true ), - new( "/*" , "!" , false , true ), - new( "/**" , "!" , false , true ), - new( "/a*" , "a" , false , true ), - new( "/a*" , "a/x" , false , true ), - new( "/a*" , "a/x/d" , false , true ), - new( "/a*" , "ab" , false , true ), - new( "/a*" , "ab/x" , false , true ), - new( "/a*" , "ab/x/d" , false , true ), - new( "/a/**" , "a" , false , false ), - new( "/*/**" , "a" , false , false ), - new( "/*/**" , "a/" , false , false ), - new( "/*/**" , "a/b" , false , false ), - new( "/*/" , "a" , false , false ), - new( "/*/" , "a/" , false , true ), - new( "/*/b" , "a/b" , false , true ), - new( "/**/a" , "a" , false , true ), - new( "/**/a" , "x/ba" , false , false ), - new( "/a/*" , "a" , false , false ), - new( "/a/*" , "a/" , false , true ), - new( "/a/*" , "a/b" , false , true ), - new( "/a/*" , "a/b/" , false , false ), - new( "/a/*" , "a/b/c" , false , false ), - new( "/a/*/" , "a" , false , false ), - new( "/a/*/" , "a/" , false , false ), - new( "/a/*/" , "a/b" , false , false ), - new( "/a/*/" , "a/b/" , false , true ), - new( "/a/*/" , "a/b/c" , false , true ), - new( "/a/**" , "a" , false , false ), - new( "/a/**" , "a/" , false , false ), - new( "/a/**" , "a/b" , false , false ), - new( "/a/**" , "a/b/" , false , false ), - new( "/a/**" , "a/b/c" , false , false ), - new( "/a/**/" , "a" , false , false ), - new( "/a/**/" , "a/" , false , false ), - new( "/a/**/" , "a/b" , false , false ), - new( "/a/**/" , "a/b/" , false , false ), - new( "/a/**/" , "a/b/c" , false , false ), - new( "/**/a/" , "a" , false , false ), - new( "/**/a/" , "a/" , false , true ), - new( "/**/a/" , "a/b" , false , true ), - new( "/**/b/" , "a/b" , false , false ), - new( "/**/b/" , "a/b/" , false , true ), - new( "/**/b/" , "a/c/" , false , false ), - new( "/a/*/b/" , "a/b/" , false , false ), - new( "/a/*/b/" , "a/x/b/" , false , true ), - new( "/a/*/b/" , "a/x/b/c" , false , true ), - new( "/a/*/b/" , "a/x/c" , false , false ), - new( "/a/*/b/" , "a/x/y/b" , false , false ), - new( "/a**b/" , "a/x/y/b" , false , false ), - new( "/a/**/b/" , "a/b" , false , false ), - new( "/a/**/b/" , "a/b/" , false , true ), - new( "/a/**/b/" , "a/x/b/" , false , true ), - new( "/a/**/b/" , "a/x/y/b/" , false , true ), - new( "/a/**/b/" , "a/x/y/c" , false , false ), - new( "/a/**/b/" , "a-b/" , false , false ), - new( "a/*/*" , "a/b" , false , false ), - new( "/a/*/*/d" , "a/b/c/d" , false , true ), - new( "/a/*/*/d" , "a/b/x/c/d" , false , false ), - new( "/a/**/*/d" , "a/b/x/c/d" , false , true ), - new( "*/*/b" , "a/b" , false , false ), - new( "/a*/" , "abc/" , false , true ), - new( "/a*/" , "ab/c/" , false , true ), - new( "/*b*/" , "axbyc/" , false , true ), - new( "/*c/" , "abc/" , false , true ), - new( "/*c/" , "a/abc/" , false , false ), - new( "/a*c/" , "axbyc/" , false , true ), - new( "/a*c/" , "axb/yc/" , false , false ), - new( "/**/*x*/" , "a/b/cxy/d" , false , true ), - new( "/a/*.md" , "a/x.md" , false , true ), - new( "/*/*/*.md" , "a/b/x.md" , false , true ), - new( "/**/*.md" , "a/b.md/x.md" , false , true ), - new( "**/*.md" , "a/b.md/x.md" , false , false ), - new( "/*.md" , "a/md" , false , false ), - new( "/a.*" , "a.b" , false , true ), - new( "/a.*" , "a.b/" , false , true ), - new( "/a.*" , "x/a.b/" , false , false ), - new( "/a.*/" , "a.b" , false , false ), - new( "/a.*/" , "a.b/" , false , true ), - new( "/**/*x*/AB/*/CD" , "a/b/cxy/AB/fff/CD" , false, true ), - new( "/**/*x*/AB/*/CD" , "a/b/cxy/AB/ff/ff/CD" , false, false ), - new( "/**/*x*/AB/**/CD/*" , "a/b/cxy/AB/ff/ff/CD" , false, false ), - new( "/**/*x*/AB/**/CD/*" , "a/b/cxy/AB/ff/ff/CD/" , false, true ), - new( "/**/*x*/AB/**/CD/*" , "a/b/cxy/AB/[]/!!/CD/h" , false, true ), + // Path: Expected match: + // Codeowners , Target , | + new( "/**" , "a" , true ), + new( "/**" , "A" , true ), + new( "/**" , "/a" , true ), + new( "/**" , "a/" , true ), + new( "/**" , "/a/" , true ), + new( "/**" , "/a/b" , true ), + new( "/**" , "/a/b/" , true ), + new( "/**" , "/a/b/c" , true ), + new( "/**" , "[" , true ), + new( "/**" , "]" , true ), + new( "/" , "a" , false ), + new( "/" , "A" , false ), + new( "/" , "/a" , false ), + new( "/" , "a/" , false ), + new( "/" , "/a/" , false ), + new( "/" , "/a/b" , false ), + new( "/a" , "a" , true ), + new( "/a" , "A" , false ), + new( "/a" , "/a" , true ), + new( "/a" , "a/" , false ), + new( "/a" , "/a/" , false ), + new( "/a" , "/a/b" , false ), + new( "/a" , "/a/b/" , false ), + new( "/a" , "/a\\ b" , false ), + new( "/a" , "/x/a/b" , false ), + new( "a" , "a" , false ), + new( "a" , "ab" , false ), + new( "a" , "ab/" , false ), + new( "a" , "/ab/" , false ), + new( "a" , "A" , false ), + new( "a" , "/a" , false ), + new( "a" , "a/" , false ), + new( "a" , "/a/" , false ), + new( "a" , "/a/b" , false ), + new( "a" , "/a/b/" , false ), + new( "a" , "/x/a/b" , false ), + new( "/a/" , "a" , false ), + new( "/a/" , "/a" , false ), + new( "/a/" , "a/" , true ), + new( "/a/" , "/a/" , true ), + new( "/a/" , "/a/b" , true ), + new( "/a/" , "/a\\ b" , false ), + new( "/a/" , "/a\\ b/" , false ), + new( "/a/" , "/a/a\\ b/" , true ), + new( "/a/" , "/a/b/" , true ), + new( "/a/" , "/A/b/" , false ), + new( "/a/" , "/x/a/b" , false ), + new( "/a/b/" , "/a" , false ), + new( "/a/b/" , "/a/" , false ), + new( "/a/b/" , "/a/b" , false ), + new( "/a/b/" , "/a/b/" , true ), + new( "/a/b/" , "/a/b/c" , true ), + new( "/a/b/" , "/a/b/c/" , true ), + new( "/a/b/" , "/a/b/c/d" , true ), + new( "/a/b" , "/a" , false ), + new( "/a/b" , "/a/" , false ), + new( "/a/b" , "/a/b" , true ), + new( "/a/b" , "/a/b/" , false ), + new( "/a/b" , "/a/bc" , false ), + new( "/a/b" , "/a/bc/" , false ), + new( "/a/b" , "/a/b/c" , false ), + new( "/a/b" , "/a/b/c/" , false ), + new( "/a/b" , "/a/b/c/d" , false ), + new( "/!a" , "!a" , false ), + new( "/!a" , "b" , false ), + new( "/a[b" , "a[b" , false ), + new( "/a]b" , "a]b" , false ), + new( "/a?b" , "a?b" , false ), + new( "/a?b" , "axb" , false ), + new( "/a" , "*" , false ), + new( "/*" , "*" , false ), + new( "/*" , "a" , true ), + new( "/*" , "a/" , false ), + new( "/*" , "/a" , true ), + new( "/*" , "/a/" , false ), + new( "/*" , "a/b" , false ), + new( "/*" , "/a/b" , false ), + new( "/*" , "[" , true ), + new( "/*" , "]" , true ), + new( "/*" , "!" , true ), + new( "/**" , "!" , true ), + new( "/a*" , "a" , true ), + new( "/a*" , "a/x" , true ), + new( "/a*" , "a/x/d" , true ), + new( "/a*" , "ab" , true ), + new( "/a*" , "ab/x" , true ), + new( "/a*" , "ab/x/d" , true ), + new( "/a/**" , "a" , false ), + new( "/*/**" , "a" , false ), + new( "/*/**" , "a/" , false ), + new( "/*/**" , "a/b" , false ), + new( "/*/" , "a" , false ), + new( "/*/" , "a/" , true ), + new( "/*/b" , "a/b" , true ), + new( "/**/a" , "a" , true ), + new( "/**/a" , "x/ba" , false ), + new( "/a/*" , "a" , false ), + new( "/a/*" , "a/" , true ), + new( "/a/*" , "a/b" , true ), + new( "/a/*" , "a/b/" , false ), + new( "/a/*" , "a/b/c" , false ), + new( "/a/*/" , "a" , false ), + new( "/a/*/" , "a/" , false ), + new( "/a/*/" , "a/b" , false ), + new( "/a/*/" , "a/b/" , true ), + new( "/a/*/" , "a/b/c" , true ), + new( "/a/**" , "a" , false ), + new( "/a/**" , "a/" , false ), + new( "/a/**" , "a/b" , false ), + new( "/a/**" , "a/b/" , false ), + new( "/a/**" , "a/b/c" , false ), + new( "/a/**/" , "a" , false ), + new( "/a/**/" , "a/" , false ), + new( "/a/**/" , "a/b" , false ), + new( "/a/**/" , "a/b/" , false ), + new( "/a/**/" , "a/b/c" , false ), + new( "/**/a/" , "a" , false ), + new( "/**/a/" , "a/" , true ), + new( "/**/a/" , "a/b" , true ), + new( "/**/b/" , "a/b" , false ), + new( "/**/b/" , "a/b/" , true ), + new( "/**/b/" , "a/c/" , false ), + new( "/a/*/b/" , "a/b/" , false ), + new( "/a/*/b/" , "a/x/b/" , true ), + new( "/a/*/b/" , "a/x/b/c" , true ), + new( "/a/*/b/" , "a/x/c" , false ), + new( "/a/*/b/" , "a/x/y/b" , false ), + new( "/a**b/" , "a/x/y/b" , false ), + new( "/a/**/b/" , "a/b" , false ), + new( "/a/**/b/" , "a/b/" , true ), + new( "/a/**/b/" , "a/x/b/" , true ), + new( "/a/**/b/" , "a/x/y/b/" , true ), + new( "/a/**/b/" , "a/x/y/c" , false ), + new( "/a/**/b/" , "a-b/" , false ), + new( "a/*/*" , "a/b" , false ), + new( "/a/*/*/d" , "a/b/c/d" , true ), + new( "/a/*/*/d" , "a/b/x/c/d" , false ), + new( "/a/**/*/d" , "a/b/x/c/d" , true ), + new( "*/*/b" , "a/b" , false ), + new( "/a*/" , "abc/" , true ), + new( "/a*/" , "ab/c/" , true ), + new( "/*b*/" , "axbyc/" , true ), + new( "/*c/" , "abc/" , true ), + new( "/*c/" , "a/abc/" , false ), + new( "/a*c/" , "axbyc/" , true ), + new( "/a*c/" , "axb/yc/" , false ), + new( "/**/*x*/" , "a/b/cxy/d" , true ), + new( "/a/*.md" , "a/x.md" , true ), + new( "/*/*/*.md" , "a/b/x.md" , true ), + new( "/**/*.md" , "a/b.md/x.md" , true ), + new( "**/*.md" , "a/b.md/x.md" , false ), + new( "/*.md" , "a/md" , false ), + new( "/a.*" , "a.b" , true ), + new( "/a.*" , "a.b/" , true ), + new( "/a.*" , "x/a.b/" , false ), + new( "/a.*/" , "a.b" , false ), + new( "/a.*/" , "a.b/" , true ), + new( "/**/*x*/AB/*/CD" , "a/b/cxy/AB/fff/CD" , true ), + new( "/**/*x*/AB/*/CD" , "a/b/cxy/AB/ff/ff/CD" , false ), + new( "/**/*x*/AB/**/CD/*" , "a/b/cxy/AB/ff/ff/CD" , false ), + new( "/**/*x*/AB/**/CD/*" , "a/b/cxy/AB/ff/ff/CD/" , true ), + new( "/**/*x*/AB/**/CD/*" , "a/b/cxy/AB/[]/!!/CD/h" , true ), // @formatter:on }; @@ -202,20 +201,18 @@ public void TestGetMatchingCodeownersEntry(TestCase testCase) List codeownersEntries = CodeownersFile.GetCodeownersEntries(testCase.CodeownersPath + "@owner"); - VerifyGetMatchingCodeownersEntry(testCase, codeownersEntries, useRegexMatcher: false, testCase.ExpectedLegacyMatch); - VerifyGetMatchingCodeownersEntry(testCase, codeownersEntries, useRegexMatcher: true, testCase.ExpectedNewMatch); + VerifyGetMatchingCodeownersEntry(testCase, codeownersEntries, testCase.ExpectedNewMatch); } private static void VerifyGetMatchingCodeownersEntry( TestCase testCase, List codeownersEntries, - bool useRegexMatcher, bool expectedMatch) { CodeownersEntry entry = // Act CodeownersFile.GetMatchingCodeownersEntry(testCase.TargetPath, - codeownersEntries, useRegexMatcher); + codeownersEntries); Assert.That(entry.Owners, Has.Count.EqualTo(expectedMatch ? 1 : 0)); } @@ -223,6 +220,5 @@ private static void VerifyGetMatchingCodeownersEntry( public record TestCase( string CodeownersPath, string TargetPath, - bool ExpectedLegacyMatch, bool ExpectedNewMatch); } diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/CodeownersManualAnalysisTests.cs b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/CodeownersManualAnalysisTests.cs index b206011cfb2..1ce50113dad 100644 --- a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/CodeownersManualAnalysisTests.cs +++ b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/CodeownersManualAnalysisTests.cs @@ -12,8 +12,7 @@ namespace Azure.Sdk.Tools.RetrieveCodeOwners.Tests; /// /// A class containing a set of tools, implemented as unit tests, /// allowing you to view and diff owners of files of locally cloned repositories, -/// by obtaining the owners based on specified CODEOWNERS files, and/or using specified -/// matching logic: the legacy prefix-based or the new regex-based wildcard-supporting matcher. +/// by obtaining the owners based on specified CODEOWNERS files. /// /// These tools are to be run manually, locally, by a developer. /// They do not participate in an automated regression test suite. @@ -58,7 +57,7 @@ public class CodeownersManualAnalysisTests /// This file is expected to be manually created by you, in your local repo clone. /// For details of usage of this file, see: /// - /// WriteTwoCodeownersFilesAndMatcherOwnersDiffToCsv + /// WriteTwoCodeownersFilesOwnersDiffToCsv /// /// private const string SecondaryCodeownersFilePathSuffix = "/.github/CODEOWNERS2"; @@ -71,7 +70,7 @@ public class CodeownersManualAnalysisTests [Test] // Runtime <1s public void OwnersForAzureDev() - => WriteOwnersToCsvUsingRegexMatcher( + => WriteOwnersToCsv( targetDirPathSuffix: "/../azure-dev", outputFileNamePrefix: "azure-dev", ignoredPathPrefixes: ".git|artifacts"); @@ -90,18 +89,18 @@ public void OwnersForAzureDev() [Test] // Runtime <1s public void OwnersForAzureSdkTools() - => WriteOwnersToCsvUsingRegexMatcher( + => WriteOwnersToCsv( targetDirPathSuffix: "", outputFileNamePrefix: "azure-sdk-tools", ignoredPathPrefixes: ".git|artifacts"); #endregion - #region Tests - Owners diffs for the prefix-based vs regex-based CODEOWNERS matchers, plus differing contents. + #region Tests - Owners diffs for differing CODEOWNERS contents. [Test] // Runtime <1s public void OwnersDiffForAzureDev() - => WriteTwoCodeownersFilesAndMatcherOwnersDiffToCsv( + => WriteTwoCodeownersFilesOwnersDiffToCsv( targetDirPathSuffix: "/../azure-dev", outputFileNamePrefix: "azure-dev", ignoredPathPrefixes: ".git|artifacts"); @@ -149,12 +148,12 @@ public void OwnersDiffForAzureDev() #region Parameterized tests - Owners private void WriteLangRepoOwnersToCsv(string langName) - => WriteOwnersToCsvUsingRegexMatcher( + => WriteOwnersToCsv( targetDirPathSuffix: LangRepoTargetDirPathSuffix(langName), outputFileNamePrefix: $"azure-sdk-for-{langName}", ignoredPathPrefixes: ".git|artifacts"); - private void WriteOwnersToCsvUsingRegexMatcher( + private void WriteOwnersToCsv( string targetDirPathSuffix, string outputFileNamePrefix, string ignoredPathPrefixes = Program.DefaultIgnoredPrefixes) @@ -171,7 +170,6 @@ private void WriteOwnersToCsvUsingRegexMatcher( targetDirPathSuffix, CodeownersFilePathSuffix, ignoredPathPrefixes, - useRegexMatcher: true, outputFileNamePrefix); } @@ -180,7 +178,7 @@ private void WriteOwnersToCsvUsingRegexMatcher( #region Parameterized tests - Owners diff private void WriteLangRepoOwnersDiffToCsv(string langName) - => WriteTwoCodeownersFilesAndMatcherOwnersDiffToCsv( + => WriteTwoCodeownersFilesOwnersDiffToCsv( targetDirPathSuffix: LangRepoTargetDirPathSuffix(langName), outputFileNamePrefix: $"azure-sdk-for-{langName}", ignoredPathPrefixes: ".git|artifacts"); @@ -192,21 +190,20 @@ private void WriteLangRepoOwnersDiffToCsv(string langName) /// /// with following meanings bound to LEFT and RIGHT: /// - /// LEFT: RetrieveCodeowners configuration using the legacy prefix-based CODEOWNERS paths matcher, - /// and given input local repository clone CODEOWNERS file. + /// LEFT: RetrieveCodeowners configuration given input local repository clone CODEOWNERS file. /// - /// RIGHT: RetrieveCodeowners configuration using the new regex-based wildcard-supporting matcher, - /// and given input repository CODEOWNERS2 file, located beside CODEOWNERS file. + /// RIGHT: RetrieveCodeowners configuration given input repository CODEOWNERS2 file, + /// located beside CODEOWNERS file. /// /// The CODEOWNERS2 file is expected to be created manually by you. This way you can diff CODEOWNERS /// to whatever version of it you want to express in CODEOWNERS2. For example, CODEOWNERS2 could have /// contents of CODEOWNERS as seen in an open PR pending being merged. /// /// Note that modifying or reordering existing paths may always impact which PR reviewers are auto-assigned, - /// but the build failure notification recipients (owners) changes apply only to paths that represent + /// but the build failure notification recipients changes apply only to paths that represent /// build definition .yml files. /// - private void WriteTwoCodeownersFilesAndMatcherOwnersDiffToCsv( + private void WriteTwoCodeownersFilesOwnersDiffToCsv( string targetDirPathSuffix, string outputFileNamePrefix, string ignoredPathPrefixes = Program.DefaultIgnoredPrefixes) @@ -215,19 +212,19 @@ private void WriteTwoCodeownersFilesAndMatcherOwnersDiffToCsv( string targetDir = rootDir + targetDirPathSuffix; Debug.Assert(Directory.Exists(targetDir), $"Ensure you have cloned the repo into '{targetDir}'. " + - "See comments on CodeownersManualAnalysisTests and WriteTwoCodeownersFilesAndMatcherOwnersDiffToCsv for details."); + "See comments on CodeownersManualAnalysisTests and WriteTwoCodeownersFilesOwnersDiffToCsv for details."); Debug.Assert(File.Exists(targetDir + CodeownersFilePathSuffix), $"Ensure you have cloned the repo into '{targetDir}'. " + - "See comments on CodeownersManualAnalysisTests and WriteTwoCodeownersFilesAndMatcherOwnersDiffToCsv for details."); + "See comments on CodeownersManualAnalysisTests and WriteTwoCodeownersFilesOwnersDiffToCsv for details."); Debug.Assert(File.Exists(targetDir + SecondaryCodeownersFilePathSuffix), $"Ensure you have created '{Path.GetFullPath(targetDir + SecondaryCodeownersFilePathSuffix)}'. " + - $"See comment on WriteTwoCodeownersFilesAndMatcherOwnersDiffToCsv for details."); + $"See comment on WriteTwoCodeownersFilesOwnersDiffToCsv for details."); WriteOwnersDiffToCsv( new[] { - (targetDirPathSuffix, CodeownersFilePathSuffix, ignoredPathPrefixes, useRegexMatcher: false), - (targetDirPathSuffix, SecondaryCodeownersFilePathSuffix, ignoredPathPrefixes, useRegexMatcher: true) + (targetDirPathSuffix, CodeownersFilePathSuffix, ignoredPathPrefixes), + (targetDirPathSuffix, SecondaryCodeownersFilePathSuffix, ignoredPathPrefixes) }, outputFileNamePrefix); } @@ -296,7 +293,6 @@ private static void WriteOwnersToCsv( string targetDirPathSuffix, string codeownersFilePathSuffix, string ignoredPrefixes, - bool useRegexMatcher, string outputFilePrefix) { var stopwatch = Stopwatch.StartNew(); @@ -306,8 +302,7 @@ private static void WriteOwnersToCsv( Dictionary ownersData = RetrieveCodeowners( targetDirPathSuffix, codeownersFilePathSuffix, - ignoredPrefixes, - useRegexMatcher); + ignoredPrefixes); List outputLines = new List { "PATH | PATH EXPRESSION | OWNERS | ISSUE" }; @@ -495,8 +490,7 @@ private static void WriteOwnersDiffToCsv( ( string targetDirPathSuffix, string codeownersFilePathSuffix, - string ignoredPrefixes, - bool useRegexMatcher + string ignoredPrefixes )[] input, string outputFilePrefix) { @@ -505,13 +499,11 @@ bool useRegexMatcher Dictionary leftOwners = RetrieveCodeowners( input[0].targetDirPathSuffix, input[0].codeownersFilePathSuffix, - input[0].ignoredPrefixes, - input[0].useRegexMatcher); + input[0].ignoredPrefixes); Dictionary rightOwners = RetrieveCodeowners( input[1].targetDirPathSuffix, input[1].codeownersFilePathSuffix, - input[1].ignoredPrefixes, - input[1].useRegexMatcher); + input[1].ignoredPrefixes); string[] diffLines = PathOwnersDiff(leftOwners, rightOwners); @@ -525,8 +517,7 @@ bool useRegexMatcher private static Dictionary RetrieveCodeowners( string targetDirPathSuffix, string codeownersFilePathSuffixToRootDir, - string ignoredPathPrefixes, - bool useRegexMatcher) + string ignoredPathPrefixes) { string rootDir = PathNavigatingToRootDir(CurrentDir); string targetDir = rootDir + targetDirPathSuffix; @@ -547,8 +538,7 @@ private static Dictionary RetrieveCodeowners( // because Contacts.GetMatchingCodeownersEntry() calls ExcludeNonUserAliases(). excludeNonUserAliases: false, targetDir, - ignoredPathPrefixes, - useRegexMatcher); + ignoredPathPrefixes); actualOutput = consoleOutput.GetStdout(); actualErr = consoleOutput.GetStderr(); diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/ProgramGlobPathTests.cs b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/RetrieveCodeOwnersProgramTests.cs similarity index 90% rename from tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/ProgramGlobPathTests.cs rename to tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/RetrieveCodeOwnersProgramTests.cs index 9551769f669..2ab96772ac8 100644 --- a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/ProgramGlobPathTests.cs +++ b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/RetrieveCodeOwnersProgramTests.cs @@ -8,16 +8,17 @@ namespace Azure.Sdk.Tools.RetrieveCodeOwners.Tests; /// /// Test class for Azure.Sdk.Tools.RetrieveCodeOwners.Program.Main(), -/// for scenario in which targetPath is a glob path, i.e. -/// targetPath.IsGlobPath() returns true. /// /// The tests assertion expectations are set to match GitHub CODEOWNERS interpreter behavior, /// as explained here: /// https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners /// and as observed based on manual tests and verification. +/// +/// For additional related tests, please see: +/// - Azure.Sdk.Tools.CodeOwnersParser.Tests.CodeownersFileTests /// [TestFixture] -public class ProgramGlobPathTests +public class RetrieveCodeOwnersProgramTests { /// /// Given: @@ -28,7 +29,7 @@ public class ProgramGlobPathTests /// /// targetPath of /** /// - /// excludeNonUserAliases set to false and useRegexMatcher set to true. + /// excludeNonUserAliases set to false /// /// When: /// The retrieve-codeowners tool is executed on these inputs. @@ -39,13 +40,12 @@ public class ProgramGlobPathTests /// /// [Test] - public void OutputsCodeownersForGlobPath() + public void OutputsCodeowners() { const string targetDir = "./TestData/InputDir"; const string targetPath = "/**"; - const string codeownersFilePathOrUrl = "./TestData/glob_path_CODEOWNERS"; + const string codeownersFilePathOrUrl = "./TestData/test_CODEOWNERS"; const bool excludeNonUserAliases = false; - const bool useRegexMatcher = true; var expectedEntries = new Dictionary { @@ -74,8 +74,7 @@ public void OutputsCodeownersForGlobPath() targetPath, codeownersFilePathOrUrl, excludeNonUserAliases, - targetDir, - useRegexMatcher: useRegexMatcher); + targetDir); actualOutput = consoleOutput.GetStdout(); actualErr = consoleOutput.GetStderr(); diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/simple_path_CODEOWNERS b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/simple_path_CODEOWNERS deleted file mode 100644 index d4a23d1bbb1..00000000000 --- a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/simple_path_CODEOWNERS +++ /dev/null @@ -1,33 +0,0 @@ -# Instructions for CODEOWNERS file format and automatic build failure notifications: -# https://github.com/Azure/azure-sdk/blob/main/docs/policies/opensource.md#codeowners - -########### -# SDK -########### - -# Catch-all for SDK changes -/sdk/ @person1 @person2 - -# Service teams -/sdk/azconfig/ @person3 @person4 - -# Example for a service that needs issues to be labeled -# ServiceLabel: %KeyVault %Service Attention -/sdk/keyvault/ @person5 @person6 - -# Example for a service that needs PRs to be labeled -# PRLabel: %label -/sdk/servicebus/ @person7 @person8 - -# Example for a service that needs both issues and PRs to be labeled -# ServiceLabel: %label -# PRLabel: %label -/sdk/eventhubs/ @person7 @person8 - -# This is for testing non user aliases case. -/sdk/testUser/ @azure/azure-sdk-eng @azure-sdk - -# Example for service that does not have the code in the repo but wants issues to be labeled -# Notice the use of the moniker // -# ServiceLabel: %label %Service Attention -// @person7 @person8 diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/glob_path_CODEOWNERS b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/test_CODEOWNERS similarity index 100% rename from tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/glob_path_CODEOWNERS rename to tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/test_CODEOWNERS diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners/Program.cs b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners/Program.cs index 0b2d2dc2d65..1661dda8d51 100644 --- a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners/Program.cs +++ b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners/Program.cs @@ -37,10 +37,6 @@ public static class Program /// Defaults to ".git". /// Example usage: ".git|foo|bar" /// - /// - /// Whether to use the new matcher for CODEOWNERS file paths, that supports matching - /// entries with * and **, instead of silently ignoring them. - /// /// /// On STDOUT: The JSON representation of the matched CodeownersEntry. /// "new CodeownersEntry()" if no path in the CODEOWNERS data matches. @@ -52,8 +48,7 @@ public static int Main( string codeownersFilePathOrUrl, bool excludeNonUserAliases = false, string? targetDir = null, - string ignoredPathPrefixes = DefaultIgnoredPrefixes, - bool useRegexMatcher = CodeownersFile.UseRegexMatcherDefault) + string ignoredPathPrefixes = DefaultIgnoredPrefixes) { try { @@ -76,13 +71,11 @@ public static int Main( targetDir!, codeownersFilePathOrUrl, excludeNonUserAliases, - SplitIgnoredPathPrefixes(), - useRegexMatcher) + SplitIgnoredPathPrefixes()) : GetCodeownersForSimplePath( targetPath, codeownersFilePathOrUrl, - excludeNonUserAliases, - useRegexMatcher); + excludeNonUserAliases); string codeownersJson = JsonSerializer.Serialize( codeownersData, @@ -108,8 +101,7 @@ private static Dictionary GetCodeownersForGlobPath( string targetDir, string codeownersFilePathOrUrl, bool excludeNonUserAliases, - string[]? ignoredPathPrefixes = null, - bool useRegexMatcher = CodeownersFile.UseRegexMatcherDefault) + string[]? ignoredPathPrefixes = null) { ignoredPathPrefixes ??= Array.Empty(); @@ -118,8 +110,7 @@ private static Dictionary GetCodeownersForGlobPath( targetPath, targetDir, codeownersFilePathOrUrl, - ignoredPathPrefixes, - useRegexMatcher); + ignoredPathPrefixes); if (excludeNonUserAliases) codeownersEntries.Values.ToList().ForEach(entry => entry.ExcludeNonUserAliases()); @@ -130,14 +121,12 @@ private static Dictionary GetCodeownersForGlobPath( private static CodeownersEntry GetCodeownersForSimplePath( string targetPath, string codeownersFilePathOrUrl, - bool excludeNonUserAliases, - bool useRegexMatcher = CodeownersFile.UseRegexMatcherDefault) + bool excludeNonUserAliases) { CodeownersEntry codeownersEntry = CodeownersFile.GetMatchingCodeownersEntry( targetPath, - codeownersFilePathOrUrl, - useRegexMatcher); + codeownersFilePathOrUrl); if (excludeNonUserAliases) codeownersEntry.ExcludeNonUserAliases(); diff --git a/tools/code-owners-parser/CodeOwnersParser/CodeownersFile.cs b/tools/code-owners-parser/CodeOwnersParser/CodeownersFile.cs index ba90dcde5f8..83f1dc376ee 100644 --- a/tools/code-owners-parser/CodeOwnersParser/CodeownersFile.cs +++ b/tools/code-owners-parser/CodeOwnersParser/CodeownersFile.cs @@ -8,8 +8,6 @@ namespace Azure.Sdk.Tools.CodeOwnersParser { public static class CodeownersFile { - public const bool UseRegexMatcherDefault = true; - public static List GetCodeownersEntriesFromFileOrUrl( string codeownersFilePathOrUrl) { @@ -38,19 +36,17 @@ public static List GetCodeownersEntries(string codeownersConten public static CodeownersEntry GetMatchingCodeownersEntry( string targetPath, - string codeownersFilePathOrUrl, - bool useRegexMatcher = UseRegexMatcherDefault) + string codeownersFilePathOrUrl) { var codeownersEntries = GetCodeownersEntriesFromFileOrUrl(codeownersFilePathOrUrl); - return GetMatchingCodeownersEntry(targetPath, codeownersEntries, useRegexMatcher); + return GetMatchingCodeownersEntry(targetPath, codeownersEntries); } public static Dictionary GetMatchingCodeownersEntries( GlobFilePath targetPath, string targetDir, string codeownersFilePathOrUrl, - string[]? ignoredPathPrefixes = null, - bool useRegexMatcher = UseRegexMatcherDefault) + string[]? ignoredPathPrefixes = null) { ignoredPathPrefixes ??= Array.Empty(); @@ -62,21 +58,17 @@ public static Dictionary GetMatchingCodeownersEntries( path => path, path => GetMatchingCodeownersEntry( path, - codeownersEntries, - useRegexMatcher)); + codeownersEntries)); return codeownersEntriesByPath; } public static CodeownersEntry GetMatchingCodeownersEntry( string targetPath, - List codeownersEntries, - bool useRegexMatcher = UseRegexMatcherDefault) + List codeownersEntries) { Debug.Assert(targetPath != null); - return useRegexMatcher - ? new MatchedCodeownersEntry(targetPath, codeownersEntries).Value - : GetMatchingCodeownersEntryLegacyImpl(targetPath, codeownersEntries); + return new MatchedCodeownersEntry(targetPath, codeownersEntries).Value; } private static CodeownersEntry ProcessCodeownersLine( @@ -124,28 +116,5 @@ private static string NormalizeLine(string line) // Remove tabs and trim extra whitespace ? line.Replace('\t', ' ').Trim() : line; - - private static CodeownersEntry GetMatchingCodeownersEntryLegacyImpl( - string targetPath, - List codeownersEntries) - { - // Normalize the start and end of the paths by trimming slash - targetPath = targetPath.Trim('/'); - - // We want to find the match closest to the bottom of the codeowners file. - // CODEOWNERS sorts the paths in order of 'RepoPath', 'ServicePath' and then 'PackagePath'. - for (int i = codeownersEntries.Count - 1; i >= 0; i--) - { - string codeownersPath = codeownersEntries[i].PathExpression.Trim('/'); - // Note that this only matches on paths without glob patterns which is good enough - // for our current scenarios but in the future might need to support globs - if (targetPath.StartsWith(codeownersPath, StringComparison.OrdinalIgnoreCase)) - { - return codeownersEntries[i]; - } - } - - return new CodeownersEntry(); - } } } diff --git a/tools/code-owners-parser/CodeOwnersParser/MatchedCodeownersEntry.cs b/tools/code-owners-parser/CodeOwnersParser/MatchedCodeownersEntry.cs index c42f729b6a8..a719576029d 100644 --- a/tools/code-owners-parser/CodeOwnersParser/MatchedCodeownersEntry.cs +++ b/tools/code-owners-parser/CodeOwnersParser/MatchedCodeownersEntry.cs @@ -13,11 +13,7 @@ namespace Azure.Sdk.Tools.CodeOwnersParser /// To use this class, construct it, passing as input relevant paths. /// Then, to obtain the value of the matched entry, reference "Value" member. /// - /// This class uses a regex-based wildcard-supporting (* and **) matcher, compared to the old one, located in: - /// - /// CodeownersFile.GetMatchingCodeownersEntryLegacyImpl() - /// - /// The old matcher is prefix-based, strips suffix "/", and doesn't support wildcards. + /// This class uses a regex-based wildcard-supporting (* and **) matcher. /// /// This matcher reflects the matching behavior of the built-in GitHub CODEOWNERS interpreter, /// but with additional assumptions imposed about the paths present in CODEOWNERS, as guaranteed @@ -29,7 +25,7 @@ namespace Azure.Sdk.Tools.CodeOwnersParser /// /// The validation spec is given in this comment: /// https://github.com/Azure/azure-sdk-tools/issues/4859#issuecomment-1370360622 - /// See also ProgramGlobPathTests and CodeownersFileTests tests. + /// See also RetrieveCodeOwnersProgramTests and CodeownersFileTests tests. /// /// Reference: /// https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax