I run Android tests on CI and after having switched to Lollipop recently the integration tests wouldn’t run. Invoking
androidConnectedTest
gradle target always resulted in crashing with
ShellCommandUnresponsiveException
. Internet says that in such a case
ou just need to set ADB_INSTALL_TIMEOUT
. I tried to no avail.
Sourcediving it is then !
A long while after that I got to this file:
Device.java [Linking
to master, here’s the commit
hash:1cb1a4c2976b99ae53d28d7f01d975232c85f990
, as I don’t seem to be
able to find how to link to that hash directly]
What do we see there ? That indeed ADB_INSTALL_TIMEOUT
is being used:
static {
String installTimeout = System.getenv("ADB_INSTALL_TIMEOUT");
long time = 4;
if (installTimeout != null) {
try {
time = Long.parseLong(installTimeout);
} catch (NumberFormatException e) {
// use default value
}
}
INSTALL_TIMEOUT_MINUTES = time;
}
So far so good,
ADB_INSTALL_TIMEOUT
system variable seems to be respected when
invoking package installation tools. Are the above the only methods that
can install a package though ? Going further on that hunch we see that
in addition to installing single packages there is a possibility of
having a multi-package installation session.
public void installPackages(List<String> apkFilePaths, int timeOutInMs, boolean reinstall, String... extraArgs) throws InstallException {
assert(!apkFilePaths.isEmpty());
if (getApiLevel() < 21) {
Log.w("Internal error : installPackages invoked with device < 21 for %s",Joiner.on(",").join(apkFilePaths));
if (apkFilePaths.size() == 1) {
installPackage(apkFilePaths.get(0), reinstall, extraArgs);
return;
}
Log.e("Internal error : installPackages invoked with device < 21 for multiple APK : %s", Joiner.on(",").join(apkFilePaths));
throw new InstallException("Internal error : installPackages invoked with device < 21 for multiple APK : " + Joiner.on(",").join(apkFilePaths));
}
[...]
String sessionId = createMultiInstallSession(apkFilePaths, extraArgsList, reinstall);
Aha ! Non-Lollipop check here, with a
fallback to the old method - we may be onto something ! Some lines pass
and we can see an invocation of createMultiInstallSession
. What’s
there ?
private String createMultiInstallSession(List<String> apkFileNames, @NonNull Collection<String> extraArgs, boolean reinstall) throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
[...]
String cmd = String.format("pm install-create %1$s -S %2$d", parameters.toString(), totalFileSize);
executeShellCommand(cmd, receiver, DdmPreferences.getTimeOut());
[...]
A different invocation of
executeShellCommand
, now using DdmPreferences.getTimeOut()
as a
timeout value source.
Summarizing - this only happens if you install
multiple applications for your androidConnectedTest
and you are using
android device to test on that has api version that is equal or greater
to 21. That is all cool that we had this little Computer Science
Investigation, but how to fix that - i.e. how to have proper timeouts
for your installations ? Ideally from somewhere you configure and/or
invoke your builds. It turns out that gradle supports invoking just
enough Java for us to use there. In your gradle.build
as the very
first lines:
println "setting global timeout for apk installation to 10 minutes"
com.android.ddmlib.DdmPreferences.setTimeOut(600000)
android {
compileSdkVersion compileSdk
buildToolsVersion buildTools
[...]
That’s it. Invoke your android tests with
ADB_INSTALL_TIMEOUT
env variable set AND have the
DddPreference
set in your gradle.build
as in the example above and
you should be golden. Happy droiding !