Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(109)

Unified Diff: chrome/android/webapk/shell_apk/javatests/src/org/chromium/webapk/shell_apk/DexLoaderTest.java

Issue 1981473002: Upstream: DexOptimizer and DexLoader classes (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/webapk/shell_apk/javatests/src/org/chromium/webapk/shell_apk/DexLoaderTest.java
diff --git a/chrome/android/webapk/shell_apk/javatests/src/org/chromium/webapk/shell_apk/DexLoaderTest.java b/chrome/android/webapk/shell_apk/javatests/src/org/chromium/webapk/shell_apk/DexLoaderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..442a1f0018a5c133bc1da6cb4bc6f4003ca0b225
--- /dev/null
+++ b/chrome/android/webapk/shell_apk/javatests/src/org/chromium/webapk/shell_apk/DexLoaderTest.java
@@ -0,0 +1,333 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.webapk.shell_apk;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.FileObserver;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import dalvik.system.DexFile;
+
+import org.chromium.base.FileUtils;
+import org.chromium.content.browser.test.util.CallbackHelper;
+import org.chromium.webapk.shell_apk.test.dex_optimizer.IDexOptimizerService;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class DexLoaderTest extends InstrumentationTestCase {
+ /**
+ * Package of APK to load dex file from and package which provides DexOptimizerService.
+ */
+ private static final String DEX_OPTIMIZER_SERVICE_PACKAGE =
+ "org.chromium.webapk.shell_apk.test.dex_optimizer";
+
+ /**
+ * Class which implements DexOptimizerService.
+ */
+ private static final String DEX_OPTIMIZER_SERVICE_CLASS_NAME =
+ "org.chromium.webapk.shell_apk.test.dex_optimizer.DexOptimizerServiceImpl";
+
+ /**
+ * Name of the dex file in DexOptimizer.apk.
+ */
+ private static final String DEX_ASSET_NAME = "canary.dex";
+
+ /**
+ * Class to load to check whether dex is valid.
+ */
+ private static final String CANARY_CLASS_NAME =
+ "org.chromium.webapk.shell_apk.test.canary.Canary";
+
+ private Context mContext;
+ private Context mRemoteContext;
+ private File mLocalDexDir;
+ private IDexOptimizerService mDexOptimizerService;
+ private ServiceConnection mServiceConnection;
+
+ /**
+ * Monitors read files and modified files in the directory passed to the constructor.
+ */
+ private static class FileMonitor extends FileObserver {
+ public ArrayList<String> mReadPaths = new ArrayList<String>();
+ public ArrayList<String> mModifiedPaths = new ArrayList<String>();
+
+ public FileMonitor(File directory) {
+ super(directory.getPath());
+ }
+
+ @Override
+ public void onEvent(int event, String path) {
+ switch (event) {
+ case FileObserver.ACCESS:
+ mReadPaths.add(path);
+ break;
+ case FileObserver.CREATE:
+ case FileObserver.DELETE:
+ case FileObserver.DELETE_SELF:
+ case FileObserver.MODIFY:
+ mModifiedPaths.add(path);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void setUp() {
+ mContext = getInstrumentation().getTargetContext();
+ mRemoteContext = getRemoteContext(mContext);
+
+ mLocalDexDir = mContext.getDir("dex", Context.MODE_PRIVATE);
+ if (mLocalDexDir.exists()) {
+ FileUtils.recursivelyDeleteFile(mLocalDexDir);
+ if (mLocalDexDir.exists()) {
+ fail("Could not delete local dex directory.");
+ }
+ }
+
+ connectToDexOptimizerService();
+
+ try {
+ if (!mDexOptimizerService.deleteDexDirectory()) {
+ fail("Could not delete remote dex directory.");
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ fail("Remote crashed during setup.");
+ }
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mContext.unbindService(mServiceConnection);
+ super.tearDown();
+ }
+
+ /**
+ * Test that {@DexLoader#load()} can create a ClassLoader from a dex and optimized dex in
+ * another app's data directory.
+ */
+ @MediumTest
+ public void testLoadFromRemoteDataDir() {
+ // Extract the dex file into another app's data directory and optimize the dex.
+ String remoteDexFilePath = null;
+ try {
+ remoteDexFilePath = mDexOptimizerService.extractAndOptimizeDex();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ fail("Remote crashed.");
+ }
+
+ if (remoteDexFilePath == null) {
+ fail("Could not extract and optimize dex.");
+ }
+
+ // Check that the Android OS knows about the optimized dex file for
+ // {@link remoteDexFilePath}.
+ File remoteDexFile = new File(remoteDexFilePath);
+ assertFalse(isDexOptNeeded(remoteDexFile));
+
+ ClassLoader loader = DexLoader.load(
+ mRemoteContext, DEX_ASSET_NAME, CANARY_CLASS_NAME, remoteDexFile, mLocalDexDir);
+ assertNotNull(loader);
+ assertTrue(canLoadCanaryClass(loader));
+
+ // Check that {@link DexLoader#load()} did not use the fallback path.
+ assertFalse(mLocalDexDir.exists());
+ }
+
+ /**
+ * That that {@link DexLoader#load()} falls back to extracting the dex from the APK to the
+ * local data directory and creating the ClassLoader from the extracted dex if creating the
+ * ClassLoader from the cached data in the remote Context's data directory fails.
+ */
+ @MediumTest
+ public void testLoadFromLocalDataDir() {
+ ClassLoader loader = DexLoader.load(
+ mRemoteContext, DEX_ASSET_NAME, CANARY_CLASS_NAME, null, mLocalDexDir);
+ assertNotNull(loader);
+ assertTrue(canLoadCanaryClass(loader));
+
+ // Check that the dex file was extracted to the local data directory and that a directory
+ // was created for the optimized dex.
+ assertTrue(mLocalDexDir.exists());
+ File[] localDexDirFiles = mLocalDexDir.listFiles();
+ assertNotNull(localDexDirFiles);
+ Arrays.sort(localDexDirFiles);
+ assertEquals(2, localDexDirFiles.length);
+ assertEquals(DEX_ASSET_NAME, localDexDirFiles[0].getName());
+ assertFalse(localDexDirFiles[0].isDirectory());
+ assertEquals("optimized", localDexDirFiles[1].getName());
+ assertTrue(localDexDirFiles[1].isDirectory());
+ }
+
+ /**
+ * Test that {@link DexLoader#load()} does not extract the dex file from the APK if the dex file
+ * was extracted in a previous call to {@link DexLoader#load()}
+ */
+ @MediumTest
+ public void testPreviouslyLoadedFromLocalDataDir() {
+ assertTrue(mLocalDexDir.mkdir());
+
+ {
+ // Load dex the first time. This should extract the dex file from the APK's assets and
+ // generate the optimized dex file.
+ FileMonitor localDexDirMonitor = new FileMonitor(mLocalDexDir);
+ localDexDirMonitor.startWatching();
+ ClassLoader loader = DexLoader.load(
+ mRemoteContext, DEX_ASSET_NAME, CANARY_CLASS_NAME, null, mLocalDexDir);
+ localDexDirMonitor.stopWatching();
+
+ assertNotNull(loader);
+ assertTrue(canLoadCanaryClass(loader));
+
+ assertTrue(localDexDirMonitor.mReadPaths.contains(DEX_ASSET_NAME));
+ assertTrue(localDexDirMonitor.mModifiedPaths.contains(DEX_ASSET_NAME));
+ }
+ {
+ // Load dex a second time. We should use the already extracted dex file.
+ FileMonitor localDexDirMonitor = new FileMonitor(mLocalDexDir);
+ localDexDirMonitor.startWatching();
+ ClassLoader loader = DexLoader.load(
+ mRemoteContext, DEX_ASSET_NAME, CANARY_CLASS_NAME, null, mLocalDexDir);
+ localDexDirMonitor.stopWatching();
+
+ // The returned ClassLoader should be valid.
+ assertNotNull(loader);
+ assertTrue(canLoadCanaryClass(loader));
+
+ // We should not have modified any files and have used the already extracted dex file.
+ assertTrue(localDexDirMonitor.mReadPaths.contains(DEX_ASSET_NAME));
+ assertTrue(localDexDirMonitor.mModifiedPaths.isEmpty());
+ }
+ }
+
+ /**
+ * Test that {@link DexLoader#load()} re-extracts the dex file from the APK after a call to
+ * {@link DexLoader#deleteCachedDexes()}.
+ */
+ @MediumTest
+ public void testLoadAfterDeleteCachedDexes() {
+ assertTrue(mLocalDexDir.mkdir());
+
+ {
+ // Load dex the first time. This should extract the dex file from the APK's assets and
+ // generate the optimized dex file.
+ FileMonitor localDexDirMonitor = new FileMonitor(mLocalDexDir);
+ localDexDirMonitor.startWatching();
+ ClassLoader loader = DexLoader.load(
+ mRemoteContext, DEX_ASSET_NAME, CANARY_CLASS_NAME, null, mLocalDexDir);
+ localDexDirMonitor.stopWatching();
+
+ assertNotNull(loader);
+ assertTrue(canLoadCanaryClass(loader));
+
+ assertTrue(localDexDirMonitor.mReadPaths.contains(DEX_ASSET_NAME));
+ assertTrue(localDexDirMonitor.mModifiedPaths.contains(DEX_ASSET_NAME));
+ }
+
+ DexLoader.deleteCachedDexes(mLocalDexDir);
+
+ {
+ // Load dex a second time.
+ FileMonitor localDexDirMonitor = new FileMonitor(mLocalDexDir);
+ localDexDirMonitor.startWatching();
+ ClassLoader loader = DexLoader.load(
+ mRemoteContext, DEX_ASSET_NAME, CANARY_CLASS_NAME, null, mLocalDexDir);
+ localDexDirMonitor.stopWatching();
+
+ // The returned ClassLoader should be valid.
+ assertNotNull(loader);
+ assertTrue(canLoadCanaryClass(loader));
+
+ // We should have re-extracted the dex from the APK's assets.
+ assertTrue(localDexDirMonitor.mReadPaths.contains(DEX_ASSET_NAME));
+ assertTrue(localDexDirMonitor.mModifiedPaths.contains(DEX_ASSET_NAME));
+ }
+ }
+
+ /**
+ * Connects to the DexOptimizerService.
+ */
+ private void connectToDexOptimizerService() {
+ Intent intent = new Intent();
+ intent.setComponent(
+ new ComponentName(DEX_OPTIMIZER_SERVICE_PACKAGE, DEX_OPTIMIZER_SERVICE_CLASS_NAME));
+ final CallbackHelper connectedCallback = new CallbackHelper();
+
+ mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mDexOptimizerService = IDexOptimizerService.Stub.asInterface(service);
+ connectedCallback.notifyCalled();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {}
+ };
+
+ try {
+ mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ fail();
+ }
+
+ try {
+ connectedCallback.waitForCallback(0);
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("Could not connect to remote.");
+ }
+ }
+
+ /**
+ * Returns the Context of the APK which provides DexOptimizerService.
+ * @param context The test application's Context.
+ * @return Context of the APK whcih provide DexOptimizerService.
+ */
+ private Context getRemoteContext(Context context) {
+ try {
+ return context.getApplicationContext().createPackageContext(
+ DEX_OPTIMIZER_SERVICE_PACKAGE,
+ Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ fail("Could not get remote context");
+ return null;
+ }
+ }
+
+ /** Returns whether the Android OS thinks that a dex file needs to be re-optimized */
+ private boolean isDexOptNeeded(File dexFile) {
+ try {
+ return DexFile.isDexOptNeeded(dexFile.getPath());
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ return false;
+ }
+ }
+
+ /** Returns whether the ClassLoader can load {@link CANARY_CLASS_NAME} */
+ private boolean canLoadCanaryClass(ClassLoader loader) {
+ try {
+ loader.loadClass(CANARY_CLASS_NAME);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698