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

fix tests not behaving consistently with Android #347

Merged
merged 1 commit into from
May 3, 2020

Conversation

codecholeric
Copy link
Collaborator

It seems like Gradle Android support treats Java and Android libraries differently. When run from Gradle ClassLoader.getResources(..) would return a package entry only from the library JAR, but not the Android JAR. When run from Android Studio it seems like plain file paths and no archives are used, so ClassLoader.getResources(..) returns URLs from both libraries.
In the beginning we only used ClassLoader.getResources(..), but then the first bug occurring was that JARs containing a package would not be detected if the folder entry for the package was missing (e.g. look for java.io but the entry /java/io from the JAR is missing, even though /java/io/File.class is there, then ClassLoader.getResources("/java/io/") would not give a result). As a workaround we would check if the requested resource was no class file and we got no result and then in turn look through all the JARs on the classpath to return all entries starting with the respective package prefix. Unfortunately this did not work for Android, because ClassLoader.getResources(..) provided a partial result, just missing some entries that should be present on the classpath. The only consistent solution seems to be to immediately do the scanning through the classpath ourselves and completely drop the usage of ClassLoader.getResources(..).
I did some performance analysis on Hibernate Core which seemed fine (similar times). What was needed though is a cache for the entries or the build time would explode. This will not make a difference if every location is only scanned once, but as soon as we repeatedly import classes from the classpath the scanning of all archives would cost massive performance (in the end the ClassLoader caches all these entries internally as well). I have compared the heap usage with and without the cache on Hibernate core and could not notice a significant increase. Thus it seems okay to go this way and keeping the resource entries in the cache for the lifetime of ArchUnit seems okay.

Resolves: #319

It seems like Gradle Android support treats Java and Android libraries differently. When run from Gradle `ClassLoader.getResources(..)` would return a package entry only from the library JAR, but not the Android JAR. When run from Android Studio it seems like plain file paths and no archives are used, so `ClassLoader.getResources(..)` returns URLs from both libraries.
In the beginning we only used `ClassLoader.getResources(..)`, but then the first bug occurring was that JARs containing a package would not be detected if the folder entry for the package was missing (e.g. look for `java.io` but the entry `/java/io` from the JAR is missing, even though `/java/io/File.class` is there, then `ClassLoader.getResources("/java/io/")` would not give a result). As a workaround we would check if the requested resource was no class file and we got no result and then in turn look through all the JARs on the classpath to return all entries starting with the respective package prefix. Unfortunately this did not work for Android, because `ClassLoader.getResources(..)` provided a partial result, just missing some entries that should be present on the classpath. The only consistent solution seems to be to immediately do the scanning through the classpath ourselves and completely drop the usage of `ClassLoader.getResources(..)`.
I did some performance analysis on Hibernate Core which seemed fine (similar times). What was needed though is a cache for the entries or the build time would explode. This will not make a difference if every location is only scanned once, but as soon as we repeatedly import classes from the classpath the scanning of all archives would cost massive performance (in the end the `ClassLoader` caches all these entries internally as well). I have compared the heap usage with and without the cache on Hibernate core and could not notice a significant increase. Thus it seems okay to go this way and keeping the resource entries in the cache for the lifetime of ArchUnit seems okay.

Signed-off-by: Peter Gafert <[email protected]>
@codecholeric codecholeric merged commit b7bf88a into master May 3, 2020
@codecholeric codecholeric deleted the fix-Android-testing-problem branch May 3, 2020 23:21
codecholeric added a commit that referenced this pull request May 23, 2020
Unfortunately #347 broke the possibility to use a manifest `Class-Path` attribute to specify the classpath. Since this is a feature that many tools use to avoid "command line too long" problems (e.g. on Windows) when the classpath grows in size, I have added support for this to the new way of scanning the classpath.
I have also added back the old way to resolve files through the context classloader, since I could not measure a real performance impact and using both sources seems safer in case there is some other classpath quirk I have overlooked. The set will remove duplicates anyway, before starting to import the classes.
codecholeric added a commit that referenced this pull request Feb 21, 2021
It seems like Gradle Android support treats Java and Android libraries differently. When run from Gradle `ClassLoader.getResources(..)` would return a package entry only from the library JAR, but not the Android JAR. When run from Android Studio it seems like plain file paths and no archives are used, so `ClassLoader.getResources(..)` returns URLs from both libraries.
In the beginning we only used `ClassLoader.getResources(..)`, but then the first bug occurring was that JARs containing a package would not be detected if the folder entry for the package was missing (e.g. look for `java.io` but the entry `/java/io` from the JAR is missing, even though `/java/io/File.class` is there, then `ClassLoader.getResources("/java/io/")` would not give a result). As a workaround we would check if the requested resource was no class file and we got no result and then in turn look through all the JARs on the classpath to return all entries starting with the respective package prefix. Unfortunately this did not work for Android, because `ClassLoader.getResources(..)` provided a partial result, just missing some entries that should be present on the classpath. The only consistent solution seems to be to immediately do the scanning through the classpath ourselves and completely drop the usage of `ClassLoader.getResources(..)`.
I did some performance analysis on Hibernate Core which seemed fine (similar times). What was needed though is a cache for the entries or the build time would explode. This will not make a difference if every location is only scanned once, but as soon as we repeatedly import classes from the classpath the scanning of all archives would cost massive performance (in the end the `ClassLoader` caches all these entries internally as well). I have compared the heap usage with and without the cache on Hibernate core and could not notice a significant increase. Thus it seems okay to go this way and keeping the resource entries in the cache for the lifetime of ArchUnit seems okay.
codecholeric added a commit that referenced this pull request Feb 21, 2021
Unfortunately #347 broke the possibility to use a manifest `Class-Path` attribute to specify the classpath. Since this is a feature that many tools use to avoid "command line too long" problems (e.g. on Windows) when the classpath grows in size, I have added support for this to the new way of scanning the classpath.
I have also added back the old way to resolve files through the context classloader, since I could not measure a real performance impact and using both sources seems safer in case there is some other classpath quirk I have overlooked. The set will remove duplicates anyway, before starting to import the classes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Android module classes not included when tests run from Gradle
1 participant