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 e13bbc776b9..559e3bfde44 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 @@ -4,8 +4,7 @@ namespace Azure.Sdk.Tools.CodeOwnersParser.Tests; /// -/// Please see the comment on: -/// - Azure.Sdk.Tools.CodeOwnersParser.Tests.CodeownersFileTests.testCases +/// Please see the comment on CodeownersFileTests.testCases /// [TestFixture] public class CodeownersFileTests @@ -23,176 +22,175 @@ public class CodeownersFileTests private static readonly TestCase[] testCases = { // @formatter:off - // 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 ), - + // 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 }; /// - /// Exercises Azure.Sdk.Tools.CodeOwnersParser.Tests.CodeownersFileTests.testCases. + /// Exercises CodeownersFileTests.testCases /// See comment on that member for details. /// [TestCaseSource(nameof(testCases))] @@ -217,6 +215,9 @@ private static void VerifyGetMatchingCodeownersEntry( Assert.That(entry.Owners, Has.Count.EqualTo(expectedMatch ? 1 : 0)); } + /// + /// Please see comment on CodeownersFileTests.testCases + /// public record TestCase( string CodeownersPath, string TargetPath, diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/RetrieveCodeOwnersProgramTests.cs b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/RetrieveCodeOwnersProgramTests.cs index 2ab96772ac8..1879eecda4d 100644 --- a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/RetrieveCodeOwnersProgramTests.cs +++ b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/RetrieveCodeOwnersProgramTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.Json; using Azure.Sdk.Tools.CodeOwnersParser; using NUnit.Framework; @@ -21,50 +22,115 @@ namespace Azure.Sdk.Tools.RetrieveCodeOwners.Tests; public class RetrieveCodeOwnersProgramTests { /// - /// Given: + /// A battery of test cases exercising the Azure.Sdk.Tools.RetrieveCodeOwners.Program.Main executable. /// - /// file system contents as seen in TestData/InputDir - /// - /// codeownersFilePathOrUrl contents as seen in TestData/glob_path_CODEOWNERS + /// Each test case is composed of a targetPath and expected CodeownersEntry that is to match against + /// the targetPath when the executable is executed. /// - /// targetPath of /** + /// These test battery is used in the following ways: /// - /// excludeNonUserAliases set to false - /// - /// When: - /// The retrieve-codeowners tool is executed on these inputs. + /// 1. In OutputsCorrectCodeownersOnSimpleTargetPath parameterized unit test, each test case is exercised + /// by running the executable with targetPath provided as input targetPath. /// - /// Then: - /// The tool should return on STDOUT owners matched as seen in the - /// "expectedEntries" dictionary. - /// + /// 2. In OutputsCorrectCodeownersOnGlobTargetPath the entire test battery is asserted against + /// by running the executable with targetPaths set to "/**", thus entering the glob-matching mode and finding + /// all the targetPaths present in this battery. + /// + /// Preconditions for running tests against this battery: + /// - directory "./TestData/InputDir" and file "./TestData/test_CODEOWNERS" contain appropriate contents + /// - the exercised executable is passed as input appropriate arguments pointing to the file system; + /// consult the aforementioned tests for concrete values. + /// + private static readonly TestCase[] testCases = + { + // @formatter:off + // targetPath expected CodeownersEntry + new ("a.txt" , new CodeownersEntry("/*", new List { "star" })), + new ("b.txt" , new CodeownersEntry("/*", new List { "star" })), + new ("foo/a.txt" , new CodeownersEntry("/foo/**/a.txt", new List { "foo_2star_a" })), + new ("foo/b.txt" , new CodeownersEntry("/**", new List { "2star" })), + new ("foo/bar/a.txt" , new CodeownersEntry("/foo/*/a.txt", new List { "foo_star_a_1", "foo_star_a_2" })), + new ("foo/bar/b.txt" , new CodeownersEntry("/**", new List { "2star" })), + new ("baz/cor/c.txt" , new CodeownersEntry("/baz*", new List { "baz_star" })), + new ("baz_.txt" , new CodeownersEntry("/baz*", new List { "baz_star" })), + new ("qux/abc/d.txt" , new CodeownersEntry("/qux/", new List { "qux" })), + new ("cor.txt" , new CodeownersEntry("/*", new List { "star" })), + new ("cor2/a.txt" , new CodeownersEntry("/**", new List { "2star" })), + new ("cor/gra/a.txt" , new CodeownersEntry("/**", new List { "2star" })) + // @formatter:on + }; + + private static Dictionary TestCasesAsDictionary + => testCases.ToDictionary( + testCase => testCase.TargetPath, + testCase => testCase.ExpectedCodeownersEntry); + + /// + /// Please see comment on RetrieveCodeOwnersProgramTests.testCases + /// + [TestCaseSource(nameof(testCases))] + public void OutputsCorrectCodeownersOnSimpleTargetPath(TestCase testCase) + { + const string targetDir = "./TestData/InputDir"; + const string codeownersFilePathOrUrl = "./TestData/test_CODEOWNERS"; + const bool excludeNonUserAliases = false; + + var targetPath = testCase.TargetPath; + var expectedEntry = testCase.ExpectedCodeownersEntry; + + // Act + (string actualOutput, string actualErr, int returnCode) = RunProgramMain( + targetPath, + codeownersFilePathOrUrl, + excludeNonUserAliases, + targetDir); + + CodeownersEntry actualEntry = TryDeserializeActualEntryFromSimpleTargetPath(actualOutput, actualErr); + + Assert.Multiple(() => + { + Assert.That(actualEntry, Is.EqualTo(expectedEntry), $"path: {targetPath}"); + Assert.That(returnCode, Is.EqualTo(0)); + Assert.That(actualErr, Is.EqualTo(string.Empty)); + }); + } + + /// + /// Please see comment on RetrieveCodeOwnersProgramTests.testCases /// [Test] - public void OutputsCodeowners() + public void OutputsCorrectCodeownersOnGlobTargetPath() { const string targetDir = "./TestData/InputDir"; const string targetPath = "/**"; const string codeownersFilePathOrUrl = "./TestData/test_CODEOWNERS"; const bool excludeNonUserAliases = false; - var expectedEntries = new Dictionary + Dictionary expectedEntriesByPath = TestCasesAsDictionary; + + // Act + (string actualOutput, string actualErr, int returnCode) = RunProgramMain( + targetPath, + codeownersFilePathOrUrl, + excludeNonUserAliases, + targetDir); + + Dictionary actualEntriesByPath = TryDeserializeActualEntriesFromGlobTargetPath(actualOutput, actualErr); + + Assert.Multiple(() => { - // @formatter:off - ["a.txt"] = new CodeownersEntry("/*", new List { "star" }), - ["b.txt"] = new CodeownersEntry("/*", new List { "star" }), - ["foo/a.txt"] = new CodeownersEntry("/foo/**/a.txt", new List { "foo_2star_a" }), - ["foo/b.txt"] = new CodeownersEntry("/**", new List { "2star" }), - ["foo/bar/a.txt"] = new CodeownersEntry("/foo/*/a.txt", new List { "foo_star_a_1", "foo_star_a_2" }), - ["foo/bar/b.txt"] = new CodeownersEntry("/**", new List { "2star" }), - ["baz/cor/c.txt"] = new CodeownersEntry("/baz*", new List { "baz_star" }), - ["baz_.txt"] = new CodeownersEntry("/baz*", new List { "baz_star" }), - ["qux/abc/d.txt"] = new CodeownersEntry("/qux/", new List { "qux" }), - ["cor.txt"] = new CodeownersEntry("/*", new List { "star" }), - ["cor2/a.txt"] = new CodeownersEntry("/**", new List { "2star" }), - ["cor/gra/a.txt"] = new CodeownersEntry("/**", new List { "2star" }), - // @formatter:on - }; - + AssertEntries(actualEntriesByPath, expectedEntriesByPath); + Assert.That(returnCode, Is.EqualTo(0)); + Assert.That(actualErr, Is.EqualTo(string.Empty)); + }); + } + + private static (string actualOutput, string actualErr, int returnCode) RunProgramMain( + string targetPath, + string codeownersFilePathOrUrl, + bool excludeNonUserAliases, + string targetDir) + { string actualOutput, actualErr; int returnCode; using (var consoleOutput = new ConsoleOutput()) @@ -80,17 +146,31 @@ public void OutputsCodeowners() actualErr = consoleOutput.GetStderr(); } - var actualEntries = TryDeserializeActualEntries(actualOutput, actualErr); + return (actualOutput, actualErr, returnCode); + } - Assert.Multiple(() => + private static CodeownersEntry TryDeserializeActualEntryFromSimpleTargetPath( + string actualOutput, + string actualErr) + { + CodeownersEntry actualEntry; + try { - AssertEntries(actualEntries, expectedEntries); - Assert.That(returnCode, Is.EqualTo(0)); - Assert.That(actualErr, Is.EqualTo(string.Empty)); - }); + actualEntry = + JsonSerializer.Deserialize(actualOutput)!; + } + catch (Exception e) + { + Console.WriteLine(e); + Console.WriteLine("actualOutput: " + actualOutput); + Console.WriteLine("actualErr: " + actualErr); + throw; + } + + return actualEntry; } - private static Dictionary TryDeserializeActualEntries( + private static Dictionary TryDeserializeActualEntriesFromGlobTargetPath( string actualOutput, string actualErr) { @@ -124,4 +204,11 @@ private static void AssertEntries( Assert.That(actualEntries, Has.Count.EqualTo(expectedEntries.Count)); } + + /// + /// Please see comment on RetrieveCodeOwnersProgramTests.testCases + /// + public record TestCase( + string TargetPath, + CodeownersEntry ExpectedCodeownersEntry); }