views:

1912

answers:

3

I have a module that builds an app called MyApp. I have another that builds some testcases for that app, called MyAppTests. They both build their own APKs, and they both work fine from within my IDE. I'd like to build them using ant so that I can take advantage of continuous integration.

Building the app module works fine. I'm having difficulty getting the Test module to compile and run.

Using Christopher's tip from a previous question, I used android create test-project -p MyAppTests -m ../MyApp -n MyAppTests to create the necessary build files to build and run my test project. This seems to work great (once I remove an unnecessary test case that it constructed for me and revert my AndroidManifest.xml to the one I was using before it got replaced by android create), but I have two problems.

The first problem: The project doesn't compile because it's missing libraries.

$ ant run-tests
Buildfile: build.xml
    [setup] Project Target: Google APIs
    [setup] Vendor: Google Inc.
    [setup] Platform Version: 1.6
    [setup] API level: 4
    [setup] WARNING: No minSdkVersion value set. Application will install on all Android versions.

-install-tested-project:
    [setup] Project Target: Google APIs
    [setup] Vendor: Google Inc.
    [setup] Platform Version: 1.6
    [setup] API level: 4
    [setup] WARNING: No minSdkVersion value set. Application will install on all Android versions.

-compile-tested-if-test:

-dirs:
     [echo] Creating output directories if needed...

-resource-src:
     [echo] Generating R.java / Manifest.java from the resources...

-aidl:
     [echo] Compiling aidl files into Java classes...

compile:
    [javac] Compiling 1 source file to /Users/mike/Projects/myapp/android/MyApp/bin/classes

-dex:
     [echo] Converting compiled files and external libraries into /Users/mike/Projects/myapp/android/MyApp/bin/classes.dex...
     [echo]          

-package-resources:
     [echo] Packaging resources
 [aaptexec] Creating full resource package...

-package-debug-sign:
[apkbuilder] Creating MyApp-debug-unaligned.apk and signing it with a debug key...
[apkbuilder] Using keystore: /Users/mike/.android/debug.keystore

debug:
     [echo] Running zip align on final apk...
     [echo] Debug Package: /Users/mike/Projects/myapp/android/MyApp/bin/MyApp-debug.apk

install:
     [echo] Installing /Users/mike/Projects/myapp/android/MyApp/bin/MyApp-debug.apk onto default emulator or device...
     [exec] 1567 KB/s (288354 bytes in 0.179s)
     [exec]     pkg: /data/local/tmp/MyApp-debug.apk
     [exec] Success

-compile-tested-if-test:

-dirs:
     [echo] Creating output directories if needed...
    [mkdir] Created dir: /Users/mike/Projects/myapp/android/MyAppTests/gen
    [mkdir] Created dir: /Users/mike/Projects/myapp/android/MyAppTests/bin
    [mkdir] Created dir: /Users/mike/Projects/myapp/android/MyAppTests/bin/classes

-resource-src:
     [echo] Generating R.java / Manifest.java from the resources...

-aidl:
     [echo] Compiling aidl files into Java classes...

compile:
    [javac] Compiling 5 source files to /Users/mike/Projects/myapp/android/MyAppTests/bin/classes
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/GsonTest.java:4: package roboguice.test does not exist
    [javac] import roboguice.test.RoboUnitTestCase;
    [javac]                      ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/GsonTest.java:8: package com.google.gson does not exist
    [javac] import com.google.gson.JsonElement;
    [javac]                       ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/GsonTest.java:9: package com.google.gson does not exist
    [javac] import com.google.gson.JsonParser;
    [javac]                       ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/GsonTest.java:11: cannot find symbol
    [javac] symbol: class RoboUnitTestCase
    [javac] public class GsonTest extends RoboUnitTestCase<MyApplication> {
    [javac]                               ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/HttpTest.java:6: package roboguice.test does not exist
    [javac] import roboguice.test.RoboUnitTestCase;
    [javac]                      ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/HttpTest.java:7: package roboguice.util does not exist
    [javac] import roboguice.util.RoboLooperThread;
    [javac]                      ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/HttpTest.java:11: package com.google.gson does not exist
    [javac] import com.google.gson.JsonObject;
    [javac]                       ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/HttpTest.java:15: cannot find symbol
    [javac] symbol: class RoboUnitTestCase
    [javac] public class HttpTest extends RoboUnitTestCase<MyApplication> {
    [javac]                               ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/LinksTest.java:4: package roboguice.test does not exist
    [javac] import roboguice.test.RoboUnitTestCase;
    [javac]                      ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/LinksTest.java:12: cannot find symbol
    [javac] symbol: class RoboUnitTestCase
    [javac] public class LinksTest extends RoboUnitTestCase<MyApplication> {
    [javac]                                ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:4: package roboguice.test does not exist
    [javac] import roboguice.test.RoboUnitTestCase;
    [javac]                      ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:5: package roboguice.util does not exist
    [javac] import roboguice.util.RoboAsyncTask;
    [javac]                      ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:6: package roboguice.util does not exist
    [javac] import roboguice.util.RoboLooperThread;
    [javac]                      ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:12: cannot find symbol
    [javac] symbol: class RoboUnitTestCase
    [javac] public class SafeAsyncTest extends RoboUnitTestCase<MyApplication> {
    [javac]                                    ^
    [javac] /Users/mike/Projects/myapp/android/MyApp/bin/classes/com/myapp/activity/Stories.class: warning: Cannot find annotation method 'value()' in type 'roboguice.inject.InjectResource': class file for roboguice.inject.InjectResource not found
    [javac] /Users/mike/Projects/myapp/android/MyApp/bin/classes/com/myapp/activity/Stories.class: warning: Cannot find annotation method 'value()' in type 'roboguice.inject.InjectResource'
    [javac] /Users/mike/Projects/myapp/android/MyApp/bin/classes/com/myapp/activity/Stories.class: warning: Cannot find annotation method 'value()' in type 'roboguice.inject.InjectView': class file for roboguice.inject.InjectView not found
    [javac] /Users/mike/Projects/myapp/android/MyApp/bin/classes/com/myapp/activity/Stories.class: warning: Cannot find annotation method 'value()' in type 'roboguice.inject.InjectView'
    [javac] /Users/mike/Projects/myapp/android/MyApp/bin/classes/com/myapp/activity/Stories.class: warning: Cannot find annotation method 'value()' in type 'roboguice.inject.InjectView'
    [javac] /Users/mike/Projects/myapp/android/MyApp/bin/classes/com/myapp/activity/Stories.class: warning: Cannot find annotation method 'value()' in type 'roboguice.inject.InjectView'
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/GsonTest.java:15: cannot find symbol
    [javac] symbol  : class JsonParser
    [javac] location: class com.myapp.test.GsonTest
    [javac]         final JsonParser parser = new JsonParser();
    [javac]               ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/GsonTest.java:15: cannot find symbol
    [javac] symbol  : class JsonParser
    [javac] location: class com.myapp.test.GsonTest
    [javac]         final JsonParser parser = new JsonParser();
    [javac]                                       ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/GsonTest.java:18: cannot find symbol
    [javac] symbol  : class JsonElement
    [javac] location: class com.myapp.test.GsonTest
    [javac]         final JsonElement e = parser.parse(s);
    [javac]               ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/GsonTest.java:20: cannot find symbol
    [javac] symbol  : class JsonElement
    [javac] location: class com.myapp.test.GsonTest
    [javac]         final JsonElement e2 = parser.parse(s2);
    [javac]               ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/HttpTest.java:19: cannot find symbol
    [javac] symbol  : method getInstrumentation()
    [javac] location: class com.myapp.test.HttpTest
    [javac]         assertEquals("MyApp", getInstrumentation().getTargetContext().getResources().getString(com.myapp.R.string.app_name));
    [javac]                              ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/HttpTest.java:62: cannot find symbol
    [javac] symbol  : class RoboLooperThread
    [javac] location: class com.myapp.test.HttpTest
    [javac]         new RoboLooperThread() {
    [javac]             ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/HttpTest.java:82: cannot find symbol
    [javac] symbol  : method assertTrue(java.lang.String,boolean)
    [javac] location: class com.myapp.test.HttpTest
    [javac]         assertTrue(result[0], result[0].contains("Search"));
    [javac]         ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/HttpTest.java:87: cannot find symbol
    [javac] symbol  : class JsonObject
    [javac] location: class com.myapp.test.HttpTest
    [javac]         final JsonObject[] result = {null};
    [javac]               ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/HttpTest.java:90: cannot find symbol
    [javac] symbol  : class RoboLooperThread
    [javac] location: class com.myapp.test.HttpTest
    [javac]         new RoboLooperThread() {
    [javac]             ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/HttpTest.java:117: cannot find symbol
    [javac] symbol  : class JsonObject
    [javac] location: class com.myapp.test.HttpTest
    [javac]         final JsonObject[] result = {null};
    [javac]               ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/HttpTest.java:120: cannot find symbol
    [javac] symbol  : class RoboLooperThread
    [javac] location: class com.myapp.test.HttpTest
    [javac]         new RoboLooperThread() {
    [javac]             ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/LinksTest.java:27: cannot find symbol
    [javac] symbol  : method assertTrue(boolean)
    [javac] location: class com.myapp.test.LinksTest
    [javac]             assertTrue(m.matches());
    [javac]             ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/LinksTest.java:28: cannot find symbol
    [javac] symbol  : method assertEquals(java.lang.String,java.lang.String)
    [javac] location: class com.myapp.test.LinksTest
    [javac]             assertEquals( map.get(url), m.group(1) );
    [javac]             ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:19: cannot find symbol
    [javac] symbol  : method getInstrumentation()
    [javac] location: class com.myapp.test.SafeAsyncTest
    [javac]         assertEquals("MyApp", getInstrumentation().getTargetContext().getString(com.myapp.R.string.app_name));
    [javac]                              ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:27: cannot find symbol
    [javac] symbol  : class RoboLooperThread
    [javac] location: class com.myapp.test.SafeAsyncTest
    [javac]         new RoboLooperThread() {
    [javac]             ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:65: cannot find symbol
    [javac] symbol  : method assertEquals(com.myapp.test.SafeAsyncTest.State,com.myapp.test.SafeAsyncTest.State)
    [javac] location: class com.myapp.test.SafeAsyncTest
    [javac]         assertEquals(State.TEST_SUCCESS,state[0]);
    [javac]         ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:74: cannot find symbol
    [javac] symbol  : class RoboLooperThread
    [javac] location: class com.myapp.test.SafeAsyncTest
    [javac]         new RoboLooperThread() {
    [javac]             ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:105: cannot find symbol
    [javac] symbol  : method assertEquals(com.myapp.test.SafeAsyncTest.State,com.myapp.test.SafeAsyncTest.State)
    [javac] location: class com.myapp.test.SafeAsyncTest
    [javac]         assertEquals(State.TEST_SUCCESS,state[0]);
    [javac]         ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:113: cannot find symbol
    [javac] symbol  : class RoboLooperThread
    [javac] location: class com.myapp.test.SafeAsyncTest
    [javac]         new RoboLooperThread() {
    [javac]             ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:144: cannot find symbol
    [javac] symbol  : method assertEquals(com.myapp.test.SafeAsyncTest.State,com.myapp.test.SafeAsyncTest.State)
    [javac] location: class com.myapp.test.SafeAsyncTest
    [javac]         assertEquals(State.TEST_SUCCESS,state[0]);
    [javac]         ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:154: cannot find symbol
    [javac] symbol  : class RoboLooperThread
    [javac] location: class com.myapp.test.SafeAsyncTest
    [javac]         new RoboLooperThread() {
    [javac]             ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java:187: cannot find symbol
    [javac] symbol  : method assertEquals(com.myapp.test.SafeAsyncTest.State,com.myapp.test.SafeAsyncTest.State)
    [javac] location: class com.myapp.test.SafeAsyncTest
    [javac]         assertEquals(State.TEST_SUCCESS,state[0]);
    [javac]         ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/StoriesTest.java:11: cannot access roboguice.activity.GuiceListActivity
    [javac] class file for roboguice.activity.GuiceListActivity not found
    [javac] public class StoriesTest extends ActivityUnitTestCase<Stories> {
    [javac]                                                      ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/StoriesTest.java:21: cannot access roboguice.application.GuiceApplication
    [javac] class file for roboguice.application.GuiceApplication not found
    [javac]         setApplication( new MyApplication( getInstrumentation().getTargetContext() ) );
    [javac]                         ^
    [javac] /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/StoriesTest.java:22: incompatible types
    [javac] found   : com.myapp.activity.Stories
    [javac] required: android.app.Activity
    [javac]         final Activity activity = startActivity(intent, null, null);
    [javac]                                                ^
    [javac] 39 errors
    [javac] 6 warnings

BUILD FAILED
/opt/local/android-sdk-mac/platforms/android-1.6/templates/android_rules.xml:248: Compile failed; see the compiler error output for details.

Total time: 24 seconds

That's not a hard problem to solve. I'm not sure it's the right thing to do, but I copied the missing libraries (roboguice and gson) from the MyApp/libs directory to the MyAppTests/libs directory and everything seems to compile fine.

But that leads to the second problem, which I'm currently stuck on. The tests compile fine but they won't run:

$ cp ../MyApp/libs/gson-r538.jar libs/

$ cp ../MyApp/libs/roboguice-1.1-SNAPSHOT.jar libs/

0 10:23 /Users/mike/Projects/myapp/android/MyAppTests $ ant run-testsBuildfile: build.xml
    [setup] Project Target: Google APIs
    [setup] Vendor: Google Inc.
    [setup] Platform Version: 1.6
    [setup] API level: 4
    [setup] WARNING: No minSdkVersion value set. Application will install on all Android versions.

-install-tested-project:
    [setup] Project Target: Google APIs
    [setup] Vendor: Google Inc.
    [setup] Platform Version: 1.6
    [setup] API level: 4
    [setup] WARNING: No minSdkVersion value set. Application will install on all Android versions.

-compile-tested-if-test:

-dirs:
     [echo] Creating output directories if needed...

-resource-src:
     [echo] Generating R.java / Manifest.java from the resources...

-aidl:
     [echo] Compiling aidl files into Java classes...

compile:
    [javac] Compiling 1 source file to /Users/mike/Projects/myapp/android/MyApp/bin/classes

-dex:
     [echo] Converting compiled files and external libraries into /Users/mike/Projects/myapp/android/MyApp/bin/classes.dex...
     [echo]          

-package-resources:
     [echo] Packaging resources
 [aaptexec] Creating full resource package...

-package-debug-sign:
[apkbuilder] Creating MyApp-debug-unaligned.apk and signing it with a debug key...
[apkbuilder] Using keystore: /Users/mike/.android/debug.keystore

debug:
     [echo] Running zip align on final apk...
     [echo] Debug Package: /Users/mike/Projects/myapp/android/MyApp/bin/MyApp-debug.apk

install:
     [echo] Installing /Users/mike/Projects/myapp/android/MyApp/bin/MyApp-debug.apk onto default emulator or device...
     [exec] 1396 KB/s (288354 bytes in 0.201s)
     [exec]     pkg: /data/local/tmp/MyApp-debug.apk
     [exec] Success

-compile-tested-if-test:

-dirs:
     [echo] Creating output directories if needed...

-resource-src:
     [echo] Generating R.java / Manifest.java from the resources...

-aidl:
     [echo] Compiling aidl files into Java classes...

compile:
    [javac] Compiling 5 source files to /Users/mike/Projects/myapp/android/MyAppTests/bin/classes
    [javac] Note: /Users/mike/Projects/myapp/android/MyAppTests/src/com/myapp/test/SafeAsyncTest.java uses unchecked or unsafe operations.
    [javac] Note: Recompile with -Xlint:unchecked for details.

-dex:
     [echo] Converting compiled files and external libraries into /Users/mike/Projects/myapp/android/MyAppTests/bin/classes.dex...
     [echo]          

-package-resources:
     [echo] Packaging resources
 [aaptexec] Creating full resource package...

-package-debug-sign:
[apkbuilder] Creating MyAppTests-debug-unaligned.apk and signing it with a debug key...
[apkbuilder] Using keystore: /Users/mike/.android/debug.keystore

debug:
     [echo] Running zip align on final apk...
     [echo] Debug Package: /Users/mike/Projects/myapp/android/MyAppTests/bin/MyAppTests-debug.apk

install:
     [echo] Installing /Users/mike/Projects/myapp/android/MyAppTests/bin/MyAppTests-debug.apk onto default emulator or device...
     [exec] 1227 KB/s (94595 bytes in 0.075s)
     [exec]     pkg: /data/local/tmp/MyAppTests-debug.apk
     [exec] Success

run-tests:
     [echo] Running tests ...
     [exec] 
     [exec] android.test.suitebuilder.TestSuiteBuilder$FailedToCreateTests:INSTRUMENTATION_RESULT: shortMsg=Class ref in pre-verified class resolved to unexpected implementation
     [exec] INSTRUMENTATION_RESULT: longMsg=java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
     [exec] INSTRUMENTATION_CODE: 0

BUILD SUCCESSFUL
Total time: 38 seconds

Any idea what's causing the "Class ref in pre-verified class resolved to unexpected implementation" error?

+2  A: 

I don't see the actual error message in the text above, but I think I can answer.

Generally, the warning happens because the same code appears in two different APKs. The implementation in one APK was used for pre-verification and optimization, but the other implementation is being used during execution. The VM detects the situation and rejects the class, because the verification and optimization were performed with a set of assumptions that are no longer true.

The way to fix this is to ensure that there is only one implementation of a class available to the VM. This may require fighting with the build scripts a bit more.

You can view the contents of an APK with "dexdump". (There's also "dexlist", which is a bit more concise, but I don't remember if that's part of the SDK.)

fadden
Yeah, I was thinking the problem might be that based on some other things I've found on Google, but I haven't been able to figure out how to avoid it yet. BTW the error shows up at the bottom of the second code block during the "run-tests" target
Mike
A: 

Have you tried to include the .class files on the test apk, instead of generating new .class files from your original project? This solved the problem in my case.

Seelengut
Hi seelengut, can you clarify? I'm not sure I understand.
Mike
+1  A: 

The problem is that there's a bug in the android ant build scripts that don't include the tested project's libs directory when compiling the tester project. If you try to get around this by copying the libs to the tester project's libs dir, you'll run into class verification problems at run time like I did, as pointed out by fadden.

The solution is to tweak the compile target originally in android's android_test_rules.xml to add <fileset dir="${tested.project.absolute.dir}/libs" includes="*.jar" /> to the <classpath> directive.

Here's the revised compile target. By adding it to the build.xml in your TESTER project, it will take precedence over the one in android_test_rules.xml:

<!-- override "compile" target in platform android_rules.xml to include tested app's external libraries -->
<target name="compile" depends="-resource-src, -aidl"
            description="Compiles project's .java files into .class files">
    <!-- If android rules are used for a test project, its classpath should include
         tested project's location -->
    <condition property="extensible.classpath"
                       value="${tested.project.absolute.dir}/bin/classes" else=".">
        <isset property="tested.project.absolute.dir" />
    </condition>
    <javac encoding="ascii" target="1.5" debug="true" extdirs=""
            destdir="${out.classes.absolute.dir}"
            bootclasspathref="android.target.classpath"
            verbose="${verbose}" classpath="${extensible.classpath}">
        <src path="${source.absolute.dir}" />
        <src path="${gen.absolute.dir}" />
        <classpath>
            <fileset dir="${tested.project.absolute.dir}/libs" includes="*.jar" />
            <fileset dir="${external.libs.absolute.dir}" includes="*.jar" />
        </classpath>
    </javac>
</target>
Mike