/* * Copyright 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.pm.dex; import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; import android.util.EventLog; import dalvik.system.DexClassLoader; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Formatter; import java.util.List; /** * Integration tests for {@link com.android.server.pm.dex.DexLogger}. * * The setup for the test dynamically loads code in a jar extracted * from our assets (a secondary dex file). * * We then use adb to trigger secondary dex file reconcilation (and * wait for it to complete). As a side-effect of this DexLogger should * be notified of the file and should log the hash of the file's name * and content. We verify that this message appears in the event log. * * Run with "atest DexLoggerIntegrationTests". */ @LargeTest @RunWith(JUnit4.class) public final class DexLoggerIntegrationTests { private static final String PACKAGE_NAME = "com.android.frameworks.dexloggertest"; // Event log tag used for SNET related events private static final int SNET_TAG = 0x534e4554; // Subtag used to distinguish dynamic code loading events private static final String DCL_SUBTAG = "dcl"; // Obtained via "echo -n copied.jar | sha256sum" private static final String EXPECTED_NAME_HASH = "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C"; private static String expectedContentHash; @BeforeClass public static void setUpAll() throws Exception { Context context = InstrumentationRegistry.getTargetContext(); MessageDigest hasher = MessageDigest.getInstance("SHA-256"); // Copy the jar from our Java resources to a private data directory File privateCopy = new File(context.getDir("jars", Context.MODE_PRIVATE), "copied.jar"); Class thisClass = DexLoggerIntegrationTests.class; try (InputStream input = thisClass.getResourceAsStream("/javalib.jar"); OutputStream output = new FileOutputStream(privateCopy)) { byte[] buffer = new byte[1024]; while (true) { int numRead = input.read(buffer); if (numRead < 0) { break; } output.write(buffer, 0, numRead); hasher.update(buffer, 0, numRead); } } // Remember the SHA-256 of the file content to check that it is the same as // the value we see logged. Formatter formatter = new Formatter(); for (byte b : hasher.digest()) { formatter.format("%02X", b); } expectedContentHash = formatter.toString(); // Feed the jar to a class loader and make sure it contains what we expect. ClassLoader loader = new DexClassLoader( privateCopy.toString(), null, null, context.getClass().getClassLoader()); loader.loadClass("com.android.dcl.Simple"); } @Test public void testDexLoggerReconcileGeneratesEvents() throws Exception { int[] tagList = new int[] { SNET_TAG }; List events = new ArrayList<>(); // There may already be events in the event log - figure out the most recent one EventLog.readEvents(tagList, events); long previousEventNanos = events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos(); events.clear(); Process process = Runtime.getRuntime().exec( "cmd package reconcile-secondary-dex-files " + PACKAGE_NAME); int exitCode = process.waitFor(); assertThat(exitCode).isEqualTo(0); int myUid = android.os.Process.myUid(); String expectedMessage = EXPECTED_NAME_HASH + " " + expectedContentHash; EventLog.readEvents(tagList, events); boolean found = false; for (EventLog.Event event : events) { if (event.getTimeNanos() <= previousEventNanos) { continue; } Object[] data = (Object[]) event.getData(); // We only care about DCL events that we generated. String subTag = (String) data[0]; if (!DCL_SUBTAG.equals(subTag)) { continue; } int uid = (int) data[1]; if (uid != myUid) { continue; } String message = (String) data[2]; assertThat(message).isEqualTo(expectedMessage); found = true; } assertThat(found).isTrue(); } }