diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/codeowners/EntryBuilder.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/codeowners/EntryBuilder.java index 0483909c2e5..3492e482a4d 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/codeowners/EntryBuilder.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/codeowners/EntryBuilder.java @@ -54,7 +54,10 @@ public EntryBuilder(CharacterMatcher.Factory characterMatcherFactory, String s) if (offset == c.length // empty line || c[offset] == '#' // comment - || c[offset] == '[') { // section header + || c[offset] == '[' // section header + || (c[offset] == '^' + && offset + 1 < c.length + && c[offset + 1] == '[')) { // GitLab optional section header (^[...]) return null; } diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/codeowners/EntryBuilderTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/codeowners/EntryBuilderTest.groovy deleted file mode 100644 index 28d260b8240..00000000000 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/codeowners/EntryBuilderTest.groovy +++ /dev/null @@ -1,178 +0,0 @@ -package datadog.trace.civisibility.codeowners - - -import datadog.trace.civisibility.codeowners.matcher.CharacterMatcher -import spock.lang.Specification - -class EntryBuilderTest extends Specification { - - def "test entry #pattern match against #path is #expectedResult"() { - setup: - def matcherFactory = new CharacterMatcher.Factory() - def entry = new EntryBuilder(matcherFactory, pattern + " " + ownersToString(owners)).parse() - def negatedEntry = new EntryBuilder(matcherFactory, "!" + pattern + " " + ownersToString(owners)).parse() - - when: - def result = entry.matcher.consume(path.toCharArray(), 0) >= 0 - def negatedResult = negatedEntry.matcher.consume(path.toCharArray(), 0) >= 0 - - then: - entry.owners == owners - result == expectedResult - negatedResult == !expectedResult - - where: - pattern | owners | path | expectedResult - "someToken" | ["owner"] | "someToken" | true - "someToken" | ["owner"] | "sometoken" | false - "someToken" | ["owner"] | "anotherToken" | false - "someToken" | ["owner"] | "parent/someToken" | true - "someToken" | ["owner"] | "grandparent/parent/someToken" | true - "/someToken" | ["owner"] | "someToken" | true - "/someToken" | ["owner"] | "sometoken" | false - "/someToken" | ["owner"] | "anotherToken" | false - "/someToken" | ["owner"] | "parent/someToken" | false - "/someToken" | ["owner"] | "grandparent/parent/someToken" | false - "someTok?n" | ["owner"] | "someTokkn" | true - "someTok?n" | ["owner"] | "someTok/n" | false - "someTok?n" | ["owner"] | "parent/someTokkn" | true - "someTok?n" | ["owner"] | "grandparent/parent/someTokkn" | true - "/someTok?n" | ["owner"] | "someTokkn" | true - "someTok*n" | ["owner"] | "someTokkn" | true - "someTok*n" | ["owner"] | "someTok/n" | false - "someTok*n" | ["owner"] | "someTokkkkn" | true - "someTok*n" | ["owner"] | "someTokenToken" | true - "someTok*n" | ["owner"] | "someTokenTokkk" | false - "s*meTok*n" | ["owner"] | "someToken" | true - "s*meTok*n" | ["owner"] | "sabcdefghmeTokabcdefgn" | true - "s*meTok*n" | ["owner"] | "sabcdefghmeTokabcdefg" | false - "someTok*n" | ["owner"] | "parent/someTokkn" | true - "someTok*n" | ["owner"] | "grandparent/parent/someTokkn" | true - "/someTok*n" | ["owner"] | "someTokkn" | true - "someTok[a-b]n" | ["owner"] | "someToken" | false - "someTok[a-z]n" | ["owner"] | "someToken" | true - "someTok[a-z]n" | ["owner"] | "someTokEn" | false - "someTok[a-z]n" | ["owner"] | "someTok9n" | false - "someTok[a-zA-Z]n" | ["owner"] | "someToken" | true - "someTok[a-zA-Z]n" | ["owner"] | "someTokEn" | true - "someTok[a-zA-Z]n" | ["owner"] | "someTok9n" | false - "someTok[a-zA-Z0-9]n" | ["owner"] | "someToken" | true - "someTok[a-zA-Z0-9]n" | ["owner"] | "someTokEn" | true - "someTok[a-zA-Z0-9]n" | ["owner"] | "someTok9n" | true - "s*meTok[a-zA-Z0-9]n" | ["owner"] | "smeToken" | true - "s*meTok[a-zA-Z0-9]n" | ["owner"] | "smeTokEn" | true - "s*meTok[a-zA-Z0-9]n" | ["owner"] | "smeTok9n" | true - "s*meTok[a-zA-Z0-9]n" | ["owner"] | "sabcdmeToken" | true - "s*meTok[a-zA-Z0-9]n" | ["owner"] | "sabcdmeTokEn" | true - "s*meTok[a-zA-Z0-9]n" | ["owner"] | "sabcdmeTok9n" | true - "someToken/" | ["owner"] | "someToken" | true - "someToken/" | ["owner"] | "someToken/child" | true - "someToken/" | ["owner"] | "someToken/child/grandChild" | true - "someToken/" | ["owner"] | "parent/someToken" | true - "someToken/" | ["owner"] | "parent/someToken/child" | true - "someToken/" | ["owner"] | "parent/grandParent/someToken/child/grandChild" | true - "/someToken/" | ["owner"] | "someToken" | true - "/someToken/" | ["owner"] | "someToken/child" | true - "/someToken/" | ["owner"] | "someToken/child/grandChild" | true - "/someToken/" | ["owner"] | "parent/someToken" | false - "/someToken/" | ["owner"] | "parent/someToken/child" | false - "/someToken/" | ["owner"] | "parent/grandParent/someToken/child/grandChild" | false - "some/token" | ["owner"] | "some/token" | true - "some/token" | ["owner"] | "parent/some/token" | false - "some/token" | ["owner"] | "some/token/child" | true - "some/token" | ["owner"] | "some/tokenSuffix" | false - "some/token/" | ["owner"] | "some/token/child" | true - "some/token/" | ["owner"] | "parent/some/token/child" | false - "some\\ Token" | ["owner"] | "some Token" | true - "\\#someToken" | ["owner"] | "#someToken" | true - "**/someToken" | ["owner"] | "someToken" | true - "**/someToken" | ["owner"] | "parent/someToken" | true - "**/someToken" | ["owner"] | "grandparent/parent/someToken" | true - "**/someToken" | ["owner"] | "anotherToken" | false - "**/some/token" | ["owner"] | "some/token" | true - "**/some/token" | ["owner"] | "parent/some/token" | true - "**/some/token" | ["owner"] | "grandparent/parent/some/token" | true - "**/some/token/" | ["owner"] | "some/token" | true - "**/some/token/" | ["owner"] | "some/token/child" | true - "**/some/token/" | ["owner"] | "some/token/child/grandchild" | true - "**/some/token/" | ["owner"] | "parent/some/token/child" | true - "**/some/token/" | ["owner"] | "parent/some/different/token" | false - "someToken/**" | ["owner"] | "someToken" | true - "someToken/**" | ["owner"] | "someToken/child" | true - "someToken/**" | ["owner"] | "someToken/child/grandChild" | true - "someToken/**" | ["owner"] | "parent/someToken" | false - "someToken/**" | ["owner"] | "parent/someToken/child" | false - "someToken/**" | ["owner"] | "parent/grandParent/someToken/child/grandChild" | false - "/someToken/**" | ["owner"] | "someToken" | true - "/someToken/**" | ["owner"] | "someToken/child" | true - "/someToken/**" | ["owner"] | "someToken/child/grandChild" | true - "/someToken/**" | ["owner"] | "parent/someToken" | false - "/someToken/**" | ["owner"] | "parent/someToken/child" | false - "/someToken/**" | ["owner"] | "parent/grandParent/someToken/child/grandChild" | false - "some/**/token" | ["owner"] | "some/token" | true - "some/**/token" | ["owner"] | "some/other/token" | true - "some/**/token" | ["owner"] | "some/yet/another/token" | true - "some/**/token" | ["owner"] | "some/yet/another" | false - "some/**/token" | ["owner"] | "yet/another/token" | false - "someToken" | ["owner", "anotherOwner"] | "someToken" | true - "*" | ["owner"] | "someToken" | true - "*" | ["owner"] | "parent/someToken" | true - "*" | ["owner"] | "parent/someToken/child" | true - "*" | ["owner"] | "someFile.java" | true - "**" | ["owner"] | "someToken" | true - "**" | ["owner"] | "parent/someToken" | true - "**" | ["owner"] | "parent/someToken/child" | true - "**" | ["owner"] | "someFile.java" | true - "*.java" | ["owner"] | "someFile.java" | true - "*.java" | ["owner"] | "parent/someFile.java" | true - "*.java" | ["owner"] | "grandparent/parent/someFile.java" | true - "*.java" | ["owner"] | "someFile.js" | false - "*.java" | ["owner"] | "someFile.java17" | false - "readme.md" | ["owner"] | "readme.md" | true - "readme.md" | ["owner"] | "parent/readme.md" | true - "readme.md" | ["owner"] | "readme.md5" | false - "some/*" | ["owner"] | "some/token" | true - "some/*" | ["owner"] | "some/other/token" | false - "some/*" | ["owner"] | "parent/some/token" | false - "some/*" | ["owner"] | "parent/some/other/token" | false - "**/test*.java" | ["owner"] | "test.java" | true - "**/test*.java" | ["owner"] | "testFile.java" | true - "**/test*.java" | ["owner"] | "parent/test.java" | true - "**/test*.java" | ["owner"] | "parent/testFile.java" | true - "**/test*.java" | ["owner"] | "grandparent/parent/test.java" | true - "**/test*.java" | ["owner"] | "grandparent/parent/testFile.java" | true - "\\#someToken" | ["owner"] | "#someToken" | true - } - - private static String ownersToString(Collection owners) { - def result = new StringBuilder() - - for (String owner : owners) { - result += owner + " " // trailing spaces will be ignored by the matcher - } - - return result.toString() - } - - def "test invalid entry parsing: #entry"() { - setup: - def matcherFactory = new CharacterMatcher.Factory() - - when: - def parsedEntry = new EntryBuilder(matcherFactory, entry).parse() - - then: - parsedEntry == null - - where: - entry << [ - "token[z-a] owner", - "# comment", - " # comment with a leading space", - "[section header]", - " [section header with a leading space]", - "", - " ", - ] - } -} diff --git a/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/codeowners/EntryBuilderTest.java b/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/codeowners/EntryBuilderTest.java new file mode 100644 index 00000000000..3dceb2830b6 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/codeowners/EntryBuilderTest.java @@ -0,0 +1,188 @@ +package datadog.trace.civisibility.codeowners; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import datadog.trace.civisibility.codeowners.matcher.CharacterMatcher; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class EntryBuilderTest { + + @ParameterizedTest(name = "entry {0} match against {2} is {3}") + @MethodSource("testEntryMatchArguments") + void testEntryMatch(String pattern, List owners, String path, boolean expectedResult) { + CharacterMatcher.Factory matcherFactory = new CharacterMatcher.Factory(); + Entry entry = new EntryBuilder(matcherFactory, pattern + " " + ownersToString(owners)).parse(); + Entry negatedEntry = + new EntryBuilder(matcherFactory, "!" + pattern + " " + ownersToString(owners)).parse(); + + boolean result = entry.getMatcher().consume(path.toCharArray(), 0) >= 0; + boolean negatedResult = negatedEntry.getMatcher().consume(path.toCharArray(), 0) >= 0; + + assertEquals(owners, entry.getOwners()); + assertEquals(expectedResult, result); + assertEquals(!expectedResult, negatedResult); + } + + static Stream testEntryMatchArguments() { + List owner = singletonList("owner"); + return Stream.of( + arguments("someToken", owner, "someToken", true), + arguments("someToken", owner, "sometoken", false), + arguments("someToken", owner, "anotherToken", false), + arguments("someToken", owner, "parent/someToken", true), + arguments("someToken", owner, "grandparent/parent/someToken", true), + arguments("/someToken", owner, "someToken", true), + arguments("/someToken", owner, "sometoken", false), + arguments("/someToken", owner, "anotherToken", false), + arguments("/someToken", owner, "parent/someToken", false), + arguments("/someToken", owner, "grandparent/parent/someToken", false), + arguments("someTok?n", owner, "someTokkn", true), + arguments("someTok?n", owner, "someTok/n", false), + arguments("someTok?n", owner, "parent/someTokkn", true), + arguments("someTok?n", owner, "grandparent/parent/someTokkn", true), + arguments("/someTok?n", owner, "someTokkn", true), + arguments("someTok*n", owner, "someTokkn", true), + arguments("someTok*n", owner, "someTok/n", false), + arguments("someTok*n", owner, "someTokkkkn", true), + arguments("someTok*n", owner, "someTokenToken", true), + arguments("someTok*n", owner, "someTokenTokkk", false), + arguments("s*meTok*n", owner, "someToken", true), + arguments("s*meTok*n", owner, "sabcdefghmeTokabcdefgn", true), + arguments("s*meTok*n", owner, "sabcdefghmeTokabcdefg", false), + arguments("someTok*n", owner, "parent/someTokkn", true), + arguments("someTok*n", owner, "grandparent/parent/someTokkn", true), + arguments("/someTok*n", owner, "someTokkn", true), + arguments("someTok[a-b]n", owner, "someToken", false), + arguments("someTok[a-z]n", owner, "someToken", true), + arguments("someTok[a-z]n", owner, "someTokEn", false), + arguments("someTok[a-z]n", owner, "someTok9n", false), + arguments("someTok[a-zA-Z]n", owner, "someToken", true), + arguments("someTok[a-zA-Z]n", owner, "someTokEn", true), + arguments("someTok[a-zA-Z]n", owner, "someTok9n", false), + arguments("someTok[a-zA-Z0-9]n", owner, "someToken", true), + arguments("someTok[a-zA-Z0-9]n", owner, "someTokEn", true), + arguments("someTok[a-zA-Z0-9]n", owner, "someTok9n", true), + arguments("s*meTok[a-zA-Z0-9]n", owner, "smeToken", true), + arguments("s*meTok[a-zA-Z0-9]n", owner, "smeTokEn", true), + arguments("s*meTok[a-zA-Z0-9]n", owner, "smeTok9n", true), + arguments("s*meTok[a-zA-Z0-9]n", owner, "sabcdmeToken", true), + arguments("s*meTok[a-zA-Z0-9]n", owner, "sabcdmeTokEn", true), + arguments("s*meTok[a-zA-Z0-9]n", owner, "sabcdmeTok9n", true), + arguments("someToken/", owner, "someToken", true), + arguments("someToken/", owner, "someToken/child", true), + arguments("someToken/", owner, "someToken/child/grandChild", true), + arguments("someToken/", owner, "parent/someToken", true), + arguments("someToken/", owner, "parent/someToken/child", true), + arguments("someToken/", owner, "parent/grandParent/someToken/child/grandChild", true), + arguments("/someToken/", owner, "someToken", true), + arguments("/someToken/", owner, "someToken/child", true), + arguments("/someToken/", owner, "someToken/child/grandChild", true), + arguments("/someToken/", owner, "parent/someToken", false), + arguments("/someToken/", owner, "parent/someToken/child", false), + arguments("/someToken/", owner, "parent/grandParent/someToken/child/grandChild", false), + arguments("some/token", owner, "some/token", true), + arguments("some/token", owner, "parent/some/token", false), + arguments("some/token", owner, "some/token/child", true), + arguments("some/token", owner, "some/tokenSuffix", false), + arguments("some/token/", owner, "some/token/child", true), + arguments("some/token/", owner, "parent/some/token/child", false), + arguments("some\\ Token", owner, "some Token", true), + arguments("\\#someToken", owner, "#someToken", true), + arguments("**/someToken", owner, "someToken", true), + arguments("**/someToken", owner, "parent/someToken", true), + arguments("**/someToken", owner, "grandparent/parent/someToken", true), + arguments("**/someToken", owner, "anotherToken", false), + arguments("**/some/token", owner, "some/token", true), + arguments("**/some/token", owner, "parent/some/token", true), + arguments("**/some/token", owner, "grandparent/parent/some/token", true), + arguments("**/some/token/", owner, "some/token", true), + arguments("**/some/token/", owner, "some/token/child", true), + arguments("**/some/token/", owner, "some/token/child/grandchild", true), + arguments("**/some/token/", owner, "parent/some/token/child", true), + arguments("**/some/token/", owner, "parent/some/different/token", false), + arguments("someToken/**", owner, "someToken", true), + arguments("someToken/**", owner, "someToken/child", true), + arguments("someToken/**", owner, "someToken/child/grandChild", true), + arguments("someToken/**", owner, "parent/someToken", false), + arguments("someToken/**", owner, "parent/someToken/child", false), + arguments("someToken/**", owner, "parent/grandParent/someToken/child/grandChild", false), + arguments("/someToken/**", owner, "someToken", true), + arguments("/someToken/**", owner, "someToken/child", true), + arguments("/someToken/**", owner, "someToken/child/grandChild", true), + arguments("/someToken/**", owner, "parent/someToken", false), + arguments("/someToken/**", owner, "parent/someToken/child", false), + arguments("/someToken/**", owner, "parent/grandParent/someToken/child/grandChild", false), + arguments("some/**/token", owner, "some/token", true), + arguments("some/**/token", owner, "some/other/token", true), + arguments("some/**/token", owner, "some/yet/another/token", true), + arguments("some/**/token", owner, "some/yet/another", false), + arguments("some/**/token", owner, "yet/another/token", false), + arguments("someToken", asList("owner", "anotherOwner"), "someToken", true), + arguments("*", owner, "someToken", true), + arguments("*", owner, "parent/someToken", true), + arguments("*", owner, "parent/someToken/child", true), + arguments("*", owner, "someFile.java", true), + arguments("**", owner, "someToken", true), + arguments("**", owner, "parent/someToken", true), + arguments("**", owner, "parent/someToken/child", true), + arguments("**", owner, "someFile.java", true), + arguments("*.java", owner, "someFile.java", true), + arguments("*.java", owner, "parent/someFile.java", true), + arguments("*.java", owner, "grandparent/parent/someFile.java", true), + arguments("*.java", owner, "someFile.js", false), + arguments("*.java", owner, "someFile.java17", false), + arguments("readme.md", owner, "readme.md", true), + arguments("readme.md", owner, "parent/readme.md", true), + arguments("readme.md", owner, "readme.md5", false), + arguments("some/*", owner, "some/token", true), + arguments("some/*", owner, "some/other/token", false), + arguments("some/*", owner, "parent/some/token", false), + arguments("some/*", owner, "parent/some/other/token", false), + arguments("**/test*.java", owner, "test.java", true), + arguments("**/test*.java", owner, "testFile.java", true), + arguments("**/test*.java", owner, "parent/test.java", true), + arguments("**/test*.java", owner, "parent/testFile.java", true), + arguments("**/test*.java", owner, "grandparent/parent/test.java", true), + arguments("**/test*.java", owner, "grandparent/parent/testFile.java", true), + arguments("\\#someToken", owner, "#someToken", true)); + } + + private static String ownersToString(Collection owners) { + StringBuilder result = new StringBuilder(); + for (String owner : owners) { + result.append(owner).append(" "); // trailing spaces will be ignored by the matcher + } + return result.toString(); + } + + @ParameterizedTest(name = "invalid entry parsing: \"{0}\"") + @MethodSource("testInvalidEntryParsingArguments") + void testInvalidEntryParsing(String entry) { + CharacterMatcher.Factory matcherFactory = new CharacterMatcher.Factory(); + Entry parsedEntry = new EntryBuilder(matcherFactory, entry).parse(); + assertNull(parsedEntry); + } + + static Stream testInvalidEntryParsingArguments() { + return Stream.of( + arguments("token[z-a] owner"), + arguments("# comment"), + arguments(" # comment with a leading space"), + arguments("[section header]"), + arguments(" [section header with a leading space]"), + arguments(""), + arguments(" "), + arguments("^[section header]"), + arguments("^[section header with] @default/owner"), + arguments(" ^[section header with a leading space]")); + } +}