Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android module classes not included when tests run from Gradle #319

Closed
xsveda opened this issue Feb 17, 2020 · 4 comments · Fixed by #347
Closed

Android module classes not included when tests run from Gradle #319

xsveda opened this issue Feb 17, 2020 · 4 comments · Fixed by #347
Assignees
Milestone

Comments

@xsveda
Copy link

xsveda commented Feb 17, 2020

Let's say I have an Android project of 3 modules:

C:/project/app            // gradle plugin "com.android.application"
C:/project/lib-android    // gradle plugin "com.android.library"
C:/project/lib-java       // gradle plugin "org.gradle.java"

I have an ArchUnit tests set up and tests are placed in :app module that depends on both library modules.
When I run them from Android Studio (IntelliJ Idea), the default LocationProvider will list classes of all modules:

file:/C:/project/app/build/tmp/kotlin-classes/debug/com/foo/ 
file:/C:/project/lib-android/build/tmp/kotlin-classes/debug/com/foo/
file:/C:/project/lib-java/build/classes/kotlin/main/com/foo/

When I run gradlew test from console, the default LocationProvider will list just classes of :app module and .jar file from :lib-java module:

file:/C:/project/app/build/tmp/kotlin-classes/debug/com/foo/ 
jar:file:/C:/project/lib-java/build/libs/lib-java.jar!/com/foo/ 

My current workaround is to list all the modules manually in custom LocationProvider implementation.

It seems that ArchUnit is not able to read classes from compiled output of :lib-android.

Not sure what is a proper and generic solution for such Android modules. Maybe a special implementation of com.tngtech.archunit.core.importer.Factory but I didn't find a way how to register a custom Factory implementation to ArchUnit.

@codecholeric
Copy link
Collaborator

What classes ArchUnit will find should depend on the classpath supplied. Is it possible that IntelliJ supplies a bigger classpath than Gradle? What is the output of System.getProperty("java.class.path")?
I think for the legacy version (JDK < 9) ArchUnit looks for URLClassLoaders in the ClassLoader hierarchy, similar to the org.reflections library. So if Gradle provides a custom ClassLoader there, that does not extend URLClassLoader, that might be one reason...
Anyway, a minimal example project would be great, then I could tell you more (everything else is just speculation).

@xsveda
Copy link
Author

xsveda commented Feb 28, 2020

Here is a minimal example with the exact structure as I've described earlier: ArchUnitIssue319.zip

When you run ArchUnitTest from Android Studio, it will catch classes in both modules:

java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes should reside in any package ['..company']' was violated (2 times):
Class <com.company.libraryandroid.CatchMeInAndroidLibrary> does not reside in any package ['..company'] in (CatchMeInAndroidLibrary.kt:0)
Class <com.company.libraryjava.CatchMeInJavaLibrary> does not reside in any package ['..company'] in (CatchMeInJavaLibrary.kt:0)

But when you run it from terminal gradlew testDebug only class is pure java modules is caught:

java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes should reside in any package ['..company']' was violated (1 times):
Class <com.company.libraryjava.CatchMeInJavaLibrary> does not reside in any package ['..company'] in (CatchMeInJavaLibrary.kt:0)

@codecholeric
Copy link
Collaborator

I've debugged a little into it, and I think I've found the problem. In the past I already had some problem, because ClassLoader.getResource(packageAsResourceName) only returns a URL within a JAR on the classpath, if there is an entry for that folder within the JAR file. Unfortunately to optimize an archive it's possible to not add entries for the folders, but only for the contained files 😦

So I had some workaround already (e.g. for java.io.File, because within the old rt.jar there was no entry for the folder java/io) to still find JARs containing packages in this case. But since this way of searching increases the runtime, I only went that path, if no URLs for the given package could be found via ClassLoader.getResource(packageAsResourceName).

Now in your case the ClassLoader does return a URL for library-java.jar, but not for library-android.jar, thus the workaround does not kick in, because ArchUnit assumes it has found all necessary paths ☹️

I'll do some research how big the performance impact of searching through the JARs really is. If it is not super big, I might drop the switch case for the workaround altogether and not use ClassLoader.getResource(..) at all any more here. AFAIS the Gradle report contains the same violations as Android Studio then. I guess in Android Studio the classpath simply contains some path to compiled classes instead of the classes.jar that Gradle builds and that is where the difference comes from (because in the file system folders are obviously never lacking). Did not verify this though (but it would make sense).

@xsveda
Copy link
Author

xsveda commented May 25, 2020

Tested with 0.14.1, works great, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants