package com.tngtech.archunit.library; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.EvaluationResult; import com.tngtech.archunit.library.testclasses.first.any.pkg.FirstAnyFirstClass; import com.tngtech.archunit.library.testclasses.first.three.any.FirstThreeAnyFirstClass; import com.tngtech.archunit.library.testclasses.second.three.any.SecondThreeAnySecondClass; import com.tngtech.archunit.library.testclasses.some.pkg.SomeFirstClass; import com.tngtech.archunit.library.testclasses.some.pkg.sub.SomeSecondClass; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import static com.tngtech.archunit.library.Architectures.layeredArchitecture; import static java.lang.System.lineSeparator; import static java.util.regex.Pattern.quote; import static org.assertj.core.api.Assertions.assertThat; public class ArchitecturesTest { @Rule public final ExpectedException thrown = ExpectedException.none(); @Test public void description_of_layered_architecture() { Architectures.LayeredArchitecture architecture = layeredArchitecture() .layer("One").definedBy("some.pkg..") .layer("Two").definedBy("first.any.pkg..", "second.any.pkg..") .layer("Three").definedBy("..three..") .whereLayer("One").mayNotBeAccessedByAnyLayer() .whereLayer("Two").mayOnlyBeAccessedByLayers("One") .whereLayer("Three").mayOnlyBeAccessedByLayers("One", "Two"); assertThat(architecture.getDescription()).isEqualTo( "Layered architecture consisting of" + lineSeparator() + "layer 'One' ('some.pkg..')" + lineSeparator() + "layer 'Two' ('first.any.pkg..', 'second.any.pkg..')" + lineSeparator() + "layer 'Three' ('..three..')" + lineSeparator() + "where layer 'One' may not be accessed by any layer" + lineSeparator() + "where layer 'Two' may only be accessed by layers ['One']" + lineSeparator() + "where layer 'Three' may only be accessed by layers ['One', 'Two']"); } @Test public void overridden_description_of_layered_architecture() { Architectures.LayeredArchitecture architecture = layeredArchitecture() .layer("One").definedBy("some.pkg..") .whereLayer("One").mayNotBeAccessedByAnyLayer() .as("overridden"); assertThat(architecture.getDescription()).isEqualTo("overridden"); } @Test public void because_clause_on_layered_architecture() { ArchRule architecture = layeredArchitecture() .layer("One").definedBy("some.pkg..") .whereLayer("One").mayNotBeAccessedByAnyLayer() .as("overridden") .because("some reason"); assertThat(architecture.getDescription()).isEqualTo("overridden, because some reason"); } @Test public void defining_constraint_on_non_existing_layer_is_rejected() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("no layer"); thrown.expectMessage("Other"); layeredArchitecture() .layer("Some").definedBy("any") .whereLayer("Other").mayNotBeAccessedByAnyLayer(); } @Test public void gathers_all_layer_violations() { Architectures.LayeredArchitecture architecture = layeredArchitecture() .layer("One").definedBy(absolute("some.pkg..")) .layer("Two").definedBy(absolute("first.any.pkg..", "second.any.pkg..")) .layer("Three").definedBy(absolute("..three..")) .whereLayer("One").mayNotBeAccessedByAnyLayer() .whereLayer("Two").mayOnlyBeAccessedByLayers("One") .whereLayer("Three").mayOnlyBeAccessedByLayers("One", "Two"); JavaClasses classes = new ClassFileImporter().importPackages(getClass().getPackage().getName() + ".testclasses"); EvaluationResult result = architecture.evaluate(classes); assertPatternMatches(result.getFailureReport().getDetails(), ImmutableSet.of( expectedViolationPattern(FirstAnyFirstClass.class, "call", SomeSecondClass.class, "callMe"), expectedViolationPattern(SecondThreeAnySecondClass.class, "call", SomeFirstClass.class, "callMe"), expectedViolationPattern(FirstThreeAnyFirstClass.class, "call", FirstAnyFirstClass.class, "callMe"))); } private void assertPatternMatches(List<String> input, Set<String> expectedRegexes) { Set<String> toMatch = new HashSet<>(expectedRegexes); for (String line : input) { if (!matchIteratorAndRemove(toMatch, line)) { Assert.fail("Line " + line + " didn't match any pattern in " + expectedRegexes); } } } private boolean matchIteratorAndRemove(Set<String> toMatch, String line) { for (Iterator<String> toMatchIterator = toMatch.iterator(); toMatchIterator.hasNext(); ) { if (line.matches(toMatchIterator.next())) { toMatchIterator.remove(); return true; } } return false; } private String expectedViolationPattern(Class<?> from, String fromMethod, Class<?> to, String toMethod) { return String.format(".*%s.%s().*%s.%s().*", quote(from.getName()), fromMethod, quote(to.getName()), toMethod); } private String[] absolute(String... pkgSuffix) { List<String> result = new ArrayList<>(); for (String s : pkgSuffix) { String absolute = getClass().getPackage().getName() + ".testclasses." + s; result.add(absolute.replaceAll("\\.\\.\\.+", "..")); } return result.toArray(new String[result.size()]); } }