diff options
author | agrieve <agrieve@chromium.org> | 2016-02-10 11:07:55 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-02-10 19:10:58 +0000 |
commit | 3bbba07e116e2c5a10303387de6bce832b191023 (patch) | |
tree | 93b662d3e04c9fa0dbfa3e231d83ee89de753886 /build/android | |
parent | a0bbcbad97cffb9121dc42769c18150a0f032e0e (diff) | |
download | chromium_src-3bbba07e116e2c5a10303387de6bce832b191023.zip chromium_src-3bbba07e116e2c5a10303387de6bce832b191023.tar.gz chromium_src-3bbba07e116e2c5a10303387de6bce832b191023.tar.bz2 |
Add java-side support for _incremental instrumentation tests
Build rule and test runner changes to follow.
BUG=575975
Review URL: https://codereview.chromium.org/1684583003
Cr-Commit-Position: refs/heads/master@{#374695}
Diffstat (limited to 'build/android')
3 files changed, 93 insertions, 37 deletions
diff --git a/build/android/incremental_install/installer.py b/build/android/incremental_install/installer.py index feebadb..b5b1d05 100755 --- a/build/android/incremental_install/installer.py +++ b/build/android/incremental_install/installer.py @@ -13,6 +13,7 @@ import os import posixpath import shutil import sys +import zipfile sys.path.append( os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) @@ -63,6 +64,12 @@ def _GetDeviceIncrementalDir(package): return '/data/local/tmp/incremental-app-%s' % package +def _HasClasses(jar_path): + """Returns whether the given jar contains classes.dex.""" + with zipfile.ZipFile(jar_path) as jar: + return 'classes.dex' in jar.namelist() + + def Uninstall(device, package, enable_device_cache=False): """Uninstalls and removes all incremental files for the given package.""" main_timer = time_profile.TimeProfile() @@ -138,7 +145,10 @@ def Install(device, apk, split_globs=None, native_libs=None, dex_files=None, # Ensure no two files have the same name. transformed_names = _TransformDexPaths(dex_files) for src_path, dest_name in zip(dex_files, transformed_names): - shutil.copy(src_path, os.path.join(temp_dir, dest_name)) + # Binary targets with no extra classes create .dex.jar without a + # classes.dex (which Android chokes on). + if _HasClasses(src_path): + shutil.copy(src_path, os.path.join(temp_dir, dest_name)) device.PushChangedFiles([(temp_dir, device_dex_dir)], delete_device_stale=True) push_dex_timer.Stop(log=False) diff --git a/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java b/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java index 7c9bcca..1fb5e40 100644 --- a/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java +++ b/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java @@ -37,6 +37,7 @@ public final class BootstrapApplication extends Application { private ClassLoaderPatcher mClassLoaderPatcher; private Application mRealApplication; + private Instrumentation mOrigInstrumentation; private Instrumentation mRealInstrumentation; private Object mStashedProviderList; private Object mActivityThread; @@ -44,41 +45,76 @@ public final class BootstrapApplication extends Application { @Override protected void attachBaseContext(Context context) { super.attachBaseContext(context); - File incrementalRootDir = new File(MANAGED_DIR_PREFIX + context.getPackageName()); - File libDir = new File(incrementalRootDir, "lib"); - File dexDir = new File(incrementalRootDir, "dex"); - File installLockFile = new File(incrementalRootDir, "install.lock"); - File firstRunLockFile = new File(incrementalRootDir, "firstrun.lock"); - try { mActivityThread = Reflect.invokeMethod(Class.forName("android.app.ActivityThread"), "currentActivityThread"); mClassLoaderPatcher = new ClassLoaderPatcher(context); - boolean isFirstRun = LockFile.installerLockExists(firstRunLockFile); + mOrigInstrumentation = + (Instrumentation) Reflect.getField(mActivityThread, "mInstrumentation"); + Context instContext = mOrigInstrumentation.getContext(); + if (instContext == null) { + instContext = context; + } + + // When running with an instrumentation that lives in a different package from the + // application, we must load the dex files and native libraries from both pacakges. + // This logic likely won't work when the instrumentation is incremental, but the app is + // non-incremental. This configuration isn't used right now though. + String appPackageName = getPackageName(); + String instPackageName = instContext.getPackageName(); + boolean instPackageNameDiffers = !appPackageName.equals(instPackageName); + Log.i(TAG, "App PackageName: " + appPackageName); + if (instPackageNameDiffers) { + Log.i(TAG, "Inst PackageName: " + instPackageName); + } + + File appIncrementalRootDir = new File(MANAGED_DIR_PREFIX + appPackageName); + File appLibDir = new File(appIncrementalRootDir, "lib"); + File appDexDir = new File(appIncrementalRootDir, "dex"); + File appInstallLockFile = new File(appIncrementalRootDir, "install.lock"); + File appFirstRunLockFile = new File(appIncrementalRootDir, "firstrun.lock"); + File instIncrementalRootDir = new File(MANAGED_DIR_PREFIX + instPackageName); + File instLibDir = new File(instIncrementalRootDir, "lib"); + File instDexDir = new File(instIncrementalRootDir, "dex"); + File instInstallLockFile = new File(instIncrementalRootDir, "install.lock"); + File instFirstRunLockFile = new File(instIncrementalRootDir , "firstrun.lock"); + + boolean isFirstRun = LockFile.installerLockExists(appFirstRunLockFile) + || (instPackageNameDiffers + && LockFile.installerLockExists(instFirstRunLockFile)); if (isFirstRun) { if (mClassLoaderPatcher.mIsPrimaryProcess) { // Wait for incremental_install.py to finish. - LockFile.waitForInstallerLock(installLockFile, 30 * 1000); + LockFile.waitForInstallerLock(appInstallLockFile, 30 * 1000); + LockFile.waitForInstallerLock(instInstallLockFile, 30 * 1000); } else { // Wait for the browser process to create the optimized dex files // and copy the library files. - LockFile.waitForInstallerLock(firstRunLockFile, 60 * 1000); + LockFile.waitForInstallerLock(appFirstRunLockFile, 60 * 1000); + LockFile.waitForInstallerLock(instFirstRunLockFile, 60 * 1000); } } - mClassLoaderPatcher.importNativeLibs(libDir); - mClassLoaderPatcher.loadDexFiles(dexDir); + mClassLoaderPatcher.importNativeLibs(instLibDir); + mClassLoaderPatcher.loadDexFiles(instDexDir); + if (instPackageNameDiffers) { + mClassLoaderPatcher.importNativeLibs(appLibDir); + mClassLoaderPatcher.loadDexFiles(appDexDir); + } if (isFirstRun && mClassLoaderPatcher.mIsPrimaryProcess) { - LockFile.clearInstallerLock(firstRunLockFile); + LockFile.clearInstallerLock(appFirstRunLockFile); + if (instPackageNameDiffers) { + LockFile.clearInstallerLock(instFirstRunLockFile); + } } // mInstrumentationAppDir is one of a set of fields that is initialized only when // instrumentation is active. if (Reflect.getField(mActivityThread, "mInstrumentationAppDir") != null) { String realInstrumentationName = - getClassNameFromMetadata(REAL_INSTRUMENTATION_META_DATA_NAME); + getClassNameFromMetadata(REAL_INSTRUMENTATION_META_DATA_NAME, instContext); initInstrumentation(realInstrumentationName); } else { Log.i(TAG, "No instrumentation active."); @@ -93,7 +129,7 @@ public final class BootstrapApplication extends Application { // attachBaseContext() is called from ActivityThread#handleBindApplication() and // Application#mApplication is changed right after we return. Thus, we cannot swap // the Application instances until onCreate() is called. - String realApplicationName = getClassNameFromMetadata(REAL_APP_META_DATA_NAME); + String realApplicationName = getClassNameFromMetadata(REAL_APP_META_DATA_NAME, context); Log.i(TAG, "Instantiating " + realApplicationName); mRealApplication = (Application) Reflect.newInstance(Class.forName(realApplicationName)); @@ -114,12 +150,14 @@ public final class BootstrapApplication extends Application { * Returns the fully-qualified class name for the given key, stored in a * <meta> witin the manifest. */ - private String getClassNameFromMetadata(String key) throws NameNotFoundException { - ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPackageName(), + private static String getClassNameFromMetadata(String key, Context context) + throws NameNotFoundException { + String pkgName = context.getPackageName(); + ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(pkgName, PackageManager.GET_META_DATA); String value = appInfo.metaData.getString(key); if (value != null && !value.contains(".")) { - value = getPackageName() + "." + value; + value = pkgName + "." + value; } return value; } @@ -129,14 +167,12 @@ public final class BootstrapApplication extends Application { */ private void initInstrumentation(String realInstrumentationName) throws ReflectiveOperationException { - Instrumentation oldInstrumentation = - (Instrumentation) Reflect.getField(mActivityThread, "mInstrumentation"); if (realInstrumentationName == null) { // This is the case when an incremental app is used as a target for an instrumentation // test. In this case, ActivityThread can instantiate the proper class just fine since // it exists within the test apk (as opposed to the incremental apk-under-test). Log.i(TAG, "Running with external instrumentation"); - mRealInstrumentation = oldInstrumentation; + mRealInstrumentation = mOrigInstrumentation; return; } // For unit tests, the instrumentation class is replaced in the manifest by a build step @@ -151,11 +187,11 @@ public final class BootstrapApplication extends Application { "mWatcher", "mUiAutomationConnection"}; for (String fieldName : initFields) { Reflect.setField(mRealInstrumentation, fieldName, - Reflect.getField(oldInstrumentation, fieldName)); + Reflect.getField(mOrigInstrumentation, fieldName)); } // But make sure the correct ComponentName is used. ComponentName newName = new ComponentName( - oldInstrumentation.getComponentName().getPackageName(), realInstrumentationName); + mOrigInstrumentation.getComponentName().getPackageName(), realInstrumentationName); Reflect.setField(mRealInstrumentation, "mComponent", newName); } diff --git a/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java b/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java index c1682e8..c382799 100644 --- a/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java +++ b/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java @@ -103,6 +103,10 @@ final class ClassLoaderPatcher { */ void importNativeLibs(File libDir) throws ReflectiveOperationException, IOException { Log.i(TAG, "Importing native libraries from: " + libDir); + if (!libDir.exists()) { + Log.i(TAG, "No native libs exist."); + return; + } // The library copying is not necessary on older devices, but we do it anyways to // simplify things (it's fast compared to dexing). // https://code.google.com/p/android/issues/detail?id=79480 @@ -169,28 +173,34 @@ final class ClassLoaderPatcher { private static void copyChangedFiles(File srcDir, File dstDir) throws IOException { // No need to delete stale libs since libraries are loaded explicitly. + int numNotChanged = 0; for (File f : srcDir.listFiles()) { // Note: Tried using hardlinks, but resulted in EACCES exceptions. File dest = new File(dstDir, f.getName()); - copyIfModified(f, dest); + if (!copyIfModified(f, dest)) { + numNotChanged++; + } + } + if (numNotChanged > 0) { + Log.i(TAG, numNotChanged + " libs already up-to-date."); } } - private static void copyIfModified(File src, File dest) throws IOException { + private static boolean copyIfModified(File src, File dest) throws IOException { long lastModified = src.lastModified(); - if (!dest.exists() || dest.lastModified() != lastModified) { - Log.i(TAG, "Copying " + src + " -> " + dest); - FileInputStream istream = new FileInputStream(src); - FileOutputStream ostream = new FileOutputStream(dest); - ostream.getChannel().transferFrom(istream.getChannel(), 0, istream.getChannel().size()); - istream.close(); - ostream.close(); - dest.setReadable(true, false); - dest.setExecutable(true, false); - dest.setLastModified(lastModified); - } else { - Log.i(TAG, "Up-to-date: " + dest); + if (dest.exists() && dest.lastModified() == lastModified) { + return false; } + Log.i(TAG, "Copying " + src + " -> " + dest); + FileInputStream istream = new FileInputStream(src); + FileOutputStream ostream = new FileOutputStream(dest); + ostream.getChannel().transferFrom(istream.getChannel(), 0, istream.getChannel().size()); + istream.close(); + ostream.close(); + dest.setReadable(true, false); + dest.setExecutable(true, false); + dest.setLastModified(lastModified); + return true; } private void ensureAppFilesSubDirExists() { |