From dd1e9f14a7827675009e85bce3f467a8a50585d7 Mon Sep 17 00:00:00 2001 From: Peco Stanoev Date: Tue, 5 Jan 2021 16:51:37 +0100 Subject: [PATCH] add ImportOption to include only tests This is useful for ArchUnit tests on test classes, e.g. test classes should not be public. Signed-off-by: Peco Stanoev Signed-off-by: Peter Gafert --- .../archunit/core/importer/ImportOption.java | 58 ++++++++--- .../core/importer/ImportOptionsTest.java | 98 +++++++++++++++---- 2 files changed, 123 insertions(+), 33 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportOption.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportOption.java index 5e24a4d54c..75fd781299 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportOption.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportOption.java @@ -15,13 +15,16 @@ */ package com.tngtech.archunit.core.importer; -import java.util.Set; import java.util.regex.Pattern; -import com.google.common.collect.ImmutableSet; +import com.google.common.base.Predicate; import com.tngtech.archunit.PublicAPI; +import static com.google.common.base.Predicates.not; +import static com.google.common.base.Predicates.or; import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE; +import static com.tngtech.archunit.core.importer.ImportOption.Predefined.NO_TEST_LOCATION; +import static com.tngtech.archunit.core.importer.ImportOption.Predefined.TEST_LOCATION; /** * Will be evaluated for every class location, to determine if the class should be imported.

@@ -47,6 +50,14 @@ public boolean includes(Location location) { return doNotIncludeTests.includes(location); } }, + ONLY_INCLUDE_TESTS { + private final OnlyIncludeTests onlyIncludeTests = new OnlyIncludeTests(); + + @Override + public boolean includes(Location location) { + return onlyIncludeTests.includes(location); + } + }, DO_NOT_INCLUDE_JARS { private final DoNotIncludeJars doNotIncludeJars = new DoNotIncludeJars(); @@ -65,30 +76,51 @@ public boolean includes(Location location) { public boolean includes(Location location) { return doNotIncludeArchives.includes(location); } + }; + + static final PatternPredicate MAVEN_TEST_PATTERN = new PatternPredicate(".*/target/test-classes/.*"); + static final PatternPredicate GRADLE_TEST_PATTERN = new PatternPredicate(".*/build/classes/([^/]+/)?test/.*"); + static final PatternPredicate INTELLIJ_TEST_PATTERN = new PatternPredicate(".*/out/test/classes/.*"); + static final Predicate TEST_LOCATION = or(MAVEN_TEST_PATTERN, GRADLE_TEST_PATTERN, INTELLIJ_TEST_PATTERN); + static final Predicate NO_TEST_LOCATION = not(TEST_LOCATION); + + private static class PatternPredicate implements Predicate { + private final Pattern pattern; + + PatternPredicate(String pattern) { + this.pattern = Pattern.compile(pattern); + } + + @Override + @SuppressWarnings("ConstantConditions") // ArchUnit never uses null as a valid parameter + public boolean apply(Location input) { + return input.matches(pattern); + } } } /** + * Best effort {@link ImportOption} to check rules only on main classes.
* NOTE: This excludes all class files residing in some directory * ../target/test-classes/.., ../build/classes/test/.. or ../build/classes/someLang/test/.. (Maven/Gradle standard). * Thus it is just a best guess, how tests can be identified, * in other environments, it might be necessary, to implement the correct {@link ImportOption} yourself. */ final class DoNotIncludeTests implements ImportOption { - private static final Pattern MAVEN_PATTERN = Pattern.compile(".*/target/test-classes/.*"); - private static final Pattern GRADLE_PATTERN = Pattern.compile(".*/build/classes/([^/]+/)?test/.*"); - private static final Pattern INTELLIJ_PATTERN = Pattern.compile(".*/out/test/classes/.*"); - - private static final Set EXCLUDED_PATTERN = ImmutableSet.of(MAVEN_PATTERN, GRADLE_PATTERN, INTELLIJ_PATTERN); + @Override + public boolean includes(Location location) { + return NO_TEST_LOCATION.apply(location); + } + } + /** + * Best effort {@link ImportOption} to check rules only on test classes.
+ * See {@link DoNotIncludeTests} for limitations of test class identification. + */ + final class OnlyIncludeTests implements ImportOption { @Override public boolean includes(Location location) { - for (Pattern pattern : EXCLUDED_PATTERN) { - if (location.matches(pattern)) { - return false; - } - } - return true; + return TEST_LOCATION.apply(location); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java index fd5ed6ddbd..a6b0cca2a6 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java @@ -3,10 +3,14 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import com.google.common.collect.ImmutableList; import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeArchives; import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeJars; import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeTests; +import com.tngtech.archunit.core.importer.ImportOption.OnlyIncludeTests; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; @@ -15,14 +19,17 @@ import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; +import static com.google.common.collect.ImmutableList.copyOf; +import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.getLast; import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_ARCHIVES; import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_JARS; import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_TESTS; +import static com.tngtech.archunit.core.importer.ImportOption.Predefined.ONLY_INCLUDE_TESTS; import static com.tngtech.java.junit.dataprovider.DataProviders.$; -import static com.tngtech.java.junit.dataprovider.DataProviders.$$; import static com.tngtech.java.junit.dataprovider.DataProviders.crossProduct; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; @RunWith(DataProviderRunner.class) @@ -46,33 +53,62 @@ public void excludes_test_class(ImportOption doNotIncludeTests) { } @DataProvider - public static Object[][] folders() { - return crossProduct(do_not_include_tests(), $$( + public static Object[][] only_include_tests() { + return testForEach(new OnlyIncludeTests(), ONLY_INCLUDE_TESTS); + } + + @Test + @UseDataProvider("only_include_tests") + public void excludes_main_class(ImportOption onlyIncludeTests) { + assertThat(onlyIncludeTests.includes(locationOf(OnlyIncludeTests.class))) + .as("includes production location").isFalse(); + + assertThat(onlyIncludeTests.includes(locationOf(getClass()))) + .as("includes test location").isTrue(); + } + + private static List getFolderPatterns() { + return ImmutableList.of( // Gradle - $(new String[]{"build", "classes", "test"}, false), - $(new String[]{"build", "classes", "java", "test"}, false), - $(new String[]{"build", "classes", "otherlang", "test"}, false), - $(new String[]{"build", "test-classes"}, true), - $(new String[]{"build", "classes", "main"}, true), - $(new String[]{"build", "classes", "java", "main"}, true), - $(new String[]{"build", "classes", "java", "main", "my", "test"}, true), + new FolderPattern("build", "classes", "test").expectTestFolder(), + new FolderPattern("build", "classes", "java", "test").expectTestFolder(), + new FolderPattern("build", "classes", "otherlang", "test").expectTestFolder(), + new FolderPattern("build", "test-classes").expectMainFolder(), + new FolderPattern("build", "classes", "main").expectMainFolder(), + new FolderPattern("build", "classes", "java", "main").expectMainFolder(), + new FolderPattern("build", "classes", "java", "main", "my", "test").expectMainFolder(), // Maven - $(new String[]{"target", "classes", "test"}, true), - $(new String[]{"target", "test-classes"}, false), - $(new String[]{"target", "classes"}, true), + new FolderPattern("target", "classes", "test").expectMainFolder(), + new FolderPattern("target", "test-classes").expectTestFolder(), + new FolderPattern("target", "classes").expectMainFolder(), // IntelliJ - $(new String[]{"out", "production", "classes"}, true), - $(new String[]{"out", "test", "classes"}, false), - $(new String[]{"out", "test", "classes", "my", "test"}, false), - $(new String[]{"out", "some", "classes"}, true) - )); + new FolderPattern("out", "production", "classes").expectMainFolder(), + new FolderPattern("out", "test", "classes").expectTestFolder(), + new FolderPattern("out", "test", "classes", "my", "test").expectTestFolder(), + new FolderPattern("out", "some", "classes").expectMainFolder() + ); + } + + @DataProvider + public static Object[][] test_location_predicates_and_expected_folder_patterns() { + List includeMainFolderInput = new ArrayList<>(); + List includeTestFolderInput = new ArrayList<>(); + for (FolderPattern folderPattern : getFolderPatterns()) { + includeMainFolderInput.add($(folderPattern.folders, folderPattern.isMainFolder)); + includeTestFolderInput.add($(folderPattern.folders, !folderPattern.isMainFolder)); + } + + Object[][] doNotIncludeTestsDataPoints = crossProduct(do_not_include_tests(), includeMainFolderInput.toArray(new Object[0][])); + Object[][] onlyIncludeTestsDataPoints = crossProduct(only_include_tests(), includeTestFolderInput.toArray(new Object[0][])); + + return copyOf(concat(asList(doNotIncludeTestsDataPoints), asList(onlyIncludeTestsDataPoints))).toArray(new Object[0][]); } @Test - @UseDataProvider("folders") - public void detects_all_output_folder_structures( + @UseDataProvider("test_location_predicates_and_expected_folder_patterns") + public void correctly_detects_all_output_folder_structures( ImportOption doNotIncludeTests, String[] folderName, boolean expectedInclude) throws IOException { File folder = temporaryFolder.newFolder(folderName); @@ -128,4 +164,26 @@ private static Location locationOf(Class clazz) { private static boolean comesFromJarArchive(Class clazz) { return LocationTest.urlOfClass(clazz).getProtocol().equals("jar"); } + + private static class FolderPattern { + final String[] folders; + final boolean isMainFolder; + + FolderPattern(String... folders) { + this(folders, false); + } + + private FolderPattern(String[] folders, boolean isMainFolder) { + this.folders = folders; + this.isMainFolder = isMainFolder; + } + + FolderPattern expectMainFolder() { + return new FolderPattern(folders, true); + } + + FolderPattern expectTestFolder() { + return new FolderPattern(folders, false); + } + } }