package com.tngtech.archunit.lang.syntax.elements; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.ArchConfiguration; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaAccess; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaCall; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.properties.HasName; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.EvaluationResult; import com.tngtech.archunit.lang.conditions.ArchConditions; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; import org.junit.Test; import org.junit.runner.RunWith; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.type; import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; import static com.tngtech.archunit.core.domain.JavaModifier.PRIVATE; import static com.tngtech.archunit.core.domain.JavaModifier.PROTECTED; import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC; import static com.tngtech.archunit.core.domain.TestUtils.importClasses; import static com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With.owner; import static com.tngtech.archunit.lang.conditions.ArchConditions.bePackagePrivate; import static com.tngtech.archunit.lang.conditions.ArchConditions.bePrivate; import static com.tngtech.archunit.lang.conditions.ArchConditions.beProtected; import static com.tngtech.archunit.lang.conditions.ArchConditions.bePublic; import static com.tngtech.archunit.lang.conditions.ArchConditions.haveModifier; import static com.tngtech.archunit.lang.conditions.ArchConditions.notBePackagePrivate; import static com.tngtech.archunit.lang.conditions.ArchConditions.notBePrivate; import static com.tngtech.archunit.lang.conditions.ArchConditions.notBeProtected; import static com.tngtech.archunit.lang.conditions.ArchConditions.notBePublic; import static com.tngtech.archunit.lang.conditions.ArchConditions.notHaveModifier; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.java.junit.dataprovider.DataProviders.$; import static com.tngtech.java.junit.dataprovider.DataProviders.$$; import static java.util.regex.Pattern.quote; import static org.assertj.core.api.Assertions.assertThat; @RunWith(DataProviderRunner.class) public class ClassesShouldTest { private static final String FAILURE_REPORT_NEWLINE_MARKER = "#"; @DataProvider public static Object[][] haveFullyQualifiedName_rules() { return $$( $(classes().should().haveFullyQualifiedName(RightNamedClass.class.getName())), $(classes().should(ArchConditions.haveFullyQualifiedName(RightNamedClass.class.getName()))) ); } @Test @UseDataProvider("haveFullyQualifiedName_rules") public void haveFullyQualifiedName(ArchRule rule) { EvaluationResult result = rule.evaluate(importClasses( RightNamedClass.class, WrongNamedClass.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should have fully qualified name '%s'", RightNamedClass.class.getName())) .contains(String.format("class %s doesn't have fully qualified name '%s'", WrongNamedClass.class.getName(), RightNamedClass.class.getName())) .doesNotMatch(String.format("%s .*name", RightNamedClass.class.getName())); } @DataProvider public static Object[][] notHaveFullyQualifiedName_rules() { return $$( $(classes().should().notHaveFullyQualifiedName(WrongNamedClass.class.getName())), $(classes().should(ArchConditions.notHaveFullyQualifiedName(WrongNamedClass.class.getName()))) ); } @Test @UseDataProvider("notHaveFullyQualifiedName_rules") public void notHaveFullyQualifiedName(ArchRule rule) { EvaluationResult result = rule.evaluate(importClasses( RightNamedClass.class, WrongNamedClass.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should not have fully qualified name '%s'", WrongNamedClass.class.getName())) .contains(String.format("%s has fully qualified name '%s'", WrongNamedClass.class.getName(), WrongNamedClass.class.getName())) .doesNotContain(String.format("%s .*name", RightNamedClass.class.getName())); } @DataProvider public static Object[][] haveSimpleName_rules() { return $$( $(classes().should().haveSimpleName(RightNamedClass.class.getSimpleName())), $(classes().should(ArchConditions.haveSimpleName(RightNamedClass.class.getSimpleName()))) ); } @Test @UseDataProvider("haveSimpleName_rules") public void haveSimpleName(ArchRule rule) { EvaluationResult result = rule.evaluate(importClasses( RightNamedClass.class, WrongNamedClass.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should have simple name '%s'", RightNamedClass.class.getSimpleName())) .contains(String.format("class %s doesn't have simple name '%s'", WrongNamedClass.class.getName(), RightNamedClass.class.getSimpleName())) .doesNotMatch(String.format(".*class %s .*simple name.*", RightNamedClass.class.getSimpleName())); } @DataProvider public static Object[][] notHaveSimpleName_rules() { return $$( $(classes().should().notHaveSimpleName(WrongNamedClass.class.getSimpleName())), $(classes().should(ArchConditions.notHaveSimpleName(WrongNamedClass.class.getSimpleName()))) ); } @Test @UseDataProvider("notHaveSimpleName_rules") public void notHaveSimpleName(ArchRule rule) { EvaluationResult result = rule.evaluate(importClasses( RightNamedClass.class, WrongNamedClass.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should not have simple name '%s'", WrongNamedClass.class.getSimpleName())) .contains(String.format("%s has simple name '%s'", WrongNamedClass.class.getName(), WrongNamedClass.class.getSimpleName())) .doesNotMatch(String.format(".*class %s .*simple name.*", RightNamedClass.class.getSimpleName())); } @DataProvider public static Object[][] haveNameMatching_rules() { String regex = containsPartOfRegex(RightNamedClass.class.getSimpleName()); return $$( $(classes().should().haveNameMatching(regex), regex), $(classes().should(ArchConditions.haveNameMatching(regex)), regex) ); } @Test @UseDataProvider("haveNameMatching_rules") public void haveNameMatching(ArchRule rule, String regex) { EvaluationResult result = rule.evaluate(importClasses( RightNamedClass.class, WrongNamedClass.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should have name matching '%s'", regex)) .contains(String.format("%s doesn't match '%s'", WrongNamedClass.class.getName(), regex)) .doesNotContain(String.format("%s", RightNamedClass.class.getSimpleName())); } @DataProvider public static Object[][] haveNameNotMatching_rules() { String regex = containsPartOfRegex(WrongNamedClass.class.getSimpleName()); return $$( $(classes().should().haveNameNotMatching(regex), regex), $(classes().should(ArchConditions.haveNameNotMatching(regex)), regex) ); } @Test @UseDataProvider("haveNameNotMatching_rules") public void haveNameNotMatching(ArchRule rule, String regex) { EvaluationResult result = rule.evaluate(importClasses( RightNamedClass.class, WrongNamedClass.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should have name not matching '%s'", regex)) .contains(String.format("%s matches '%s'", WrongNamedClass.class.getName(), regex)) .doesNotContain(String.format("%s", RightNamedClass.class.getSimpleName())); } @DataProvider public static Object[][] resideInAPackage_rules() { String thePackage = ArchRule.class.getPackage().getName(); return $$( $(classes().should().resideInAPackage(thePackage), thePackage), $(classes().should(ArchConditions.resideInAPackage(thePackage)), thePackage) ); } @Test @UseDataProvider("resideInAPackage_rules") public void resideInAPackage(ArchRule rule, String packageIdentifier) { checkTestStillValid(packageIdentifier, ImmutableSet.of(ArchRule.class, ArchCondition.class), ImmutableSet.<Class<?>>of(ArchConfiguration.class), ImmutableSet.<Class<?>>of(GivenObjects.class)); EvaluationResult result = rule.evaluate(importClasses( ArchRule.class, ArchCondition.class, ArchConfiguration.class, GivenObjects.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should reside in a package '%s'", packageIdentifier)) .contains(doesntResideInAPackageMessageFor(ArchConfiguration.class, packageIdentifier)) .contains(doesntResideInAPackageMessageFor(GivenObjects.class, packageIdentifier)) .doesNotContain(String.format("%s", ArchRule.class.getSimpleName())) .doesNotContain(String.format("%s", ArchCondition.class.getSimpleName())); } @DataProvider public static Object[][] resideInAnyPackage_rules() { String firstPackage = ArchRule.class.getPackage().getName(); String secondPackage = ArchConfiguration.class.getPackage().getName(); return $$( $(classes().should().resideInAnyPackage(firstPackage, secondPackage), new String[]{firstPackage, secondPackage}), $(classes().should(ArchConditions.resideInAnyPackage(firstPackage, secondPackage)), new String[]{firstPackage, secondPackage}) ); } @Test @UseDataProvider("resideInAnyPackage_rules") public void resideInAnyPackage(ArchRule rule, String... packageIdentifiers) { checkTestStillValid(packageIdentifiers, ImmutableSet.of(ArchRule.class, ArchConfiguration.class), ImmutableSet.<Class<?>>of(GivenObjects.class)); EvaluationResult result = rule.evaluate(importClasses( ArchRule.class, ArchConfiguration.class, GivenObjects.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should reside in any package ['%s']", Joiner.on("', '").join(packageIdentifiers))) .contains(doesntResideInAnyPackageMessageFor(GivenObjects.class, packageIdentifiers)) .doesNotContain(String.format("%s", ArchRule.class.getSimpleName())) .doesNotContain(String.format("%s", ArchConfiguration.class.getSimpleName())); } @DataProvider public static Object[][] resideOutsideOfPackage_rules() { String thePackage = ArchRule.class.getPackage().getName(); return $$( $(classes().should().resideOutsideOfPackage(thePackage), thePackage), $(classes().should(ArchConditions.resideOutsideOfPackage(thePackage)), thePackage) ); } @Test @UseDataProvider("resideOutsideOfPackage_rules") public void resideOutsideOfPackage(ArchRule rule, String packageIdentifier) { checkTestStillValid(packageIdentifier, ImmutableSet.of(ArchRule.class, ArchCondition.class), ImmutableSet.<Class<?>>of(ArchConfiguration.class), ImmutableSet.<Class<?>>of(GivenObjects.class)); EvaluationResult result = rule.evaluate(importClasses( ArchRule.class, ArchCondition.class, ArchConfiguration.class, GivenObjects.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should reside outside of package '%s'", packageIdentifier)) .contains(doesntResideOutsideOfPackageMessageFor(ArchRule.class, packageIdentifier)) .contains(doesntResideOutsideOfPackageMessageFor(ArchCondition.class, packageIdentifier)) .doesNotContain(String.format("%s", ArchConfiguration.class.getSimpleName())) .doesNotContain(String.format("%s", GivenObjects.class.getSimpleName())); } @DataProvider public static Object[][] resideOutsideOfPackages_rules() { String firstPackage = ArchRule.class.getPackage().getName(); String secondPackage = ArchConfiguration.class.getPackage().getName(); return $$( $(classes().should().resideOutsideOfPackages(firstPackage, secondPackage), new String[]{firstPackage, secondPackage}), $(classes().should(ArchConditions.resideOutsideOfPackages(firstPackage, secondPackage)), new String[]{firstPackage, secondPackage}) ); } @Test @UseDataProvider("resideOutsideOfPackages_rules") public void resideOutsideOfPackages(ArchRule rule, String... packageIdentifiers) { checkTestStillValid(packageIdentifiers, ImmutableSet.of(ArchRule.class, ArchConfiguration.class), ImmutableSet.<Class<?>>of(GivenObjects.class)); EvaluationResult result = rule.evaluate(importClasses( ArchRule.class, ArchCondition.class, ArchConfiguration.class, GivenObjects.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should reside outside of packages ['%s']", Joiner.on("', '").join(packageIdentifiers))) .contains(doesntResideOutsideOfPackagesMessageFor(ArchRule.class, packageIdentifiers)) .contains(doesntResideOutsideOfPackagesMessageFor(ArchCondition.class, packageIdentifiers)) .doesNotContain(String.format("%s", GivenObjects.class.getSimpleName())); } @DataProvider public static Object[][] visibility_rules() { return $$( $(classes().should().bePublic(), PUBLIC, PublicClass.class, PrivateClass.class), $(classes().should(bePublic()), PUBLIC, PublicClass.class, PrivateClass.class), $(classes().should().beProtected(), PROTECTED, ProtectedClass.class, PrivateClass.class), $(classes().should(beProtected()), PROTECTED, ProtectedClass.class, PrivateClass.class), $(classes().should().bePrivate(), PRIVATE, PrivateClass.class, PublicClass.class), $(classes().should(bePrivate()), PRIVATE, PrivateClass.class, PublicClass.class)); } @Test @UseDataProvider("visibility_rules") public void visibility(ArchRule rule, JavaModifier modifier, Class<?> satisfied, Class<?> violated) { EvaluationResult result = rule.evaluate(importClasses(satisfied, violated)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should be %s", modifier.name().toLowerCase())) .containsPattern(String.format("class %s .* modifier %s", quote(violated.getName()), modifier)) .doesNotMatch(String.format(".*class %s .* modifier %s.*", quote(satisfied.getName()), modifier)); } @DataProvider public static Object[][] not_visibility_rules() { return $$( $(classes().should().notBePublic(), PUBLIC, PrivateClass.class, PublicClass.class), $(classes().should(notBePublic()), PUBLIC, PrivateClass.class, PublicClass.class), $(classes().should().notBeProtected(), PROTECTED, PrivateClass.class, ProtectedClass.class), $(classes().should(notBeProtected()), PROTECTED, PrivateClass.class, ProtectedClass.class), $(classes().should().notBePrivate(), PRIVATE, PublicClass.class, PrivateClass.class), $(classes().should(notBePrivate()), PRIVATE, PublicClass.class, PrivateClass.class)); } @Test @UseDataProvider("not_visibility_rules") public void notVisibility(ArchRule rule, JavaModifier modifier, Class<?> satisfied, Class<?> violated) { EvaluationResult result = rule.evaluate(importClasses(satisfied, violated)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should not be %s", modifier.name().toLowerCase())) .containsPattern(String.format("class %s .* modifier %s", quote(violated.getName()), modifier)) .doesNotMatch(String.format(".*class %s .* modifier %s.*", quote(satisfied.getName()), modifier)); } @DataProvider public static Object[][] package_private_visibility_rules() { return $$( $(classes().should().bePackagePrivate(), "be package private"), $(classes().should(bePackagePrivate()), "be package private")); } @Test @UseDataProvider("package_private_visibility_rules") public void package_private_visibility(ArchRule rule, String description) { EvaluationResult result = rule.evaluate(importClasses(PackagePrivateClass.class, PrivateClass.class)); assertThat(singleLineFailureReportOf(result)) .contains("classes should " + description) .containsPattern(String.format("class %s .* modifier %s", quote(PrivateClass.class.getName()), PRIVATE)) .doesNotMatch(String.format(".*class %s .* modifier.*", quote(PackagePrivateClass.class.getName()))); } @DataProvider public static Object[][] non_package_private_visibility_rules() { return $$( $(classes().should().notBePackagePrivate(), "not be package private"), $(classes().should(notBePackagePrivate()), "not be package private")); } @Test @UseDataProvider("non_package_private_visibility_rules") public void non_package_private_visibility(ArchRule rule, String description) { EvaluationResult result = rule.evaluate(importClasses(PrivateClass.class, PackagePrivateClass.class)); assertThat(singleLineFailureReportOf(result)) .contains("classes should " + description) .contains(String.format("class %s", PackagePrivateClass.class.getName())) .contains("doesn't have modifier " + PUBLIC) .contains("doesn't have modifier " + PROTECTED) .contains("doesn't have modifier " + PRIVATE) .doesNotMatch(String.format(".*class %s .* modifier.*", quote(PrivateClass.class.getName()))); } @DataProvider public static Object[][] modifiers_rules() { return $$( $(classes().should().haveModifier(PUBLIC), "", PUBLIC, PublicClass.class, PrivateClass.class), $(classes().should(haveModifier(PUBLIC)), "", PUBLIC, PublicClass.class, PrivateClass.class), $(classes().should().notHaveModifier(PUBLIC), "not ", PUBLIC, PrivateClass.class, PublicClass.class), $(classes().should(notHaveModifier(PUBLIC)), "not ", PUBLIC, PrivateClass.class, PublicClass.class)); } @Test @UseDataProvider("modifiers_rules") public void modifiers(ArchRule rule, String havePrefix, JavaModifier modifier, Class<?> satisfied, Class<?> violated) { EvaluationResult result = rule.evaluate(importClasses(satisfied, violated)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should %shave modifier %s", havePrefix, modifier.name())) .containsPattern(String.format("class %s .* modifier %s", quote(violated.getName()), modifier)) .doesNotMatch(String.format(".*class %s .* modifier %s.*", quote(satisfied.getName()), modifier)); } @DataProvider public static Object[][] annotated_rules() { return $$( $(classes().should().beAnnotatedWith(SomeAnnotation.class), SomeAnnotatedClass.class, String.class), $(classes().should(ArchConditions.beAnnotatedWith(SomeAnnotation.class)), SomeAnnotatedClass.class, String.class), $(classes().should().beAnnotatedWith(SomeAnnotation.class.getName()), SomeAnnotatedClass.class, String.class), $(classes().should(ArchConditions.beAnnotatedWith(SomeAnnotation.class.getName())), SomeAnnotatedClass.class, String.class), $(classes().should().beAnnotatedWith(annotation(SomeAnnotation.class)), SomeAnnotatedClass.class, String.class), $(classes().should(ArchConditions.beAnnotatedWith(annotation(SomeAnnotation.class))), SomeAnnotatedClass.class, String.class)); } @Test @UseDataProvider("annotated_rules") public void annotatedWith(ArchRule rule, Class<?> correctClass, Class<?> wrongClass) { EvaluationResult result = rule.evaluate(importClasses(correctClass, wrongClass)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should be annotated with @%s", SomeAnnotation.class.getSimpleName())) .contains(String.format("class %s is not annotated with @%s", wrongClass.getName(), SomeAnnotation.class.getSimpleName())) .doesNotMatch(String.format(".*%s.*annotated.*", quote(correctClass.getName()))); } @DataProvider public static Object[][] notAnnotated_rules() { return $$( $(classes().should().notBeAnnotatedWith(SomeAnnotation.class), String.class, SomeAnnotatedClass.class), $(classes().should(ArchConditions.notBeAnnotatedWith(SomeAnnotation.class)), String.class, SomeAnnotatedClass.class), $(classes().should().notBeAnnotatedWith(SomeAnnotation.class.getName()), String.class, SomeAnnotatedClass.class), $(classes().should(ArchConditions.notBeAnnotatedWith(SomeAnnotation.class.getName())), String.class, SomeAnnotatedClass.class), $(classes().should().notBeAnnotatedWith(annotation(SomeAnnotation.class)), String.class, SomeAnnotatedClass.class), $(classes().should(ArchConditions.notBeAnnotatedWith(annotation(SomeAnnotation.class))), String.class, SomeAnnotatedClass.class)); } @Test @UseDataProvider("notAnnotated_rules") public void notAnnotatedWith(ArchRule rule, Class<?> correctClass, Class<?> wrongClass) { EvaluationResult result = rule.evaluate(importClasses(correctClass, wrongClass)); assertThat(singleLineFailureReportOf(result)) .contains("classes should not be annotated with @" + SomeAnnotation.class.getSimpleName()) .contains(String.format("class %s is annotated with @%s", wrongClass.getName(), SomeAnnotation.class.getSimpleName())) .doesNotMatch(String.format(".*%s.*annotated.*", quote(correctClass.getName()))); } @DataProvider public static Object[][] implement_rules() { return $$( $(classes().should().implement(Collection.class), ArrayList.class, List.class), $(classes().should(ArchConditions.implement(Collection.class)), ArrayList.class, List.class), $(classes().should().implement(Collection.class.getName()), ArrayList.class, List.class), $(classes().should(ArchConditions.implement(Collection.class.getName())), ArrayList.class, List.class), $(classes().should().implement(getNameIsEqualToNameOf(Collection.class)), ArrayList.class, List.class), $(classes().should(ArchConditions.implement(getNameIsEqualToNameOf(Collection.class))), ArrayList.class, List.class)); } @Test @UseDataProvider("implement_rules") public void implement(ArchRule rule, Class<?> satisfied, Class<?> violated) { EvaluationResult result = rule.evaluate(importClasses(satisfied, violated)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should implement %s", Collection.class.getName())) .contains(String.format("class %s doesn't implement %s", violated.getName(), Collection.class.getName())) .doesNotMatch(String.format(".*class %s .* implement.*", quote(satisfied.getName()))); } @DataProvider public static Object[][] notImplement_rules() { return $$( $(classes().should().notImplement(Collection.class), List.class, ArrayList.class), $(classes().should(ArchConditions.notImplement(Collection.class)), List.class, ArrayList.class), $(classes().should().notImplement(Collection.class.getName()), List.class, ArrayList.class), $(classes().should(ArchConditions.notImplement(Collection.class.getName())), List.class, ArrayList.class), $(classes().should().notImplement(getNameIsEqualToNameOf(Collection.class)), List.class, ArrayList.class), $(classes().should(ArchConditions.notImplement(getNameIsEqualToNameOf(Collection.class))), List.class, ArrayList.class)); } @Test @UseDataProvider("notImplement_rules") public void notImplement(ArchRule rule, Class<?> satisfied, Class<?> violated) { EvaluationResult result = rule.evaluate(importClasses(satisfied, violated)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should not implement %s", Collection.class.getName())) .contains(String.format("class %s implements %s", violated.getName(), Collection.class.getName())) .doesNotMatch(String.format(".*class %s .* implement.*", quote(satisfied.getName()))); } @DataProvider public static Object[][] assignableTo_rules() { return $$( $(classes().should().beAssignableTo(Collection.class), List.class, String.class), $(classes().should(ArchConditions.beAssignableTo(Collection.class)), List.class, String.class), $(classes().should().beAssignableTo(Collection.class.getName()), List.class, String.class), $(classes().should(ArchConditions.beAssignableTo(Collection.class.getName())), List.class, String.class), $(classes().should().beAssignableTo(getNameIsEqualToNameOf(Collection.class)), List.class, String.class), $(classes().should(ArchConditions.beAssignableTo(getNameIsEqualToNameOf(Collection.class))), List.class, String.class)); } @Test @UseDataProvider("assignableTo_rules") public void assignableTo(ArchRule rule, Class<?> satisfied, Class<?> violated) { EvaluationResult result = rule.evaluate(importClasses(satisfied, violated)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should be assignable to %s", Collection.class.getName())) .contains(String.format("class %s is not assignable to %s", violated.getName(), Collection.class.getName())) .doesNotMatch(String.format(".*class %s .* assignable.*", quote(satisfied.getName()))); } @DataProvider public static Object[][] notAssignableTo_rules() { return $$( $(classes().should().notBeAssignableTo(Collection.class), String.class, List.class), $(classes().should(ArchConditions.notBeAssignableTo(Collection.class)), String.class, List.class), $(classes().should().notBeAssignableTo(Collection.class.getName()), String.class, List.class), $(classes().should(ArchConditions.notBeAssignableTo(Collection.class.getName())), String.class, List.class), $(classes().should().notBeAssignableTo(getNameIsEqualToNameOf(Collection.class)), String.class, List.class), $(classes().should(ArchConditions.notBeAssignableTo(getNameIsEqualToNameOf(Collection.class))), String.class, List.class)); } @Test @UseDataProvider("notAssignableTo_rules") public void notAssignableTo(ArchRule rule, Class<?> satisfied, Class<?> violated) { EvaluationResult result = rule.evaluate(importClasses(satisfied, violated)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should not be assignable to %s", Collection.class.getName())) .contains(String.format("class %s is assignable to %s", violated.getName(), Collection.class.getName())) .doesNotMatch(String.format(".*class %s .* assignable.*", quote(satisfied.getName()))); } @DataProvider public static Object[][] assignableFrom_rules() { return $$( $(classes().should().beAssignableFrom(List.class), Collection.class, String.class), $(classes().should(ArchConditions.beAssignableFrom(List.class)), Collection.class, String.class), $(classes().should().beAssignableFrom(List.class.getName()), Collection.class, String.class), $(classes().should(ArchConditions.beAssignableFrom(List.class.getName())), Collection.class, String.class), $(classes().should().beAssignableFrom(getNameIsEqualToNameOf(List.class)), Collection.class, String.class), $(classes().should(ArchConditions.beAssignableFrom(getNameIsEqualToNameOf(List.class))), Collection.class, String.class)); } @Test @UseDataProvider("assignableFrom_rules") public void assignableFrom(ArchRule rule, Class<?> satisfied, Class<?> violated) { EvaluationResult result = rule.evaluate(importClasses(satisfied, violated)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should be assignable from %s", List.class.getName())) .contains(String.format("class %s is not assignable from %s", violated.getName(), List.class.getName())) .doesNotMatch(String.format(".*class %s .* assignable.*", quote(satisfied.getName()))); } @DataProvider public static Object[][] notAssignableFrom_rules() { return $$( $(classes().should().notBeAssignableFrom(List.class), String.class, Collection.class), $(classes().should(ArchConditions.notBeAssignableFrom(List.class)), String.class, Collection.class), $(classes().should().notBeAssignableFrom(List.class.getName()), String.class, Collection.class), $(classes().should(ArchConditions.notBeAssignableFrom(List.class.getName())), String.class, Collection.class), $(classes().should().notBeAssignableFrom(getNameIsEqualToNameOf(List.class)), String.class, Collection.class), $(classes().should(ArchConditions.notBeAssignableFrom(getNameIsEqualToNameOf(List.class))), String.class, Collection.class)); } @Test @UseDataProvider("notAssignableFrom_rules") public void notAssignableFrom(ArchRule rule, Class<?> satisfied, Class<?> violated) { EvaluationResult result = rule.evaluate(importClasses(satisfied, violated)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should not be assignable from %s", List.class.getName())) .contains(String.format("class %s is assignable from %s", violated.getName(), List.class.getName())) .doesNotMatch(String.format(".*class %s .* assignable.*", quote(satisfied.getName()))); } @DataProvider public static Object[][] accessField_rules() { return $$( $(classes().should().getField(ClassWithField.class, "field"), "get", "gets"), $(classes().should(ArchConditions.getField(ClassWithField.class, "field")), "get", "gets"), $(classes().should().getField(ClassWithField.class.getName(), "field"), "get", "gets"), $(classes().should(ArchConditions.getField(ClassWithField.class.getName(), "field")), "get", "gets"), $(classes().should().setField(ClassWithField.class, "field"), "set", "sets"), $(classes().should(ArchConditions.setField(ClassWithField.class, "field")), "set", "sets"), $(classes().should().setField(ClassWithField.class.getName(), "field"), "set", "sets"), $(classes().should(ArchConditions.setField(ClassWithField.class.getName(), "field")), "set", "sets"), $(classes().should().accessField(ClassWithField.class, "field"), "access", "accesses"), $(classes().should(ArchConditions.accessField(ClassWithField.class, "field")), "access", "accesses"), $(classes().should().accessField(ClassWithField.class.getName(), "field"), "access", "accesses"), $(classes().should(ArchConditions.accessField(ClassWithField.class.getName(), "field")), "access", "accesses") ); } @Test @UseDataProvider("accessField_rules") public void accessField(ArchRule rule, String accessTypePlural, String accessTypeSingular) { EvaluationResult result = rule.evaluate(importClasses( ClassWithField.class, ClassAccessingField.class, ClassAccessingWrongField.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should %s field %s.%s", accessTypePlural, ClassWithField.class.getSimpleName(), "field")) .containsPattern(accessesFieldRegex( ClassAccessingWrongField.class, accessTypeSingular, ClassAccessingField.class, "classWithField")) .doesNotMatch(accessesFieldRegex( ClassAccessingField.class, accessTypeSingular, ClassWithField.class, "field")); } @DataProvider public static Object[][] accessFieldWhere_rules() { return $$( $(classes().should().getFieldWhere(accessTargetIs(ClassWithField.class)), "get", "gets"), $(classes().should(ArchConditions.getFieldWhere(accessTargetIs(ClassWithField.class))), "get", "gets"), $(classes().should().setFieldWhere(accessTargetIs(ClassWithField.class)), "set", "sets"), $(classes().should(ArchConditions.setFieldWhere(accessTargetIs(ClassWithField.class))), "set", "sets"), $(classes().should().accessFieldWhere(accessTargetIs(ClassWithField.class)), "access", "accesses"), $(classes().should(ArchConditions.accessFieldWhere(accessTargetIs(ClassWithField.class))), "access", "accesses") ); } @Test @UseDataProvider("accessFieldWhere_rules") public void accessFieldWhere(ArchRule rule, String accessTypePlural, String accessTypeSingular) { EvaluationResult result = rule.evaluate(importClasses( ClassWithField.class, ClassAccessingField.class, ClassAccessingWrongField.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should %s field where target is %s", accessTypePlural, ClassWithField.class.getSimpleName())) .containsPattern(accessesFieldRegex( ClassAccessingWrongField.class, accessTypeSingular, ClassAccessingField.class, "classWithField")) .doesNotMatch(accessesFieldRegex( ClassAccessingField.class, accessTypeSingular, ClassWithField.class, "field")); } @DataProvider public static Object[][] callMethod_rules() { return $$( $(classes().should().callMethod(ClassWithMethod.class, "method", String.class)), $(classes().should(ArchConditions.callMethod(ClassWithMethod.class, "method", String.class))), $(classes().should().callMethod(ClassWithMethod.class.getName(), "method", String.class.getName())), $(classes().should(ArchConditions.callMethod(ClassWithMethod.class.getName(), "method", String.class.getName()))) ); } @Test @UseDataProvider("callMethod_rules") public void callMethod(ArchRule rule) { EvaluationResult result = rule.evaluate(importClasses( ClassWithMethod.class, ClassCallingMethod.class, ClassCallingWrongMethod.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should call method %s.%s(%s)", ClassWithMethod.class.getSimpleName(), "method", String.class.getSimpleName())) .containsPattern(callMethodRegex( ClassCallingWrongMethod.class, ClassCallingMethod.class, "call")) .doesNotMatch(callMethodRegex( ClassCallingMethod.class, ClassWithMethod.class, "method", String.class)); } @DataProvider public static Object[][] callMethodWhere_rules() { return $$( $(classes().should().callMethodWhere(callTargetIs(ClassWithMethod.class))), $(classes().should(ArchConditions.callMethodWhere(callTargetIs(ClassWithMethod.class)))) ); } @Test @UseDataProvider("callMethodWhere_rules") public void callMethodWhere(ArchRule rule) { EvaluationResult result = rule.evaluate(importClasses( ClassWithMethod.class, ClassCallingMethod.class, ClassCallingWrongMethod.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should call method where target is %s", ClassWithMethod.class.getSimpleName())) .containsPattern(callMethodRegex( ClassCallingWrongMethod.class, ClassCallingMethod.class, "call")) .doesNotMatch(callMethodRegex( ClassCallingMethod.class, ClassWithMethod.class, "method", String.class)); } @DataProvider public static Object[][] callConstructor_rules() { return $$( $(classes().should().callConstructor(ClassWithConstructor.class, String.class)), $(classes().should(ArchConditions.callConstructor(ClassWithConstructor.class, String.class))), $(classes().should().callConstructor(ClassWithConstructor.class.getName(), String.class.getName())), $(classes().should(ArchConditions.callConstructor(ClassWithConstructor.class.getName(), String.class.getName()))) ); } @Test @UseDataProvider("callConstructor_rules") public void callConstructor(ArchRule rule) { EvaluationResult result = rule.evaluate(importClasses( ClassWithConstructor.class, ClassCallingConstructor.class, ClassCallingWrongConstructor.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should call constructor %s.<init>(%s)", ClassWithConstructor.class.getSimpleName(), String.class.getSimpleName())) .containsPattern(callConstructorRegex( ClassCallingWrongConstructor.class, ClassCallingConstructor.class, int.class, Date.class)) .doesNotMatch(callConstructorRegex( ClassCallingConstructor.class, ClassWithConstructor.class, String.class)); } @DataProvider public static Object[][] callConstructorWhere_rules() { return $$( $(classes().should().callConstructorWhere(callTargetIs(ClassWithConstructor.class))), $(classes().should(ArchConditions.callConstructorWhere(callTargetIs(ClassWithConstructor.class)))) ); } @Test @UseDataProvider("callConstructorWhere_rules") public void callConstructorWhere(ArchRule rule) { EvaluationResult result = rule.evaluate(importClasses( ClassWithConstructor.class, ClassCallingConstructor.class, ClassCallingWrongConstructor.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should call constructor where target is %s", ClassWithConstructor.class.getSimpleName())) .containsPattern(callConstructorRegex( ClassCallingWrongConstructor.class, ClassCallingConstructor.class, int.class, Date.class)) .doesNotMatch(callConstructorRegex( ClassCallingConstructor.class, ClassWithConstructor.class, String.class)); } @DataProvider public static Object[][] accessTargetWhere_rules() { return $$( $(classes().should().accessTargetWhere(accessTargetIs(ClassWithFieldMethodAndConstructor.class))), $(classes().should(ArchConditions.accessTargetWhere(accessTargetIs(ClassWithFieldMethodAndConstructor.class)))) ); } @Test @UseDataProvider("accessTargetWhere_rules") public void accessTargetWhere(ArchRule rule) { EvaluationResult result = rule.evaluate(importClasses( ClassWithFieldMethodAndConstructor.class, ClassAccessingFieldMethodAndConstructor.class, ClassAccessingWrongFieldMethodAndConstructor.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should access target where target is %s", ClassWithFieldMethodAndConstructor.class.getSimpleName())) .containsPattern(accessTargetRegex( ClassAccessingWrongFieldMethodAndConstructor.class, ClassAccessingFieldMethodAndConstructor.class, "wrongField")) .containsPattern(accessTargetRegex( ClassAccessingWrongFieldMethodAndConstructor.class, ClassAccessingFieldMethodAndConstructor.class, CONSTRUCTOR_NAME)) .containsPattern(accessTargetRegex( ClassAccessingWrongFieldMethodAndConstructor.class, ClassAccessingFieldMethodAndConstructor.class, "call")) .doesNotMatch(accessTargetRegex( ClassAccessingFieldMethodAndConstructor.class, ClassWithFieldMethodAndConstructor.class, "")); } @DataProvider public static Object[][] callCodeUnitWhere_rules() { return $$( $(classes().should().callCodeUnitWhere(accessTargetIs(ClassWithFieldMethodAndConstructor.class))), $(classes().should(ArchConditions.callCodeUnitWhere(accessTargetIs(ClassWithFieldMethodAndConstructor.class)))) ); } @Test @UseDataProvider("callCodeUnitWhere_rules") public void callCodeUnitWhere(ArchRule rule) { EvaluationResult result = rule.evaluate(importClasses( ClassWithFieldMethodAndConstructor.class, ClassAccessingFieldMethodAndConstructor.class, ClassAccessingWrongFieldMethodAndConstructor.class)); assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should call code unit where target is %s", ClassWithFieldMethodAndConstructor.class.getSimpleName())) .containsPattern(callCodeUnitRegex( ClassAccessingWrongFieldMethodAndConstructor.class, ClassAccessingFieldMethodAndConstructor.class, CONSTRUCTOR_NAME, int.class, Date.class)) .containsPattern(callCodeUnitRegex( ClassAccessingWrongFieldMethodAndConstructor.class, ClassAccessingFieldMethodAndConstructor.class, "call")) .doesNotMatch(callCodeUnitRegex( ClassAccessingWrongFieldMethodAndConstructor.class, ClassAccessingFieldMethodAndConstructor.class, "wrongField")) .doesNotMatch(callCodeUnitRegex( ClassAccessingFieldMethodAndConstructor.class, ClassWithFieldMethodAndConstructor.class, "")); } private String singleLineFailureReportOf(EvaluationResult result) { return result.getFailureReport().toString().replaceAll("\\r?\\n", FAILURE_REPORT_NEWLINE_MARKER); } private static DescribedPredicate<JavaAnnotation> annotation(final Class<? extends Annotation> type) { return new DescribedPredicate<JavaAnnotation>("@" + type.getSimpleName()) { @Override public boolean apply(JavaAnnotation input) { return input.getType().getName().equals(type.getName()); } }; } private static String containsPartOfRegex(String fullString) { return String.format(".*%s.*", fullString.substring(1, fullString.length() - 1)); } private String doesntResideInAPackageMessageFor(Class<?> clazz, String packageIdentifier) { return String.format("%s doesn't reside in a package '%s'", clazz.getName(), packageIdentifier); } private String doesntResideOutsideOfPackageMessageFor(Class<?> clazz, String packageIdentifier) { return String.format("%s doesn't reside outside of package '%s'", clazz.getName(), packageIdentifier); } private String doesntResideInAnyPackageMessageFor(Class<?> clazz, String[] packageIdentifiers) { return String.format("%s doesn't reside in any package ['%s']", clazz.getName(), Joiner.on("', '").join(packageIdentifiers)); } private String doesntResideOutsideOfPackagesMessageFor(Class<?> clazz, String[] packageIdentifiers) { return String.format("%s doesn't reside outside of packages ['%s']", clazz.getName(), Joiner.on("', '").join(packageIdentifiers)); } private static DescribedPredicate<JavaCall<?>> callTargetIs(Class<?> type) { return JavaCall.Predicates.target(owner(type(type))).as("target is " + type.getSimpleName()); } private static DescribedPredicate<JavaAccess<?>> accessTargetIs(Class<?> type) { return JavaAccess.Predicates.target(owner(type(type))).as("target is " + type.getSimpleName()); } private static DescribedPredicate<HasName> getNameIsEqualToNameOf(Class<?> type) { return HasName.Functions.GET_NAME.is(DescribedPredicate.equalTo(type.getName())).as(type.getName()); } private void checkTestStillValid(String[] packages, Set<Class<?>> inSomePackage, Set<Class<?>> notInAnyPackage) { for (Class<?> c : inSomePackage) { assertThat(packageMatches(c, packages)).as("Package matches").isTrue(); } for (Class<?> c : notInAnyPackage) { assertThat(packageMatches(c, packages)).as("Package matches").isFalse(); } } private boolean packageMatches(Class<?> c, String[] packages) { for (String p : packages) { if (c.getPackage().getName().equals(p)) { return true; } } return false; } private void checkTestStillValid(String thePackage, Set<Class<?>> inPackage, Set<Class<?>> inSuperPackage, Set<Class<?>> inSubPackage) { for (Class<?> c : inPackage) { assertThat(c.getPackage().getName()).isEqualTo(thePackage); } for (Class<?> c : inSuperPackage) { assertThat(thePackage).startsWith(c.getPackage().getName()); } for (Class<?> c : inSubPackage) { assertThat(c.getPackage().getName()).startsWith(thePackage); } } private Pattern accessesFieldRegex(Class<?> origin, String accessType, Class<?> targetClass, String fieldName) { String originAccesses = String.format("%s[^%s]* %s", quote(origin.getName()), FAILURE_REPORT_NEWLINE_MARKER, accessType); String target = String.format("[^%s]*%s\\.%s", FAILURE_REPORT_NEWLINE_MARKER, quote(targetClass.getName()), fieldName); return Pattern.compile(String.format(".*%s field %s.*", originAccesses, target)); } private Pattern callConstructorRegex(Class<?> origin, Class<?> targetClass, Class<?>... paramTypes) { return callRegex(origin, targetClass, "constructor", CONSTRUCTOR_NAME, paramTypes); } private Pattern callMethodRegex(Class<?> origin, Class<?> targetClass, String methodName, Class<?>... paramTypes) { return callRegex(origin, targetClass, "method", methodName, paramTypes); } private Pattern callCodeUnitRegex(Class<?> origin, Class<?> targetClass, String methodName, Class<?>... paramTypes) { return callRegex(origin, targetClass, "(method|constructor)", methodName, paramTypes); } private Pattern callRegex(Class<?> origin, Class<?> targetClass, String targetType, String methodName, Class<?>... paramTypes) { String params = Joiner.on(", ").join(JavaClass.namesOf(paramTypes)); String originCalls = String.format("%s[^%s]* calls", quote(origin.getName()), FAILURE_REPORT_NEWLINE_MARKER); String target = String.format("[^%s]*%s\\.%s\\(%s\\)", FAILURE_REPORT_NEWLINE_MARKER, quote(targetClass.getName()), methodName, quote(params)); return Pattern.compile(String.format(".*%s %s %s.*", originCalls, targetType, target)); } private Pattern accessTargetRegex(Class<?> origin, Class<?> targetClass, String memberName) { String originAccesses = String.format("%s[^%s]* (accesses|calls|sets|gets)", quote(origin.getName()), FAILURE_REPORT_NEWLINE_MARKER); String target = String.format("[^%s]*%s\\.%s", FAILURE_REPORT_NEWLINE_MARKER, quote(targetClass.getName()), memberName); return Pattern.compile(String.format(".*%s (field|method|constructor) %s.*", originAccesses, target)); } private static class RightNamedClass { } private static class WrongNamedClass { } private static class ClassWithField { String field; } private static class ClassAccessingField { ClassWithField classWithField; String access() { classWithField.field = "new"; return classWithField.field; } } private static class ClassAccessingWrongField { ClassAccessingField classAccessingField; ClassWithField wrongAccess() { classAccessingField.classWithField = null; return classAccessingField.classWithField; } } private static class ClassWithMethod { void method(String param) { } } private static class ClassCallingMethod { ClassWithMethod classWithMethod; void call() { classWithMethod.method("param"); } } private static class ClassCallingWrongMethod { ClassCallingMethod classCallingMethod; void callWrong() { classCallingMethod.call(); } } private static class ClassWithConstructor { ClassWithConstructor(String param) { } } private static class ClassCallingConstructor { ClassCallingConstructor(int number, Date date) { } void call() { new ClassWithConstructor("param"); } } private static class ClassCallingWrongConstructor { void callWrong() { new ClassCallingConstructor(0, null); } } private static class ClassWithFieldMethodAndConstructor { String field; ClassWithFieldMethodAndConstructor(String param) { } void method(String param) { } } private static class ClassAccessingFieldMethodAndConstructor { String wrongField; ClassAccessingFieldMethodAndConstructor(int number, Date date) { } void call() { ClassWithFieldMethodAndConstructor instance = new ClassWithFieldMethodAndConstructor("param"); instance.field = "field"; instance.method("param"); } } private static class ClassAccessingWrongFieldMethodAndConstructor { void callWrong() { ClassAccessingFieldMethodAndConstructor instance = new ClassAccessingFieldMethodAndConstructor(0, null); instance.wrongField = "field"; instance.call(); } } public static class PublicClass { } protected static class ProtectedClass { } static class PackagePrivateClass { } private static class PrivateClass { } @Retention(RetentionPolicy.RUNTIME) private @interface SomeAnnotation { } @SomeAnnotation private static class SomeAnnotatedClass {} }