Merge "Fix remounting overlay tests failure"

This commit is contained in:
TreeHugger Robot
2020-01-17 00:00:23 +00:00
committed by Android (Google) Code Review
6 changed files with 51 additions and 294 deletions

View File

@@ -19,9 +19,6 @@
<option name="test-tag" value="OverlayRemountedTest" />
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="remount" />
</target_preparer>
<test class="com.android.tradefed.testtype.HostTest">
<option name="jar" value="OverlayRemountedTest.jar" />

View File

@@ -1,114 +0,0 @@
/*
* Copyright (C) 2019 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.overlaytest.remounted;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import org.junit.Rule;
import org.junit.rules.RuleChain;
import org.junit.rules.TemporaryFolder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class OverlayHostTest extends BaseHostJUnit4Test {
private static final long TIME_OUT_MS = 30000;
private static final String RES_INSTRUMENTATION_ARG = "res";
private static final String OVERLAY_INSTRUMENTATION_ARG = "overlays";
private static final String RESOURCES_TYPE_SUFFIX = "_type";
private static final String RESOURCES_DATA_SUFFIX = "_data";
public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
public final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice);
@Rule
public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer);
private Map<String, String> mLastResults;
/**
* Retrieves the values of the resources in the test package. The test package must use the
* {@link com.android.overlaytest.remounted.target.ResourceRetrievalRunner} instrumentation.
**/
void retrieveResource(String testPackageName, List<String> requiredOverlayPaths,
String... resourceNames) throws DeviceNotAvailableException {
final HashMap<String, String> args = new HashMap<>();
if (!requiredOverlayPaths.isEmpty()) {
// Enclose the require overlay paths in quotes so the arguments will be string arguments
// rather than file arguments.
args.put(OVERLAY_INSTRUMENTATION_ARG,
String.format("\"%s\"", String.join(" ", requiredOverlayPaths)));
}
if (resourceNames.length == 0) {
throw new IllegalArgumentException("Must specify at least one resource to retrieve.");
}
// Pass the names of the resources to retrieve into the test as one string.
args.put(RES_INSTRUMENTATION_ARG,
String.format("\"%s\"", String.join(" ", resourceNames)));
runDeviceTests(getDevice(), null, testPackageName, null, null, null, TIME_OUT_MS,
TIME_OUT_MS, TIME_OUT_MS, false, false, args);
// Retrieve the results of the most recently run test.
mLastResults = (getLastDeviceRunResults().getRunMetrics() == mLastResults) ? null :
getLastDeviceRunResults().getRunMetrics();
}
/** Returns the base resource directories of the specified packages. */
List<String> getPackagePaths(String... packageNames)
throws DeviceNotAvailableException {
final ArrayList<String> paths = new ArrayList<>();
for (String packageName : packageNames) {
// Use the package manager shell command to find the path of the package.
final String result = getDevice().executeShellCommand(
String.format("pm dump %s | grep \"resourcePath=\"", packageName));
assertNotNull("Failed to find path for package " + packageName, result);
int splitIndex = result.indexOf('=');
assertTrue(splitIndex >= 0);
paths.add(result.substring(splitIndex + 1).trim());
}
return paths;
}
/** Builds the full name of a resource in the form package:type/entry. */
String resourceName(String pkg, String type, String entry) {
return String.format("%s:%s/%s", pkg, type, entry);
}
/**
* Asserts that the type and data of a a previously retrieved is the same as expected.
* @param resourceName the full name of the resource in the form package:type/entry
* @param type the expected {@link android.util.TypedValue} type of the resource
* @param data the expected value of the resource when coerced to a string using
* {@link android.util.TypedValue#coerceToString()}
**/
void assertResource(String resourceName, int type, String data) {
assertNotNull("Failed to get test results", mLastResults);
assertNotEquals("No resource values were retrieved", mLastResults.size(), 0);
assertEquals("" + type, mLastResults.get(resourceName + RESOURCES_TYPE_SUFFIX));
assertEquals("" + data, mLastResults.get(resourceName + RESOURCES_DATA_SUFFIX));
}
}

View File

@@ -16,17 +16,21 @@
package com.android.overlaytest.remounted;
import static org.junit.Assert.assertTrue;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import java.util.Collections;
import java.util.List;
@RunWith(DeviceJUnit4ClassRunner.class)
public class OverlaySharedLibraryTest extends OverlayHostTest {
public class OverlaySharedLibraryTest extends BaseHostJUnit4Test {
private static final String TARGET_APK = "OverlayRemountedTest_Target.apk";
private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target";
private static final String SHARED_LIBRARY_APK =
@@ -38,6 +42,17 @@ public class OverlaySharedLibraryTest extends OverlayHostTest {
private static final String SHARED_LIBRARY_OVERLAY_PACKAGE =
"com.android.overlaytest.remounted.shared_library.overlay";
public final TemporaryFolder temporaryFolder = new TemporaryFolder();
public final SystemPreparer preparer = new SystemPreparer(temporaryFolder, this::getDevice);
@Rule
public final RuleChain ruleChain = RuleChain.outerRule(temporaryFolder).around(preparer);
@Before
public void startBefore() throws DeviceNotAvailableException {
getDevice().waitForDeviceAvailable();
}
@Test
public void testSharedLibrary() throws Exception {
final String targetResource = resourceName(TARGET_PACKAGE, "bool",
@@ -45,23 +60,20 @@ public class OverlaySharedLibraryTest extends OverlayHostTest {
final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
"shared_library_overlaid");
mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
.installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
.reboot()
.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false)
.installResourceApk(TARGET_APK, TARGET_PACKAGE);
// The shared library resource is not currently overlaid.
retrieveResource(Collections.emptyList(), targetResource, libraryResource);
assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "false");
assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "false");
assertResource(targetResource, "false");
assertResource(libraryResource, "false");
// Overlay the shared library resource.
mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource,
libraryResource);
assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
preparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
assertResource(targetResource, "true");
assertResource(libraryResource, "true");
}
@Test
@@ -71,20 +83,27 @@ public class OverlaySharedLibraryTest extends OverlayHostTest {
final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
"shared_library_overlaid");
mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
.installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true)
.reboot()
.installResourceApk(TARGET_APK, TARGET_PACKAGE);
retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource,
libraryResource);
assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
assertResource(targetResource, "true");
assertResource(libraryResource, "true");
}
private void retrieveResource(List<String> requiredOverlayPaths, String... resourceNames)
/** Builds the full name of a resource in the form package:type/entry. */
String resourceName(String pkg, String type, String entry) {
return String.format("%s:%s/%s", pkg, type, entry);
}
void assertResource(String resourceName, String expectedValue)
throws DeviceNotAvailableException {
retrieveResource(TARGET_PACKAGE, requiredOverlayPaths, resourceNames);
final String result = getDevice().executeShellCommand(
String.format("cmd overlay lookup %s %s", TARGET_PACKAGE, resourceName));
assertTrue(String.format("expected: <[%s]> in: <[%s]>", expectedValue, result),
result.equals(expectedValue + "\n") ||
result.endsWith("-> " + expectedValue + "\n"));
}
}

View File

@@ -38,8 +38,7 @@ import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeoutException;
class SystemPreparer extends ExternalResource {
private static final long REBOOT_SLEEP_MS = 30000;
private static final long OVERLAY_ENABLE_TIMEOUT_MS = 20000;
private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000;
// The paths of the files pushed onto the device through this rule.
private ArrayList<String> mPushedFiles = new ArrayList<>();
@@ -59,6 +58,7 @@ class SystemPreparer extends ExternalResource {
SystemPreparer pushResourceFile(String resourcePath,
String outputPath) throws DeviceNotAvailableException, IOException {
final ITestDevice device = mDeviceProvider.getDevice();
device.executeAdbCommand("remount");
assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath));
mPushedFiles.add(outputPath);
return this;
@@ -77,7 +77,7 @@ class SystemPreparer extends ExternalResource {
/** Sets the enable state of an overlay pacakage. */
SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
throws ExecutionException, TimeoutException {
throws ExecutionException, DeviceNotAvailableException {
final ITestDevice device = mDeviceProvider.getDevice();
// Wait for the overlay to change its enabled state.
@@ -86,8 +86,10 @@ class SystemPreparer extends ExternalResource {
device.executeShellCommand(String.format("cmd overlay %s %s",
enabled ? "enable" : "disable", packageName));
final String pattern = (enabled ? "[x]" : "[ ]") + " " + packageName;
if (device.executeShellCommand("cmd overlay list").contains(pattern)) {
final String result = device.executeShellCommand("cmd overlay dump " + packageName);
final int startIndex = result.indexOf("mIsEnabled");
final int endIndex = result.indexOf('\n', startIndex);
if (result.substring(startIndex, endIndex).contains((enabled) ? "true" : "false")) {
return true;
}
}
@@ -98,6 +100,8 @@ class SystemPreparer extends ExternalResource {
try {
enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException ignored) {
} catch (TimeoutException e) {
throw new IllegalStateException(device.executeShellCommand("cmd overlay list"));
}
return this;
@@ -106,14 +110,7 @@ class SystemPreparer extends ExternalResource {
/** Restarts the device and waits until after boot is completed. */
SystemPreparer reboot() throws DeviceNotAvailableException {
final ITestDevice device = mDeviceProvider.getDevice();
device.executeShellCommand("stop");
device.executeShellCommand("start");
try {
// Sleep until the device is ready for test execution.
Thread.sleep(REBOOT_SLEEP_MS);
} catch (InterruptedException ignored) {
}
device.reboot();
return this;
}
@@ -141,12 +138,14 @@ class SystemPreparer extends ExternalResource {
protected void after() {
final ITestDevice device = mDeviceProvider.getDevice();
try {
device.executeAdbCommand("remount");
for (final String file : mPushedFiles) {
device.deleteFile(file);
}
for (final String packageName : mInstalledPackages) {
device.uninstallPackage(packageName);
}
device.reboot();
} catch (DeviceNotAvailableException e) {
Assert.fail(e.toString());
}

View File

@@ -23,8 +23,4 @@
<uses-library android:name="com.android.overlaytest.remounted.shared_library"
android:required="true" />
</application>
<instrumentation android:name="com.android.overlaytest.remounted.target.ResourceRetrievalRunner"
android:targetPackage="com.android.overlaytest.remounted.target"
android:label="Remounted system RRO tests" />
</manifest>

View File

@@ -1,140 +0,0 @@
/*
* Copyright (C) 2019 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.overlaytest.remounted.target;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
/**
* An {@link Instrumentation} that retrieves the value of specified resources within the
* application.
**/
public class ResourceRetrievalRunner extends Instrumentation {
private static final String TAG = ResourceRetrievalRunner.class.getSimpleName();
// A list of whitespace separated resource names of which to retrieve the resource values.
private static final String RESOURCE_LIST_TAG = "res";
// A list of whitespace separated overlay package paths that must be present before retrieving
// resource values.
private static final String REQUIRED_OVERLAYS_LIST_TAG = "overlays";
// The suffixes of the keys returned from the instrumentation. To retrieve the type of a
// resource looked up with the instrumentation, append the {@link #RESOURCES_TYPE_SUFFIX} suffix
// to the end of the name of the resource. For the value of a resource, use
// {@link #RESOURCES_DATA_SUFFIX} instead.
private static final String RESOURCES_TYPE_SUFFIX = "_type";
private static final String RESOURCES_DATA_SUFFIX = "_data";
// The amount of time in seconds to wait for the overlays to be present in the AssetManager.
private static final int OVERLAY_PATH_TIMEOUT = 60;
private final ArrayList<String> mResourceNames = new ArrayList<>();
private final ArrayList<String> mOverlayPaths = new ArrayList<>();
private final Bundle mResult = new Bundle();
/**
* Receives the instrumentation arguments and runs the resource retrieval.
* The entry with key {@link #RESOURCE_LIST_TAG} in the {@link Bundle} arguments is a
* whitespace separated string of resource names of which to retrieve the resource values.
* The entry with key {@link #REQUIRED_OVERLAYS_LIST_TAG} in the {@link Bundle} arguments is a
* whitespace separated string of overlay package paths prefixes that must be present before
* retrieving the resource values.
*/
@Override
public void onCreate(Bundle arguments) {
super.onCreate(arguments);
mResourceNames.addAll(Arrays.asList(arguments.getString(RESOURCE_LIST_TAG).split(" ")));
if (arguments.containsKey(REQUIRED_OVERLAYS_LIST_TAG)) {
mOverlayPaths.addAll(Arrays.asList(
arguments.getString(REQUIRED_OVERLAYS_LIST_TAG).split(" ")));
}
start();
}
@Override
public void onStart() {
final Resources res = getContext().getResources();
res.getAssets().setResourceResolutionLoggingEnabled(true);
if (!mOverlayPaths.isEmpty()) {
Log.d(TAG, String.format("Waiting for overlay paths [%s]",
String.join(",", mOverlayPaths)));
// Wait for all required overlays to be present in the AssetManager.
final FutureTask<Boolean> overlayListener = new FutureTask<>(() -> {
while (!mOverlayPaths.isEmpty()) {
final String[] apkPaths = res.getAssets().getApkPaths();
for (String path : apkPaths) {
for (String overlayPath : mOverlayPaths) {
if (path.startsWith(overlayPath)) {
mOverlayPaths.remove(overlayPath);
break;
}
}
}
}
return true;
});
try {
final Executor executor = (t) -> new Thread(t).start();
executor.execute(overlayListener);
overlayListener.get(OVERLAY_PATH_TIMEOUT, TimeUnit.SECONDS);
} catch (Exception e) {
Log.e(TAG, String.format("Failed to wait for required overlays [%s]",
String.join(",", mOverlayPaths)), e);
finish(Activity.RESULT_CANCELED, mResult);
}
}
// Retrieve the values for each resource passed in.
final TypedValue typedValue = new TypedValue();
for (final String resourceName : mResourceNames) {
try {
final int resId = res.getIdentifier(resourceName, null, null);
res.getValue(resId, typedValue, true);
Log.d(TAG, String.format("Resolution for 0x%s: %s", Integer.toHexString(resId),
res.getAssets().getLastResourceResolution()));
} catch (Resources.NotFoundException e) {
Log.e(TAG, "Failed to retrieve value for resource " + resourceName, e);
finish(Activity.RESULT_CANCELED, mResult);
}
putValue(resourceName, typedValue);
}
finish(Activity.RESULT_OK, mResult);
}
private void putValue(String resourceName, TypedValue value) {
mResult.putInt(resourceName + RESOURCES_TYPE_SUFFIX, value.type);
final CharSequence textValue = value.coerceToString();
mResult.putString(resourceName + RESOURCES_DATA_SUFFIX,
textValue == null ? "null" : textValue.toString());
}
}