Tests for missing blocks.
+improve the streaming framework to work with failed installations
Bug: 160635126
Test: atest PackageManagerShellCommandIncrementalTest#testInstallWithMissingBlocks --iterations
Change-Id: If5e97307a2fb43fdac9725ce98d77d589ff5e138
diff --git a/tests/tests/content/src/android/content/pm/cts/IncrementalDeviceConnection.java b/tests/tests/content/src/android/content/pm/cts/IncrementalDeviceConnection.java
index a4b6cd7..ea96139 100644
--- a/tests/tests/content/src/android/content/pm/cts/IncrementalDeviceConnection.java
+++ b/tests/tests/content/src/android/content/pm/cts/IncrementalDeviceConnection.java
@@ -32,6 +32,7 @@
import android.system.Os;
import android.system.OsConstants;
import android.system.StructPollfd;
+import android.util.Log;
import android.util.Slog;
import com.android.incfs.install.IDeviceConnection;
@@ -41,17 +42,18 @@
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
public class IncrementalDeviceConnection implements IDeviceConnection {
private static final String TAG = "IncrementalDeviceConnection";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final int POLL_TIMEOUT_MS = 300000;
- enum ConnectionType {
+ private enum ConnectionType {
RELIABLE,
UNRELIABLE,
}
@@ -125,8 +127,8 @@
@Override
public void close() throws Exception {
- IoUtils.closeQuietly(mPfd);
mShellCommand.join();
+ IoUtils.closeQuietly(mPfd);
}
static class Logger implements ILogger {
@@ -156,9 +158,23 @@
static class Factory implements IDeviceConnection.Factory {
private final ConnectionType mConnectionType;
+ private final boolean mExpectInstallationSuccess;
- Factory(ConnectionType connectionType) {
+ static Factory reliable() {
+ return new Factory(ConnectionType.RELIABLE, true);
+ }
+
+ static Factory ureliable() {
+ return new Factory(ConnectionType.UNRELIABLE, false);
+ }
+
+ static Factory reliableExpectInstallationFailure() {
+ return new Factory(ConnectionType.RELIABLE, false);
+ }
+
+ private Factory(ConnectionType connectionType, boolean expectInstallationSuccess) {
mConnectionType = connectionType;
+ mExpectInstallationSuccess = expectInstallationSuccess;
}
@Override
@@ -172,31 +188,29 @@
final ParcelFileDescriptor processPfd = pipe[1];
final ResultReceiver resultReceiver;
- if (mConnectionType == ConnectionType.RELIABLE) {
+ if (mExpectInstallationSuccess) {
resultReceiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == 0) {
return;
}
- try {
- final String message = readFullStream(
- new ParcelFileDescriptor.AutoCloseInputStream(localPfd));
- assertEquals(message, 0, resultCode);
- } catch (IOException e) {
- assertNull("Failed to pull error from failed command: " + resultCode
- + ", exception: " + e, e);
- }
+ final String message = readFullStreamOrError(
+ new FileInputStream(localPfd.getFileDescriptor()));
+ assertEquals(message, 0, resultCode);
}
};
} else {
resultReceiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
- try {
- readFullStream(new ParcelFileDescriptor.AutoCloseInputStream(localPfd));
- } catch (IOException ignored) {
+ if (resultCode == 0) {
+ return;
}
+ final String message = readFullStreamOrError(
+ new FileInputStream(localPfd.getFileDescriptor()));
+ Log.i(TAG, "Installation finished with code: " + resultCode + ", message: "
+ + message);
}
};
}
@@ -218,14 +232,20 @@
}
}
- private static String readFullStream(InputStream inputStream) throws IOException {
+ private static String readFullStreamOrError(InputStream inputStream) {
try (ByteArrayOutputStream result = new ByteArrayOutputStream()) {
- final byte[] buffer = new byte[1024];
- int length;
- while ((length = inputStream.read(buffer)) != -1) {
- result.write(buffer, 0, length);
+ try {
+ final byte[] buffer = new byte[1024];
+ int length;
+ while ((length = inputStream.read(buffer)) != -1) {
+ result.write(buffer, 0, length);
+ }
+ } catch (IOException e) {
+ return result.toString("UTF-8") + " exception [" + e + "]";
}
return result.toString("UTF-8");
+ } catch (IOException e) {
+ return e.toString();
}
}
}
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
index 240d0be..1440aeb 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
@@ -40,11 +40,13 @@
import android.service.dataloader.DataLoaderService;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.incfs.install.IBlockFilter;
import com.android.incfs.install.IBlockTransformer;
import com.android.incfs.install.IncrementalInstallSession;
import com.android.incfs.install.PendingBlock;
@@ -72,6 +74,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
+import java.util.Random;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.Callable;
@@ -88,6 +91,8 @@
@AppModeFull
@LargeTest
public class PackageManagerShellCommandIncrementalTest {
+ private static final String TAG = "PackageManagerShellCommandIncrementalTest";
+
private static final String CTS_PACKAGE_NAME = "android.content.cts";
private static final String TEST_APP_PACKAGE = "com.example.helloworld";
@@ -202,6 +207,7 @@
executeShellCommand("pm install-incremental -t -g " + file.getPath()));
}
+ @LargeTest
@Test
public void testInstallWithIdSigAndSplit() throws Exception {
File apkfile = new File(createApkPath(TEST_APK));
@@ -232,10 +238,8 @@
.build();
getUiAutomation().adoptShellPermissionIdentity();
try {
- mSession.start(
- Executors.newSingleThreadExecutor(),
- new IncrementalDeviceConnection.Factory(
- IncrementalDeviceConnection.ConnectionType.RELIABLE));
+ mSession.start(Executors.newSingleThreadExecutor(),
+ IncrementalDeviceConnection.Factory.reliable());
mSession.waitForInstallCompleted(30, TimeUnit.SECONDS);
} finally {
getUiAutomation().dropShellPermissionIdentity();
@@ -243,6 +247,65 @@
assertTrue(isAppInstalled(TEST_APP_PACKAGE));
}
+ @LargeTest
+ @Test
+ public void testInstallWithMissingBlocks() throws Exception {
+ setDeviceProperty("incfs_default_timeouts", "0:0:0");
+ setDeviceProperty("known_digesters_list", CTS_PACKAGE_NAME);
+ setSystemProperty("debug.incremental.always_enable_read_timeouts_for_system_dataloaders",
+ "0");
+
+ final long randomSeed = System.currentTimeMillis();
+ Log.i(TAG, "Randomizing missing blocks with seed: " + randomSeed);
+ final Random random = new Random(randomSeed);
+
+ // TODO: add detection of orphaned IncFS instances after failed installations
+
+ final int blockSize = 4096;
+ final int retries = 7; // 7 * 3s + leeway ~= 30secs of test timeout
+
+ final File apk = new File(createApkPath(TEST_APK));
+ final int blocks = (int) (apk.length() / blockSize);
+
+ for (int i = 0; i < retries; ++i) {
+ final int skipBlock = random.nextInt(blocks);
+ Log.i(TAG, "skipBlock: " + skipBlock + " out of " + blocks);
+ try {
+ installWithBlockFilter((block -> block.getType() == PendingBlock.Type.SIGNATURE_TREE
+ || block.getBlockIndex() != skipBlock));
+ if (isAppInstalled(TEST_APP_PACKAGE)) {
+ uninstallPackageSilently(TEST_APP_PACKAGE);
+ }
+ } catch (RuntimeException re) {
+ Log.i(TAG, "RuntimeException: ", re);
+ assertTrue(re.toString(), re.getCause() instanceof IOException);
+ } catch (IOException e) {
+ Log.i(TAG, "IOException: ", e);
+ throw e;
+ }
+ }
+ }
+
+ public void installWithBlockFilter(IBlockFilter blockFilter) throws Exception {
+ final String apk = createApkPath(TEST_APK);
+ final String idsig = createApkPath(TEST_APK_IDSIG);
+ mSession =
+ new IncrementalInstallSession.Builder()
+ .addApk(Paths.get(apk), Paths.get(idsig))
+ .addExtraArgs("-t", "-i", CTS_PACKAGE_NAME)
+ .setLogger(new IncrementalDeviceConnection.Logger())
+ .setBlockFilter(blockFilter)
+ .build();
+ getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ mSession.start(Executors.newSingleThreadExecutor(),
+ IncrementalDeviceConnection.Factory.reliableExpectInstallationFailure());
+ mSession.waitForAnyCompletion(3, TimeUnit.SECONDS);
+ } finally {
+ getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
/**
* Compress the data if the compressed size is < original size, otherwise return the original
* data.
@@ -322,10 +385,8 @@
.build();
getUiAutomation().adoptShellPermissionIdentity();
try {
- mSession.start(
- Executors.newSingleThreadExecutor(),
- new IncrementalDeviceConnection.Factory(
- IncrementalDeviceConnection.ConnectionType.RELIABLE));
+ mSession.start(Executors.newSingleThreadExecutor(),
+ IncrementalDeviceConnection.Factory.reliable());
mSession.waitForInstallCompleted(30, TimeUnit.SECONDS);
} finally {
getUiAutomation().dropShellPermissionIdentity();
@@ -346,10 +407,8 @@
.build();
getUiAutomation().adoptShellPermissionIdentity();
try {
- mSession.start(
- Executors.newSingleThreadExecutor(),
- new IncrementalDeviceConnection.Factory(
- IncrementalDeviceConnection.ConnectionType.UNRELIABLE));
+ mSession.start(Executors.newSingleThreadExecutor(),
+ IncrementalDeviceConnection.Factory.ureliable());
mSession.waitForInstallCompleted(30, TimeUnit.SECONDS);
} catch (Exception ignored) {
// Ignore, we are looking for crashes anyway.
@@ -730,10 +789,8 @@
// Partially install the apk+split0+split1.
getUiAutomation().adoptShellPermissionIdentity();
try {
- mSession.start(
- Executors.newSingleThreadExecutor(),
- new IncrementalDeviceConnection.Factory(
- IncrementalDeviceConnection.ConnectionType.RELIABLE));
+ mSession.start(Executors.newSingleThreadExecutor(),
+ IncrementalDeviceConnection.Factory.reliable());
mSession.waitForInstallCompleted(30, TimeUnit.SECONDS);
assertEquals("base, config.hdpi, config.mdpi", getSplits(TEST_APP_PACKAGE));
} finally {
@@ -1046,7 +1103,7 @@
getContext().startActivity(intent);
return () -> {
try {
- return fullyLoaded.get(10, TimeUnit.SECONDS);
+ return fullyLoaded.get(30, TimeUnit.SECONDS);
} catch (TimeoutException e) {
return false;
}
@@ -1131,6 +1188,8 @@
setSystemProperty("debug.incremental.enforce_readlogs_max_interval_for_system_dataloaders",
"0");
setSystemProperty("debug.incremental.readlogs_max_interval_sec", "10000");
+ setSystemProperty("debug.incremental.always_enable_read_timeouts_for_system_dataloaders",
+ "1");
IoUtils.closeQuietly(mSession);
mSession = null;
}