diff --git a/archunit/src/main/java/com/tngtech/archunit/library/GeneralCodingRules.java b/archunit/src/main/java/com/tngtech/archunit/library/GeneralCodingRules.java index ee6cbb6813..f78b0e9c47 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/GeneralCodingRules.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/GeneralCodingRules.java @@ -452,12 +452,16 @@ public static ArchRule testClassesShouldResideInTheSamePackageAsImplementation(S private static ArchCondition resideInTheSamePackageAsTheirTestClasses(String testClassSuffix) { return new ArchCondition("reside in the same package as their test classes") { Map> testClassesBySimpleClassName = new HashMap<>(); + Map> classesByPackageName = new HashMap<>(); @Override public void init(Collection allClasses) { testClassesBySimpleClassName = allClasses.stream() .filter(clazz -> clazz.getName().endsWith(testClassSuffix)) .collect(groupingBy(JavaClass::getSimpleName)); + + classesByPackageName = allClasses.stream() + .collect(groupingBy(JavaClass::getPackageName)); } @Override @@ -468,7 +472,8 @@ public void check(JavaClass implementationClass, ConditionEvents events) { List possibleTestClasses = testClassesBySimpleClassName.getOrDefault(possibleTestClassName, emptyList()); boolean isTestClassInWrongPackage = !possibleTestClasses.isEmpty() - && possibleTestClasses.stream().noneMatch(clazz -> clazz.getPackageName().equals(implementationClassPackageName)); + && possibleTestClasses.stream().noneMatch(clazz -> clazz.getPackageName().equals(implementationClassPackageName)) + && !allPossibleTestClassesHaveImplementationInRightPackage(possibleTestClasses); if (isTestClassInWrongPackage) { possibleTestClasses.forEach(wrongTestClass -> { @@ -478,6 +483,14 @@ public void check(JavaClass implementationClass, ConditionEvents events) { }); } } + + private boolean allPossibleTestClassesHaveImplementationInRightPackage(List possibleTestClasses) { + return possibleTestClasses.stream() + .allMatch(testClazz -> classesByPackageName.getOrDefault(testClazz.getPackageName(), emptyList()) + .stream() + .filter(clazz -> !testClazz.getSimpleName().equals(clazz.getSimpleName())) + .anyMatch(clazz -> testClazz.getSimpleName().startsWith(clazz.getSimpleName()))); + } }; } diff --git a/archunit/src/test/java/com/tngtech/archunit/library/GeneralCodingRulesTest.java b/archunit/src/test/java/com/tngtech/archunit/library/GeneralCodingRulesTest.java index 22ef0e6bf0..09c1493843 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/GeneralCodingRulesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/GeneralCodingRulesTest.java @@ -5,6 +5,7 @@ import com.tngtech.archunit.library.testclasses.packages.correct.defaultsuffix.ImplementationClassWithCorrectPackage; import com.tngtech.archunit.library.testclasses.packages.correct.notest.ImplementationClassWithoutTestClass; import com.tngtech.archunit.library.testclasses.packages.correct.onedirmatching.ImplementationClassWithOneTestPackageMatchingOutOfTwo; +import com.tngtech.archunit.library.testclasses.packages.correct.twoimplementationsonetestdir1.MultipleImplementationsWithOnlyOneTestAndInRightPackage; import com.tngtech.archunit.library.testclasses.packages.incorrect.nodirmatching.ImplementationClassWithMultipleTestsNotMatchingImplementationClassPackage; import com.tngtech.archunit.library.testclasses.packages.incorrect.wrongsubdir.customsuffix.ImplementationClassWithWrongTestClassPackageCustomSuffix; import com.tngtech.archunit.library.testclasses.packages.incorrect.wrongsubdir.customsuffix.subdir.ImplementationClassWithWrongTestClassPackageCustomSuffixTestingScenario; @@ -83,6 +84,16 @@ public void should_not_pass_when_none_of_multiple_matching_test_classes_resides_ ); } + @Test + public void should_pass_when_only_one_of_two_implementations_have_test_class_and_it_is_in_implementation_package() { + assertThatRule(testClassesShouldResideInTheSamePackageAsImplementation()) + .checking(new ClassFileImporter().importPackagesOf( + MultipleImplementationsWithOnlyOneTestAndInRightPackage.class, + com.tngtech.archunit.library.testclasses.packages.incorrect.twoimplementationsonetestdir2.MultipleImplementationsWithOnlyOneTestAndInRightPackage.class + )) + .hasNoViolation(); + } + @Test public void ASSERTIONS_SHOULD_HAVE_DETAIL_MESSAGE_should_fail_on_assert_without_detail_message() { @SuppressWarnings("unused") diff --git a/archunit/src/test/java/com/tngtech/archunit/library/testclasses/packages/correct/twoimplementationsonetestdir1/MultipleImplementationsWithOnlyOneTestAndInRightPackage.java b/archunit/src/test/java/com/tngtech/archunit/library/testclasses/packages/correct/twoimplementationsonetestdir1/MultipleImplementationsWithOnlyOneTestAndInRightPackage.java new file mode 100644 index 0000000000..b588c8b81a --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/library/testclasses/packages/correct/twoimplementationsonetestdir1/MultipleImplementationsWithOnlyOneTestAndInRightPackage.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.library.testclasses.packages.correct.twoimplementationsonetestdir1; + +public class MultipleImplementationsWithOnlyOneTestAndInRightPackage { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/library/testclasses/packages/correct/twoimplementationsonetestdir1/OnlyOneImplementationHaveTestAndItIsMatchingImplPackageTest.java b/archunit/src/test/java/com/tngtech/archunit/library/testclasses/packages/correct/twoimplementationsonetestdir1/OnlyOneImplementationHaveTestAndItIsMatchingImplPackageTest.java new file mode 100644 index 0000000000..ebd29a2a6f --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/library/testclasses/packages/correct/twoimplementationsonetestdir1/OnlyOneImplementationHaveTestAndItIsMatchingImplPackageTest.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.library.testclasses.packages.correct.twoimplementationsonetestdir1; + +public class OnlyOneImplementationHaveTestAndItIsMatchingImplPackageTest { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/library/testclasses/packages/incorrect/twoimplementationsonetestdir2/MultipleImplementationsWithOnlyOneTestAndInRightPackage.java b/archunit/src/test/java/com/tngtech/archunit/library/testclasses/packages/incorrect/twoimplementationsonetestdir2/MultipleImplementationsWithOnlyOneTestAndInRightPackage.java new file mode 100644 index 0000000000..6456b7d36d --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/library/testclasses/packages/incorrect/twoimplementationsonetestdir2/MultipleImplementationsWithOnlyOneTestAndInRightPackage.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.library.testclasses.packages.incorrect.twoimplementationsonetestdir2; + +public class MultipleImplementationsWithOnlyOneTestAndInRightPackage { +}