tng / archunit Goto Github PK
View Code? Open in Web Editor NEWA Java architecture test library, to specify and assert architecture rules in plain Java
Home Page: http://archunit.org
License: Apache License 2.0
A Java architecture test library, to specify and assert architecture rules in plain Java
Home Page: http://archunit.org
License: Apache License 2.0
without the ".check( classes )" it is not acutally testing what one might think
if you approve #30 (self contained build for archunit-example), then a brief readme
might be helpful for newbie users (like myself).
I want to check that no class prints exception stack traces directly to stderr. My attempt was this:
class ExampleTest {
@Test
void noMethodShouldCall_printStackTrace() {
JavaClasses classes = new ClassFileImporter().importClasses(Example.class);
noClasses().should().callMethod(Throwable.class, "printStackTrace").check(classes);
}
static class Example {
void printStackTraceOfException() {
try {
throw new Exception("Hello World");
} catch (Exception e) {
e.printStackTrace();
}
}
void printStackTraceOfRuntimeException() {
try {
throw new RuntimeException("Hello World");
} catch (RuntimeException e) {
e.printStackTrace();
}
}
void logErrorOnException() {
try {
throw new Exception("Hello World");
} catch (Exception e) {
// log.error(...)
}
}
}
}
Actual: The check is green.
Expectation: The check fails for printStackTraceOfException
and printStackTraceOfRuntimeException
.
If I replace to callMethod(Exception.class, "printStackTrace")
then the check fails for printStackTraceOfException
but not for printStackTraceOfRuntimeException
.
With Java 9, the application class loader isn't an instance of URLClassLoader any more, but ArchUnit searches for a class loader of this type.
So ArchUnit doesn't find any classes in class path.
So no classes are checked and all tests pass successfully.
ArchUnit must be modified to support both Java 8 or lower and Java 9 class loading mechanism.
While there is a predicate syntax
classes().that().implement(foo)
the only way to filter for interfaces is using
classes().that(are(JavaClass.Predicates.INTERFACES))
The fluent API should allow filtering for interfaces
classes().that().areInterfaces()
classes().that().areNoInterfaces()
as well as asserting interfaces
classes().should().beInterfaces()
classes().should().notBeInterfaces()
When running 'gradle test' on the archunit-example directory, I get:
53 tests, 0 failures, 53 ignored.
As an ArchUnit-user, I don't want to test the whole library, but only a (small) example...
(this issue belongs to the more general #22 )
At the company we work, we want to isolate a package gradually to reach a point where it can be taken to its own JAR. That's why we want to avoid this package to access others in the project in the meantime and I want to use ArchUnit to force this, but I have a problem with the enumerations within the package.
Maybe I'm doing it wrong, or there is a workaround for this. I appreciate any light that takes me in the right path.
I prepared a demo that replicates the behavior here: https://github.com/paolocarrasco/archunit-enumeration-issue.
The body of my test goes something like this:
noClasses()
.that()
.resideInAPackage("package1")
.should()
.accessClassesThat()
.resideOutsideOfPackages(
"java..",
"..package1..",
"..package2..")
.check(this.javaClasses);
After I run the test it shows a message that states something similar to this:
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package 'package1' should access classes that reside outside of packages ['java..', '..package1..', '..package2..']' was violated (1 times):
Method <package1.Colors.values()> calls method <[Lpackage1.Colors;.clone()> in (Colors.java:3)
at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:92)
at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81)
at com.tngtech.archunit.lang.ArchRule$Factory$SimpleArchRule.check(ArchRule.java:190)
at com.tngtech.archunit.lang.syntax.ObjectsShouldInternal.check(ObjectsShouldInternal.java:75)
at com.tngtech.archunit.lang.syntax.ClassesShouldThatInternal.check(ClassesShouldThatInternal.java:366)
at DependenciesTest.shouldHaveDependenciesOnlyToLowerPackages(DependenciesTest.java:32)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
After some refactorings, I want to ensure that all (.*)Test classes reside in the same package as their corresponding classes-under-test. How to achieve this with ArchUnit?
I made a mistake in my layeredArchitecture definition. There is no layer called control:
@ArchTest
public static final ArchRule layerRule = layeredArchitecture()
.layer("boundary").definedBy("..boundary..")
.layer("service").definedBy("..control.service..")
.layer("repository").definedBy("..control.repository..")
.layer("entity").definedBy("..entity..")
.whereLayer("boundary").mayNotBeAccessedByAnyLayer()
.whereLayer("repository").mayOnlyBeAccessedByLayers("boundary", "control")
.whereLayer("service").mayOnlyBeAccessedByLayers("boundary")
.whereLayer("entity").mayOnlyBeAccessedByLayers("boundary", "control");
When I execute the test I get a NPE:
java.lang.NullPointerException
at com.tngtech.archunit.library.Architectures$LayeredArchitecture$LayerDefinition.access$500(Architectures.java:229)
at com.tngtech.archunit.library.Architectures$LayeredArchitecture.packagesOf(Architectures.java:218)
at com.tngtech.archunit.library.Architectures$LayeredArchitecture.evaluate(Architectures.java:151)
at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:79)
at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:174)
at com.tngtech.archunit.junit.ArchRuleExecution.evaluateOn(ArchRuleExecution.java:57)
at com.tngtech.archunit.junit.ArchUnitRunner.runChild(ArchUnitRunner.java:134)
at com.tngtech.archunit.junit.ArchUnitRunner.runChild(ArchUnitRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at com.tngtech.archunit.junit.ArchUnitRunner$1.evaluate(ArchUnitRunner.java:73)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
It would be great to print a meaningful error message instead of the NPE.
http://archunit.org still redirects me to the github repository...
most likely NOT what was intended when creating the docs directory with all the nice jekyll stuff in it...
We're running some ArchTests using the performance optimized version with the AnalyzeClasses annotation:
@RunWith(ArchUnitRunner.class)
@AnalyzeClasses(packages = "de.whatever")
public class ArchUnitTest {
@ArchTest
public static final ArchRule SOME_RULE = ...;
}
It seems the JavaClasses cache is not freed after running the tests in this class. (We're running into memory problems in the unit tests that are executed after ArchUnit. For performance reasons our tests use the same JVM instance.)
Is there an option for clearing the cache after all ArchTests in a test class have run?
Currently one broken file evil.class
breaks the whole import causing an exception. It would be more convenient, to just log a warning and move on.
How to reproduce:
echo "broken" > /some/test/folder/Evil.class
cp some.GoodClass.class /some/test/folder
then
JavaClasses classes = new ClassFileImporter().importPath(Paths.get("/some/test/folder"));
Expected: some.GoodClass
to be imported
Actual: Import dies with an exception
While filtering imports via importOptions=
is quite powerful, choosing the source of the classes is not (you can only choose the whole classpath, packages, or classes defining packages).
We should extend @AnalyzeClasses
, to also allow specifying some generic "UrlProvider" type (or similar), that can in turn provide arbitrary URLs to import classes from.
The most typical violations of components / layers can be caught by using a rule like
noClasses().that()...should().accessClassesThat()...
However, there are different types of dependencies, that are not caught by this (annotations, declared fields that are never accessed, etc.). While it is possible to check for this with several rules, the rule API should offer an easy and extensive way to check for this. Proposal:
noClasses().that()...should().dependOnClassesThat()...
And likewise, it should be possible to check the other way around
classes().that()...should().onlyHaveDepedentClassesThat()...
I.e. the last rule checks that all classes depending on this class should satisfy some condition.
The current implementation relies on ClassLoader#getResources("/some/pkg")
to import package some.pkg
. This works for most Jars, however, if the Jar doesn't contain an entry for the respective folder, getResources(..)
will return an empty array, and thus classes won't be imported.
An easy way to reproduce this, is to import java.io
and observe, that java.io.File
will be missing. This happens, because the default rt.jar
is missing the entry /java/io
, but has an entry /java/io/File.class
.
Obviously, this is not desired behavior, because the import of packages should not depend on whether or not the respective Jar has folder entries or not.
Sometimes a rule only targets a single class (e.g. class Foo
is only accessed by classes matching a certain pattern). It should be possible to supply the class object in that case, instead of writing classes().that().haveFullyQualifiedName(Foo.class.getName())
. Thus I propose an API extension:
classes().that().areEquivalentTo(Foo.class)
classes().should().beEquivalentTo(Foo.class)
which just pipes to JavaClass.isEquivalentTo(Foo.class)
.
Right now it's possible to import multiple packages and multiple Locations
via ClassFileImporter
, but only a single JarFile
.
It would be consistent and more convenient, to allow importing multiple JarFiles
as well.
Hey there,
I'd need to find out, still not been able to, if it's possibile to prevent a class to use an annotation with a given value of one of its attributes.
for instance, to enforce a rule which prevents the value EAGER like
fetch = FetchType.EAGER
to be used in the annotation javax.persistence.OneToMany
thanks in advance,
s2r
Is there a possibility to specify two or more conditions for the onlyBeAccessed() call?
For example:
classes().that().areAnnotatedWith(Service.class)
.should().onlyBeAccessed().byClassesThat().areAnnotatedWith(Controller.class)
.orShould().onlyBeAccessed().byClassesThat().areAnnotatedWith(RestController.class);
ArchUnit is a great testing framework! I wonder whether there is a good way to relax the rules for classes under /src/test/java
in a portable way.
At the moment imported classes for a specific set of URLs will stay in memory forever. While this might be useful, if multiple tests in fact want to import the same set of classes, it might be necessary in some scenarios, to empty the cache after each test class is finished.
Proposal: It should be possible to declare
@AnalyzeClasses(cache = CacheMode.FOR_CLASS)
where the default is the current behavior CacheMode.FOREVER
.
I learned about the archunit_ignore_patterns
before the new documentation and treated the Regex as a way to ignore classes within the package. Today I learned that it is applied on the whole violation message. This is too much in my case, because the signature of a violating method uses a class from an ignored package.
Could there be a way to improve the FailureReport
class and the global ignore file to allow more specific ignoring rules?
When executing an ArchRule like noClasses().should().beAnnotatedWith(SuppressWarnings.class)
ArchUnit reports no violations, even if there are SuppressWarnings annotations on some of the classes.
The reason is that SuppressWarnings has RetentionPolicy.SOURCE, i.e. ArchUnit cannot query these annotations.
Would it be possible for ArchUnit to issue some warning (or better: fail the test?) if we're checking a SOURCE annotation?
I forked the repository and executed a build with clean build -PallTests
. Without any changes the following tests fail on Windows because of illegal file URIs.
:archunit:test
com.tngtech.archunit.core.importer.ClassFileImporterSlowTest > imports_jars FAILED
java.lang.IllegalArgumentException: Illegal character in opaque part at index 5: file:\Users\xxxx\.gradle\caches\modules-2\files-2.1\junit\junit\4.12\2973d150c0dc1fefe998f834810d68f278ea58ec\junit-4.12.jar
at java.net.URI.create(URI.java:852)
at com.tngtech.archunit.core.importer.Location.newFileUri(Location.java:164)
at com.tngtech.archunit.core.importer.Location.of(Location.java:143)
at com.tngtech.archunit.core.importer.ClassFileImporter.importJars(ClassFileImporter.java:112)
at com.tngtech.archunit.core.importer.ClassFileImporter.importJars(ClassFileImporter.java:105)
at com.tngtech.archunit.core.importer.ClassFileImporter.importJar(ClassFileImporter.java:100)
at com.tngtech.archunit.core.importer.ClassFileImporterSlowTest.imports_jars(ClassFileImporterSlowTest.java:60)
Caused by:
java.net.URISyntaxException: Illegal character in opaque part at index 5: file:\Users\xxxx\.gradle\caches\modules-2\files-2.1\junit\junit\4.12\2973d150c0dc1fefe998f834810d68f278ea58ec\junit-4.12.jar
at java.net.URI$Parser.fail(URI.java:2848)
at java.net.URI$Parser.checkChars(URI.java:3021)
at java.net.URI$Parser.parse(URI.java:3058)
at java.net.URI.<init>(URI.java:588)
at java.net.URI.create(URI.java:850)
... 6 more
com.tngtech.archunit.core.importer.ClassFileImporterTest > ImportOptions_are_respected FAILED
java.lang.IllegalArgumentException: Illegal character in opaque part at index 5: file:\Users\xxxx\.gradle\caches\modules-2\files-2.1\junit\junit\4.12\2973d150c0dc1fefe998f834810d68f278ea58ec\junit-4.12.jar
at java.net.URI.create(URI.java:852)
at com.tngtech.archunit.core.importer.Location.newFileUri(Location.java:164)
at com.tngtech.archunit.core.importer.Location.of(Location.java:143)
at com.tngtech.archunit.core.importer.ClassFileImporter.importJars(ClassFileImporter.java:112)
at com.tngtech.archunit.core.importer.ClassFileImporter.importJars(ClassFileImporter.java:105)
at com.tngtech.archunit.core.importer.ClassFileImporter.importJar(ClassFileImporter.java:100)
at com.tngtech.archunit.core.importer.ClassFileImporterTest.ImportOptions_are_respected(ClassFileImporterTest.java:1757)
Caused by:
java.net.URISyntaxException: Illegal character in opaque part at index 5: file:\Users\xxxx\.gradle\caches\modules-2\files-2.1\junit\junit\4.12\2973d150c0dc1fefe998f834810d68f278ea58ec\junit-4.12.jar
at java.net.URI$Parser.fail(URI.java:2848)
at java.net.URI$Parser.checkChars(URI.java:3021)
at java.net.URI$Parser.parse(URI.java:3058)
at java.net.URI.<init>(URI.java:588)
at java.net.URI.create(URI.java:850)
... 6 more
com.tngtech.archunit.core.importer.ClassFileSourceTest > classes_in_JAR_are_filtered[0: [/one/Foo.class, /one/Bar.class, /two/Bar.class], com.tngtech.archunit.core.importer.ImportOptions@22bf94fb, [/one/Foo.class, /one/Bar.class, /two/Bar.class]] FAILED
java.lang.IllegalArgumentException: Illegal character in opaque part at index 7: file:C:\Users\xxxx\AppData\Local\Temp\archtmp9878364045828424399998574865074908\test.jar
at java.net.URI.create(URI.java:852)
at com.tngtech.archunit.core.importer.Location.newFileUri(Location.java:164)
at com.tngtech.archunit.core.importer.Location.of(Location.java:143)
at com.tngtech.archunit.core.importer.ClassFileSourceTest.classes_in_JAR_are_filtered(ClassFileSourceTest.java:62)
Caused by:
java.net.URISyntaxException: Illegal character in opaque part at index 7: file:C:\Users\xxxx\AppData\Local\Temp\archtmp9878364045828424399998574865074908\test.jar
at java.net.URI$Parser.fail(URI.java:2848)
at java.net.URI$Parser.checkChars(URI.java:3021)
at java.net.URI$Parser.parse(URI.java:3058)
at java.net.URI.<init>(URI.java:588)
at java.net.URI.create(URI.java:850)
... 3 more
com.tngtech.archunit.core.importer.ClassFileSourceTest > classes_in_JAR_are_filtered[1: [/one/Foo.class, /one/Bar.class, /two/Bar.class], com.tngtech.archunit.core.importer.ImportOptions@1ae7f181, [/one/Foo.class, /one/Bar.class]] FAILED
java.lang.IllegalArgumentException: Illegal character in opaque part at index 7: file:C:\Users\xxxx\AppData\Local\Temp\archtmp9878372910508525595324178910651973\test.jar
at java.net.URI.create(URI.java:852)
at com.tngtech.archunit.core.importer.Location.newFileUri(Location.java:164)
at com.tngtech.archunit.core.importer.Location.of(Location.java:143)
at com.tngtech.archunit.core.importer.ClassFileSourceTest.classes_in_JAR_are_filtered(ClassFileSourceTest.java:62)
Caused by:
java.net.URISyntaxException: Illegal character in opaque part at index 7: file:C:\Users\xxxx\AppData\Local\Temp\archtmp9878372910508525595324178910651973\test.jar
at java.net.URI$Parser.fail(URI.java:2848)
at java.net.URI$Parser.checkChars(URI.java:3021)
at java.net.URI$Parser.parse(URI.java:3058)
at java.net.URI.<init>(URI.java:588)
at java.net.URI.create(URI.java:850)
... 3 more
com.tngtech.archunit.core.importer.ClassFileSourceTest > classes_in_JAR_are_filtered[2: [/one/Foo.class, /one/Bar.class, /two/Bar.class], com.tngtech.archunit.core.importer.ImportOptions@7de272a, [/two/Bar.class]] FAILED
java.lang.IllegalArgumentException: Illegal character in opaque part at index 7: file:C:\Users\xxxx\AppData\Local\Temp\archtmp987837304491962-8304586973681892963\test.jar
at java.net.URI.create(URI.java:852)
at com.tngtech.archunit.core.importer.Location.newFileUri(Location.java:164)
at com.tngtech.archunit.core.importer.Location.of(Location.java:143)
at com.tngtech.archunit.core.importer.ClassFileSourceTest.classes_in_JAR_are_filtered(ClassFileSourceTest.java:62)
Caused by:
java.net.URISyntaxException: Illegal character in opaque part at index 7: file:C:\Users\xxxx\AppData\Local\Temp\archtmp987837304491962-8304586973681892963\test.jar
at java.net.URI$Parser.fail(URI.java:2848)
at java.net.URI$Parser.checkChars(URI.java:3021)
at java.net.URI$Parser.parse(URI.java:3058)
at java.net.URI.<init>(URI.java:588)
at java.net.URI.create(URI.java:850)
... 3 more
com.tngtech.archunit.core.importer.ClassFileSourceTest > classes_in_JAR_are_filtered[3: [/one/Foo.class, /one/Bar.class, /two/Bar.class], com.tngtech.archunit.core.importer.ImportOptions@732cfbe1, [/one/Bar.class, /two/Bar.class]] FAILED
java.lang.IllegalArgumentException: Illegal character in opaque part at index 7: file:C:\Users\xxxx\AppData\Local\Temp\archtmp9878373187069975548690689213990652\test.jar
at java.net.URI.create(URI.java:852)
at com.tngtech.archunit.core.importer.Location.newFileUri(Location.java:164)
at com.tngtech.archunit.core.importer.Location.of(Location.java:143)
at com.tngtech.archunit.core.importer.ClassFileSourceTest.classes_in_JAR_are_filtered(ClassFileSourceTest.java:62)
Caused by:
java.net.URISyntaxException: Illegal character in opaque part at index 7: file:C:\Users\xxxx\AppData\Local\Temp\archtmp9878373187069975548690689213990652\test.jar
at java.net.URI$Parser.fail(URI.java:2848)
at java.net.URI$Parser.checkChars(URI.java:3021)
at java.net.URI$Parser.parse(URI.java:3058)
at java.net.URI.<init>(URI.java:588)
at java.net.URI.create(URI.java:850)
... 3 more
com.tngtech.archunit.core.importer.ClassFileSourceTest > classes_in_JAR_are_filtered[4: [/one/Foo.class, /one/Bar.class, /two/Bar.class], com.tngtech.archunit.core.importer.ImportOptions@2deaa133, []] FAILED
java.lang.IllegalArgumentException: Illegal character in opaque part at index 7: file:C:\Users\xxxx\AppData\Local\Temp\archtmp9878373309837588610823013150992030\test.jar
at java.net.URI.create(URI.java:852)
at com.tngtech.archunit.core.importer.Location.newFileUri(Location.java:164)
at com.tngtech.archunit.core.importer.Location.of(Location.java:143)
at com.tngtech.archunit.core.importer.ClassFileSourceTest.classes_in_JAR_are_filtered(ClassFileSourceTest.java:62)
Caused by:
java.net.URISyntaxException: Illegal character in opaque part at index 7: file:C:\Users\xxxx\AppData\Local\Temp\archtmp9878373309837588610823013150992030\test.jar
at java.net.URI$Parser.fail(URI.java:2848)
at java.net.URI$Parser.checkChars(URI.java:3021)
at java.net.URI$Parser.parse(URI.java:3058)
at java.net.URI.<init>(URI.java:588)
at java.net.URI.create(URI.java:850)
... 3 more
com.tngtech.archunit.core.importer.LocationTest > initializationError FAILED
java.lang.Error: Cannot explode 'LocationTest.JAR_protocol_is_added_to_file_urls_that_point_to_JARs' using 'file_locations_pointing_to_jar' due to: Exception while invoking dataprovider method 'file_locations_pointing_to_jar': Illegal character in authority at index 7: file://C:\Users\xxxx\AppData\Local\Temp\archtmp9878411977992676820986918056713005\test.jar
Caused by:
java.lang.IllegalArgumentException: Exception while invoking dataprovider method 'file_locations_pointing_to_jar': Illegal character in authority at index 7: file://C:\Users\xxxx\AppData\Local\Temp\archtmp9878411977992676820986918056713005\test.jar
Caused by:
java.lang.IllegalArgumentException: Illegal character in authority at index 7: file://C:\Users\xxxx\AppData\Local\Temp\archtmp9878411977992676820986918056713005\test.jar
at java.net.URI.create(URI.java:852)
at com.tngtech.archunit.core.importer.LocationTest.file_locations_pointing_to_jar(LocationTest.java:64)
Caused by:
java.net.URISyntaxException: Illegal character in authority at index 7: file://C:\Users\xxxx\AppData\Local\Temp\archtmp9878411977992676820986918056713005\test.jar
at java.net.URI$Parser.fail(URI.java:2848)
at java.net.URI$Parser.parseAuthority(URI.java:3186)
at java.net.URI$Parser.parseHierarchical(URI.java:3097)
at java.net.URI$Parser.parse(URI.java:3053)
at java.net.URI.<init>(URI.java:588)
at java.net.URI.create(URI.java:850)
... 1 more
2945 tests completed, 8 failed
:archunit:test FAILED
The Spring Framework allows (and encourages) to compose annotations, for example composing @Service
and @Transactional
to @TransactionalService
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Service
@Transactional
public @interface TransactionalService {
}
@TransactionalService
public class DummyBean {
}
With ArchUnit it should be possible to access the meta-annotations @Service
and @Transactional
, for example
@Test
void serviceAnnotation() {
JavaClasses classes = new ClassFileImporter().importPackagesOf(DummyBean.class);
classes()
.that().areAssignableTo(DummyBean.class)
.should().beAnnotatedWith(Service.class)
.check(classes);
}
With 0.5.0 this test fails with
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes that are assignable to example.DummyBean should be annotated with @Service' was violated (1 times):
class example.DummyBean is not annotated with @Service
at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:92)
at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:81)
at com.tngtech.archunit.lang.ArchRule$Factory$SimpleArchRule.check(ArchRule.java:190)
at com.tngtech.archunit.lang.syntax.ObjectsShouldInternal.check(ObjectsShouldInternal.java:75)
at example.DummyTest.serviceAnnotation(DummyTest.java:61)
See also the Spring Annotation Programming Model.
Hi Peter, I very like the idea of the ArchUnit.
My question is it possible to create a rule that does not allow to use some third-party libraries?
For instance, there are several libraries for test assertions, but I want a team use only one in test classes.
From my point of view, I can simply check imports of test classes and raise a fail if import in a "blacklist".
While one can do pretty much any filtering / assertion on class names using haveNameMatching(regex)
, common use cases are to check the simple name for endsWith
, startsWith
or contains
.
These cases should be directly represented within the syntax, without thinking in regex. Thus we should add classes().
that().haveSimpleNameEndingWith(..)
that().haveSimpleNameStartingWith(..)
that().haveSimpleNameContaining(..)
should().haveSimpleNameEndingWith(..)
should().haveSimpleNameStartingWith(..)
should().haveSimpleNameContaining(..)
and the respective negations (notHave...
)
Typically in legacy code bases there will be a ton of violations, when architecture rules are introduced for the first time. ArchUnit supports the 'archunit_ignore_patterns.txt' file, to exclude messages based on regular expressions, but once violations are ignored, there is no further feedback.
The following is a list of suggestions, to improve the overview:
when compared to jQAssist, ArchUnit has some way to go wrt user documentation and accessibility.
Simple proposal: setup a static site generator (e.g. jekyll): You might copy the configuration/setup from arc42.org and simply replace the content...
(there's already a dockerized version so you don't need to install jekyll, ruby etc.).
In case of questions with this setup, I'm happy to support...
Since it's possible to import multiple URLs and multiple JARs in one shot, it would be more consistent, to also allow to import multiple paths in one shot. Currently it's only possible to import one path at a time.
I just wrote my first test with ArchUnit. I really like your DSL, much nicer than what I created with Degraph https://github.com/schauder/degraph
There is just one thing that I miss from Degraph: the ability to create visual representations of dependencies, especially in the case of failures.
Would you be interested in adding a feature that would cause a (failing) test to create a graphml file containing a diagram similar to what Degraph creates? Like this: http://blog.schauderhaft.de/degraph/documentation.html#result
Don't if I find the time, but I would be interested in creating a PR.
In the console log I see multiple MalformedURLException:
10:32:08.043 [main] WARN com.tngtech.archunit.core.importer.UrlSource$From - Cannot parse URL from path C:\Program Files\Java\jdk1.8.0_112\jre\lib\resources.jar!/
java.net.MalformedURLException: invalid url: file://C:%5CProgram%20Files%5CJava%5Cjdk1.8.0_112%5Cjre%5Clib%5Cresources.jar!/ (java.net.MalformedURLException: For input string: "%5CProgram%20Files%5CJava%5Cjdk1.8.0_112%5Cjre%5Clib%5Cresources.jar")
at java.net.URL.<init>(URL.java:627)
at java.net.URL.<init>(URL.java:490)
at java.net.URL.<init>(URL.java:439)
at com.tngtech.archunit.core.importer.UrlSource$From.newUrl(UrlSource.java:100)
at com.tngtech.archunit.core.importer.UrlSource$From.newJarUri(UrlSource.java:95)
at com.tngtech.archunit.core.importer.UrlSource$From.parseClassPathEntry(UrlSource.java:86)
at com.tngtech.archunit.core.importer.UrlSource$From.findUrlsForClassPathProperty(UrlSource.java:79)
at com.tngtech.archunit.core.importer.UrlSource$From.classPathSystemProperties(UrlSource.java:70)
at com.tngtech.archunit.core.importer.LocationResolver$Legacy.resolveClassPath(LocationResolver.java:38)
at com.tngtech.archunit.core.importer.Locations.getLocationsOf(Locations.java:88)
at com.tngtech.archunit.core.importer.Locations.ofPackage(Locations.java:63)
at com.tngtech.archunit.junit.ClassCache.locationsOf(ClassCache.java:136)
at com.tngtech.archunit.junit.ClassCache.getLocationsOfPackages(ClassCache.java:103)
at com.tngtech.archunit.junit.ClassCache.locationsToImport(ClassCache.java:91)
at com.tngtech.archunit.junit.ClassCache.getClassesToAnalyzeFor(ClassCache.java:74)
at com.tngtech.archunit.junit.ArchUnitRunner.runChild(ArchUnitRunner.java:133)
at com.tngtech.archunit.junit.ArchUnitRunner.runChild(ArchUnitRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at com.tngtech.archunit.junit.ArchUnitRunner$1.evaluate(ArchUnitRunner.java:73)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.NullPointerException: invalid url: file://C:%5CProgram%20Files%5CJava%5Cjdk1.8.0_112%5Cjre%5Clib%5Cresources.jar!/ (java.net.MalformedURLException: For input string: "%5CProgram%20Files%5CJava%5Cjdk1.8.0_112%5Cjre%5Clib%5Cresources.jar")
at sun.net.www.protocol.jar.Handler.parseAbsoluteSpec(Handler.java:178)
at sun.net.www.protocol.jar.Handler.parseURL(Handler.java:151)
at java.net.URL.<init>(URL.java:622)
... 28 common frames omitted
I'm running the tests on Windows.
Do these exception have any impact on the functionality of ArchUnit?
Btw. thanks a lot for this excellent tool!
Is there a planned junit 5 support?
I want to provide a shared library with common rules. These rules are used in different corporate projects. This is to ensure that every project is built the same way.
Example:
@Test
void checkCommonRules() {
GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS.check(javaClasses);
GeneralCodingRules.NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS.check(javaClasses);
GeneralCodingRules.NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING.check(javaClasses);
}
This prints only the violations of the first violated rule.
Now I want to ensure that in every project every of those common rules is checked. If a new common rule is added to the library, every project should execute this new rule without further code modification.
I thought of something like this:
public ArchRules COMMON_RULES = ArchRules.of(
GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS,
GeneralCodingRules.NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS,
GeneralCodingRules.NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING
);
@Test
void checkCommonRules() {
GeneralCodingRules.COMMON_RULES.check(javaClasses);
}
This check should print all violations of all given rules.
The following rule produces an assertion error (in IntelliJ) that contains only text, no hyperlinks to the classes.
/**
* Test classes should not be public (Junit 5).
*/
@Test
void shouldNotUsePublicInTestCases() {
JavaClasses classes = new ClassFileImporter().importPackages("edu.hm.hafner");
ArchRule noPublicClasses = noClasses().that().haveSimpleNameEndingWith("Test")
.should().bePublic();
noPublicClasses.check(classes);
}
Produces the output (example):
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'no classes that have simple name ending with 'Test' should be public' was violated (71 times):
class edu.hm.hafner.ArchitectureRulesTest has modifier PUBLIC
class edu.hm.hafner.analysis.AbstractParserTest has modifier PUBLIC
class edu.hm.hafner.analysis.IssueFilterTest has modifier PUBLIC
class edu.hm.hafner.analysis.IssueTest has modifier PUBLIC
class edu.hm.hafner.analysis.parser.AcuCobolParserTest has modifier PUBLIC
class edu.hm.hafner.analysis.parser.AjcParserTest has modifier PUBLIC
class edu.hm.hafner.analysis.parser.AnsibleLintTest has modifier PUBLIC
[...]
Expected result:
each class has a hyperlink (as the other assertion errors from ArchUnit).
When checking
classes()...should().onlyBeAccessedBy()...
it would make sense to ignore violations by self access by default. Often usecases look like
classes().that().resideInAPackage("..bar..").should().onlyBeAccessedByAnyPackage("..foo..", "..bar..")
because otherwise a ton of violations of '..bar..' calling itself would be reported.
It seems to never make sense, to report self-accesses, so this should be excluded by default, making rules easier to specify.
We should add to ArchRuleDefinition
the factory methods
GivenFields fields()
GivenMethods methods()
GivenConstructors constructors()
GivenCodeUnits codeUnits()
GivenMembers members()
that provide an easy entrance to speak about the respective objects, i.e. provide a that(predicate)
and should(condition)
fluent API. At some later point, this API can be extended for generally useful methods like declaredIn(clazz)
or similar.
ArchRule suites ignore the @ArchIgnore
annotation, e.g.:
public class MyRules {
@ArchTest
public static final ArchRule someRule = ...
}
@RunWith(ArchUnitRunner.class)
// ...
public class MyArchTest {
@ArchTest
@ArchIgnore // This is ignored
public static final ArchRules rules = ArchRules.in(MyRules.class);
}
Expected behavior when running MyArchTest
:
All rules within MyRules
are skipped
Actual behavior:
Rules within MyRules
run anyway.
Wrote Test
@ArchTest
public static void classIsInRepositoryPackageSoItsAnnotatedWithTransactional(JavaClasses classes) {
classes()
.that().resideInAPackage("..repository..")
.should().beAnnotatedWith(Transactional.class)
.check(classes);
}
So now no class is allowed in repository package that does not have a Transactional annotation.
It would be nice to be able to specify which type of org.springframework.transaction.annotation.Propagation is required.
When Kotlin is used together with the JUnit support, writing rules becomes too bloated, since it's necessary to create a companion object due to the static requirements for fields.
I.e. at the moment ArchUnit JUnit tests in Kotlin have to be written like
@RunWith(ArchUnitRunner::class)
@AnalyzeClasses(packages = ["com.mycompany.myapp"])
class MyArchitectureTest {
companion object {
@ArchTest @JvmField val myRule = classes()
.that().resideInAPackage("..service..")
.should().onlyBeAccessed().byAnyPackage("..controller..", "..service..")
}
}
This seems unnecessary bloated, what we would want, is
@RunWith(ArchUnitRunner::class)
@AnalyzeClasses(packages = ["com.mycompany.myapp"])
class MyArchitectureTest {
@ArchTest val myRule = classes()
.that().resideInAPackage("..service..")
.should().onlyBeAccessed().byAnyPackage("..controller..", "..service..")
}
I just tried to do a very simple case in my eclipse project and I'm getting an InvalidPathException
JavaClasses importedClasses = new ClassFileImporter().importPackages("com.mypackage");
java.nio.file.InvalidPathException: Illegal char <:> at index 2: /C:/Software/eclipse/eclipse-neon-developer/eclipse/configuration/org.eclipse.osgi/432/0/.cp/
at sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182)
at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153)
at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77)
at sun.nio.fs.WindowsPath.parse(WindowsPath.java:94)
at sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:255)
at java.nio.file.Paths.get(Paths.java:84)
at com.tngtech.archunit.core.importer.UrlSource$From.newFileUri(UrlSource.java:92)
at com.tngtech.archunit.core.importer.UrlSource$From.parseClassPathEntry(UrlSource.java:86)
at com.tngtech.archunit.core.importer.UrlSource$From.findUrlsForClassPathProperty(UrlSource.java:79)
at com.tngtech.archunit.core.importer.UrlSource$From.classPathSystemProperties(UrlSource.java:71)
at com.tngtech.archunit.core.importer.LocationResolver$Legacy.resolveClassPath(LocationResolver.java:38)
at com.tngtech.archunit.core.importer.Locations.getLocationsOf(Locations.java:88)
at com.tngtech.archunit.core.importer.Locations.ofPackage(Locations.java:63)
at com.tngtech.archunit.core.importer.ClassFileImporter.importPackages(ClassFileImporter.java:128)
at com.tngtech.archunit.core.importer.ClassFileImporter.importPackages(ClassFileImporter.java:138)
at com.fisglobal.apexcollateral.DependencyCycleIntegrationTest.findCycles(DependencyCycleIntegrationTest.java:14)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
It looks like this is a classpath entry that was added by eclipse when running the Junit test. I see that UrlSource is already catching MalformedURLException, should it also catch InvalidPathException?
actually, this is more of a pull-request than issue, but:
too bad, I cannot open a pull request on the orphaned gh-pages branch, therefore I attach the modified Gemfile here:
source "https://rubygems.org"
# commented the line below to get rid of github authentication warnings
# gem "github-pages", group: :jekyll_plugins
# added the following line, same reason as above
gem "jekyll", group: :jekyll_plugins
group :jekyll_plugins do
gem "jekyll-paginate"
gem "jekyll-sitemap"
gem "jekyll-gist"
gem "jekyll-feed"
gem "jemoji"
end
The SecurityTest tests if java.security.cert
is accessed only by certain packages. On Mac jvms apple.security
is also accessing the cert package.
In any legacy project trying to introduce checks for layer dependencies will most likely result in a lot of existing violations.
It should be possible to ignore existing violations in a convenient way.
Suggestion:
layeredArchitecture()
...
.whereLayer("foo").mayOnlyBeAccessedByLayers("bar")
.ignore(/*(Class/ClassName/DescribedPredicate)*/ from, (/*(Class/ClassName/DescribedPredicate)*/ to)
.ignore(...);
For a custom ArchCondition that checks the definition of a JavaClass/JavaMethod/JavaField it would be useful to append the declaration line to the error message as clickable link.
Example:
all(javaMethods).should(haveNameStartingWithLowerCase()).check(javaClasses);
private ArchCondition<JavaMethod> haveNameStartingWithLowerCase() {
return new ArchCondition<JavaMethod>("have name starting with lower case") {
@Override
public void check(JavaMethod item, ConditionEvents events) {
boolean satisfied = Character.isLowerCase(item.getName().charAt(0));
String message = item.getFullName() + " " + Formatters.formatLocation(item.getOwner(), item.getDeclarationLineNumber());
events.add(new SimpleConditionEvent(item, satisfied, message));
}
};
}
would print
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'methods should have name starting with lower case' was violated (1 times):
demo.GreetEndpoint.Greet(java.lang.String) (GreetEndpoint.java:23)
Relates to #38
Is it possible to detect all cyclic references? I know the syntax for slices e.g.
slices().matching("com.hascode.(tutorial).(*)").namingSlices("$2 of $1").should()
.beFreeOfCycles();
Is it possible somehow to write something similar to this one?
classes().should().beFreeOfCycles();
I'd like to implement tests with ArchUnit that basically assert the following:
Public methods of classes in package ABC always return classes of a certain type
In my particular case, I'd like to make sure that public methods of classes in the controller layer always return a particular class that we use as a wrapper for API responses.
Other scenarios I can think of (and would want to implement in the future) could be to check that public methods of classes of the service layer always return classes (or collections of classes) of the domain layer.
I've been checking the ArchUnit-Examples and the User Guide but couldn't find any APIs related to method return values. I also checked the current issues and didn't find anything related to this.
Is that something that you'd see in scope for ArchUnit?
Hi,
I stumbled upon this amazing framework when I read the article on informatik-aktuell.de. The simple idea of this amazes me, because for our Microservices Domain Driven Design project I see a big potential to make use of it, since I am faced often with exactly these questions like "Why can I not directly access the ValueObject out from the ApplicationService" and so on.
So right now I am experimenting a bit and I am trying to setup this enforcement:
@ArchTest
public static final ArchRule enforceValueObjectsCanOnlyBeAccessFromOtherVOAndEntities =
classes()
.that().areAssignableTo(ValueObject.class)
.should().onlyBeAccessed().byClassesThat().areAssignableTo(ValueObject.class)
.orShould().onlyBeAccessed().byClassesThat().areAssignableTo(Entity.class);
However I seem to miss something, because running this code results in a lot of violations, where it even (!) says something like this:
Method <...Birthdate.hasAgeRequirement(int)> gets field <...Birthdate.birthdate> in (Birthdate.java:39)
How can this be? I mean obviously Birthdate was identified as an ValueObject (which it is), but why is it not allowed to access fields of itself?
Any help is appreciated.
Stef
Hi @codecholeric,
Thank you for this great library. It is awesome and saves me a lot of time.
Recently I've faced a case where i wanted to check that JavaClass
is enum
.
Currently I can solve this by: javaClass.reflect().isEnum()
, but it would be more convenient to have such check in JavaClass
.
If you don't mind I'll create corresponding PR.
it would be neat to support a dependencies.uml.
this file should define the dependencies of the packages.
it could contain "unintented dependencies", but shouldn't.
a project then just should include archunit.jar, and then everybody could be sure, that dependencies.uml reflect the current project.
this way your UML keeps up to date and your depencies are better readable then a fluent api ever could be.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.