I'm using the #VisibleForTesting annotation located in the Android Support Annotations library, and it looks like this:
#Retention(SOURCE)
public #interface VisibleForTesting {
}
As I understand it, ProGuard operates on the .class files and since this annotation isn't available at compile time due to its retention policy, all the annotated methods are stripped away. I'd like to run automated tests on my app and use the methods exposed for testing to verify that the ProGuard configuration doesn't break any use cases.
Is it possible to configure ProGuard to keep these elements somehow? So far I've tried:
-keep #android.support.annotation.VisibleForTesting class *
-keep class android.support.annotation.** {
#**.VisibleForTesting *;
}
-keep interface android.support.annotation.** {
#**.VisibleForTesting *;
}
And:
-keep interface android.support.annotation.VisibleForTesting
-keepclasseswithmembers class * {
#android.support.annotation.VisibleForTesting *;
}
-keepclassmembers class ** {
#android.support.annotation.VisibleForTesting *;
}
These two configurations do not work. If I annotate the methods with #Keep as well, and configure ProGuard to keep those methods, the methods are kept and the tests pass. However, by doing that I have to annotate all methods with two annotations.
Is it possible to hook into the annotation processor and override the retention policy for #VisibleForTesting? Or is that already too late in the build process?
Guava's #VisibleForTesting uses RetentionPolicy.CLASS, while Android Support Annotations Library uses RetentionPolicy.SOURCE. I'm considering posting a request to change the policy, but I suppose it's set to SOURCE for a reason, possibly due to performance and a very slightly increased file size?
Are there any options other than using two annotations (#VisibleForTesting and #Keep)?
Annotations with a RetentionPolicy == SOURCE are not present in .class files on which ProGuard is operating. Thus there is no way to use them in rules as they will never match.
Annotations with RetentionPolicy == CLASS should work fine. If needed, they can even be removed in release builds using ProGuard.
Related
I'm trying to keep proguard from renaming/optimizing classes which have a certain annotaion. There are quite a few examples out there and it should be straight forward but proguard isn't behaving as I would expect it to be.
Issue
Proguard v6.2.2 obfuscates classes annotated with #KeepClass although -whyareyoukeeping shows test.KeepMe is kept by a directive in the configuration.
I can reproduce the issue with a simple 3 file project.
Proguard config:
-optimizations !code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 3
-allowaccessmodification
-adaptresourcefilecontents **.properties,META-INF/MANIFEST.MF
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
-keep public class test.Entry {
public static void main(java.lang.String[]);
}
-keep #proguard.annotation.KeepClass public class * {
*;
}
-whyareyoukeeping class test.KeepMe
Class which should be kept:
import proguard.annotation.KeepClass;
#KeepClass
public class KeepMe
{
}
Proguard log:
Explaining why classes and class members are being kept...
Printing usage to [D:\Development\Projects\ProguardTest\build\usage.txt]...
test.KeepMe
is kept by a directive in the configuration.
Removing unused program classes and class elements...
Original number of program classes: 3
Final number of program classes: 2
And finally the files in the processed jar file:
- test
|- Entry.class
|- a.class (Obfuscated KeepMe class)
If however I explicitly list the class in the proguard configuration using
-keep class test.KeepMe{*;}
proguard reports the same log output but the class is untouched (as expected)
Am I missing something or is this a bug in proguard?
As it turns out the issue was actually related to a bug in proguard. After trying the latest beta it's working as expected.
I need to make sure the method
java.lang.String -> isEmpty()
Is present in the compiled code.
Will Proguard keep this method if it is referenced somewhere in my code? Or is it better to include
-keep class java.lang.String { *; }
Into Proguard configuration file?
I'm asking because to fix java.lang.ClassNotFoundException this code is used:
try {
Class.forName("android.os.AsyncTask");
}
catch(Throwable ignore) {}
Instead of adding this to Proguard:
-keep class android.os.AsyncTask { *; }
If you're referencing that method in your code, it should be kept by Proguard. From the documentation:
The ProGuard tool shrinks, optimizes, and obfuscates your code by
removing unused code and renaming classes, fields, and methods with
semantically obscure names
So if the code is being used, there's no reason why Proguard would remove it.
Hi I need to write Junit tests for an Android project but it has JNI methods as it uses webkit.Is there any way I can test those android methods(I dont want to test JNI methods).
Its like:
public void androidMethod(){
//some android code
nativeInit(); //how do I mock such methods?
//some code again
}
I have tried powermock,easymock,roboelectric but wasnt successful.Please help me.
I yesterday found I could solve this with Mockito (I didn't try powermock or easymock). Assuming your class is class C, my solution is:
C c=spy(new C);
doNothing().when(c).nativeInit();
c.androidMethod()
verify(c).nativeInit();
This does, of course, require that nativeInit is visible to the test.
Similar Problem
I had the same problem event though I was already using mockito in JUnit tests under src/test. Once I added tests under src/androidTest I started having issues, including this crash:
Mockito cannot mock/spy because :
- final class
And after making the class open, manually, I still got crashes in the JNI layer as it tried to load the *.so library (which wouldn't happen if mocks were working properly).
Working Solution
Instead, what I had to do was open the class for testing purposes using Kotlin's all-open plugin. The process is also explained well in this recent medium post but it boils down to the following four simple changes that are also modeled in one of the architecture components sample apps:
1. Make these additions to build.gradle:
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-allopen:${versions.kotlin}"
}
}
apply plugin: "kotlin-allopen"
allOpen {
// marker for classes that we want to be able to extend in debug builds
annotation 'com.your.package.name.OpenClass'
}
2. Add the corresponding annotations in the debug flavor. For example: app/src/debug/java/com/your/package/name/OpenForTesting.kt
package com.your.package.name
#Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class OpenClass
#OpenClass
#Target(AnnotationTarget.CLASS)
annotation class OpenForTesting
3. Add the corresponding annotation in the release flavor. For example: app/src/release/java/com/your/package/name/OpenForTesting.kt
package com.your.package.name
#Target(AnnotationTarget.CLASS)
annotation class OpenForTesting
4. Add the #OpenForTesting annotation to the class that needs to be mocked
package com.your.package.name
#OpenForTesting
class JniClassOfVictory {
...
external fun nativeInit()
...
companion object {
init {
System.loadLibrary("victoryeveryday")
}
}
}
The result is a flexible way to mark classes as open without actually making them open in release builds. Of course, this is because the #OpenForTesting annotation that we created in release is not marked with #OpenClass but the same annotation in debug is marked with #OpenClass. In build.gradle we designated that annotation as the signal to the kotlin-allopen plugin. So any class annotated with #OpenForTesting will be made open at compile-time but only on Debug builds.
I'm having troubles getting proguard to keep things based on their annotations (under Android).
I have an annotation, com.example.Keep:
#Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR })
public #interface Keep {
}
I have a class, which is only ever constructed through reflection:
public class Bar extends Foo {
#com.example.Keep
Bar(String a, String b) { super(a, b); }
//...
}
It works as expected (class keeps its constructor albeit with an obfuscated class name) When my proguard config includes the following:
-keepattributes Signature,*Annotation*,SourceFile,LineNumberTable
-keepclassmembers class ** extends com.example.Bar {
<init>(...);
}
It drops the constructor if I try change that to:
-keepattributes Signature,*Annotation*,SourceFile,LineNumberTable
-keepclassmembers class ** extends com.example.Bar {
#com.example.Keep <init>(...);
}
I see the same behaviour with both my own annotations and proguard's annotation.jar annotations. I've copied and pasted the annotation name from Foo's import of Keep, so am confident it's not a typo or incorrect import.
Can anyone think of what I might be missing?
If your annotations are being processed as program code, you should explicitly preserve them:
-keep,allowobfuscation #interface *
The reason is somewhat technical: ProGuard may remove them in the shrinking step, so they are no longer effective in the optimization step and the obfuscation step.
Cfr. ProGuard manual > Troubleshooting > Classes or class members not being kept.
In other words: what -keep commands should I use to tell Proguard to avoid obfuscating my classes that represent native libraries? (since JNA requires that the names match the equivalent native function, struct, etc.)
This is the rule I'm using for now:
-keepclassmembers class * extends com.sun.jna.** {
<fields>;
<methods>;
}
I still think there might be a better way to do it though.
For me worked as well
-keep class com.sun.jna.** { *; }
-keep class * implements com.sun.jna.** { *; }
I think I solved it using these rules instead, because it seems they need everything of the package to be de-obfuscated:
-keep class com.sun.jna.** { *; }
-keep class * implements com.sun.jna.** { *; }
JNA by default uses Library interface method names to look up native function names. Anything other than those should be able to withstand obfuscation.
If your tests include coverage of all JNA calls then you should be able to test this almost as fast as asking the question here.
EDIT
Consider this a comment, since I'm not prepared to offer "-keep" commands :)
You certainly must avoid elimination or reordering of any Structure fields.