diff options
4 files changed, 279 insertions, 0 deletions
diff --git a/test/update-rollback/Android.bp b/test/update-rollback/Android.bp new file mode 100644 index 0000000000..3713328b06 --- /dev/null +++ b/test/update-rollback/Android.bp @@ -0,0 +1,27 @@ +// Copyright (C) 2021 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 { + default_applicable_licenses: ["art_license"], +} + +java_test_host { + name: "art-apex-update-rollback", + defaults: ["art_module_source_build_java_defaults"], + srcs: ["src/**/*.java"], + libs: ["tradefed"], + static_libs: ["cts-install-lib-host"], + data: [":test_broken_com.android.art"], + test_suites: ["general-tests"], +} diff --git a/test/update-rollback/AndroidTest.xml b/test/update-rollback/AndroidTest.xml new file mode 100644 index 0000000000..d389cc2df8 --- /dev/null +++ b/test/update-rollback/AndroidTest.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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. +--> +<configuration description="Configuration for broken ART APEX rollback test."> + <option name="test-suite-tag" value="art-apex-update-rollback" /> + <option name="test-suite-tag" value="apct" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="jar" value="art-apex-update-rollback.jar" /> + </test> +</configuration> diff --git a/test/update-rollback/src/com/android/tests/apex/art/ArtApexTestUtils.java b/test/update-rollback/src/com/android/tests/apex/art/ArtApexTestUtils.java new file mode 100644 index 0000000000..79bea9fdc0 --- /dev/null +++ b/test/update-rollback/src/com/android/tests/apex/art/ArtApexTestUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 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.tests.apex.art; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeTrue; + +import android.cts.install.lib.host.InstallUtilsHost; + +import com.android.tradefed.device.ITestDevice.ApexInfo; +import com.android.tradefed.invoker.TestInformation; + +import java.io.File; +import java.time.Duration; + +/** + * Utilities to install and uninstall the "broken" (test) ART APEX (which is expected to trigger a + * rollback during the next boot). + */ +public class ArtApexTestUtils { + + private static final String APEX_FILENAME = "test_broken_com.android.art.apex"; + private static final Duration BOOT_COMPLETE_TIMEOUT = Duration.ofMinutes(2); + + private final InstallUtilsHost mInstallUtils; + private final TestInformation mTestInfo; + + public ArtApexTestUtils(TestInformation testInfo) throws Exception { + assertNotNull(testInfo.getDevice()); + mInstallUtils = new InstallUtilsHost(testInfo); + mTestInfo = testInfo; + } + + public void installTestApex() throws Exception { + assumeTrue("Updating APEX is not supported", mInstallUtils.isApexUpdateSupported()); + + // Use `InstallUtilsHost.installStagedPackage` instead of `InstallUtilsHost.installApexes`, + // as the latter does not enable us to check whether the APEX was successfully installed + // (staged). + File apexFile = mInstallUtils.getTestFile(APEX_FILENAME); + String error = mInstallUtils.installStagedPackage(apexFile); + assertThat(error).isNull(); + reboot(); + } + + public void uninstallTestApex() throws Exception { + ApexInfo apex = mInstallUtils.getApexInfo(mInstallUtils.getTestFile(APEX_FILENAME)); + mTestInfo.getDevice().uninstallPackage(apex.name); + reboot(); + } + + public void reboot() throws Exception { + mTestInfo.getDevice().reboot(); + boolean success = + mTestInfo.getDevice().waitForBootComplete(BOOT_COMPLETE_TIMEOUT.toMillis()); + assertWithMessage("Device didn't boot in %s", BOOT_COMPLETE_TIMEOUT).that(success).isTrue(); + } +} diff --git a/test/update-rollback/src/com/android/tests/apex/art/BrokenArtApexUpdateRollbackHostTest.java b/test/update-rollback/src/com/android/tests/apex/art/BrokenArtApexUpdateRollbackHostTest.java new file mode 100644 index 0000000000..901b352e4b --- /dev/null +++ b/test/update-rollback/src/com/android/tests/apex/art/BrokenArtApexUpdateRollbackHostTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2021 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.tests.apex.art; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import com.android.ddmlib.Log.LogLevel; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.invoker.TestInformation; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.IDeviceTest; +import com.android.tradefed.testtype.junit4.AfterClassWithInfo; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.android.tradefed.testtype.junit4.BeforeClassWithInfo; +import com.android.tradefed.util.GenericLogcatEventParser; +import com.android.tradefed.util.StreamUtil; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test checking whether broken ART APEX updates are properly rolled back. + */ +@RunWith(DeviceJUnit4ClassRunner.class) +public class BrokenArtApexUpdateRollbackHostTest extends BaseHostJUnit4Test { + + private static final long EVENT_TIMEOUT_MS = 30 * 1000L; + + private ApexUpdateLogcatEventParser mLogcatEventParser; + + private static ArtApexTestUtils sTestUtils; + + @BeforeClassWithInfo + public static void beforeClassWithDevice(TestInformation testInfo) throws Exception { + sTestUtils = new ArtApexTestUtils(testInfo); + sTestUtils.installTestApex(); + } + + @Test + public void verifyArtApexIsRolledBack() throws Exception { + startLogcatListener(); + + { + ApexUpdateLogcatEventParser.LogcatEvent result = + mLogcatEventParser.waitForEvent(EVENT_TIMEOUT_MS); + assertNotNull(result); + assertEquals(ApexUpdateLogcatEventType.SESSION_REVERTED, result.getEventType()); + CLog.i("Found event type " + result.getEventType() + " in logcat: " + + result.getMessage()); + } + + { + ApexUpdateLogcatEventParser.LogcatEvent result = + mLogcatEventParser.waitForEvent(EVENT_TIMEOUT_MS); + assertNotNull(result); + assertEquals(ApexUpdateLogcatEventType.ROLLBACK_SUCCESS, result.getEventType()); + CLog.i("Found event type " + result.getEventType() + " in logcat: " + + result.getMessage()); + } + + stopLogcatListener(); + } + + // TODO(b/185672266): When tombstones are preserved across rollbacks, add a check verifying the + // presence of a tombstone for the crash that trigerred the rollback. + + // TODO(b/185672266): When logcat entries are preserved across rollbacks, add a check verifying + // the presence of relevant logcat entries (e.g. a stack trace for the native crash). + + @AfterClassWithInfo + public static void afterClassWithDevice(TestInformation testInfo) throws Exception { + // Note: This should not be necessary, as the "broken" ART APEX is expected to be rolled + // back. + sTestUtils.uninstallTestApex(); + } + + private void startLogcatListener() { + if (mLogcatEventParser == null) { + mLogcatEventParser = ApexUpdateLogcatEventParserFactory.buildParser(getDevice()); + } + mLogcatEventParser.start(); + } + + private void stopLogcatListener() { + StreamUtil.close(mLogcatEventParser); + } +} + + +/** Event types for {@link ApexUpdateLogcatEventParser}. */ +enum ApexUpdateLogcatEventType { + SESSION_REVERTED, + ROLLBACK_SUCCESS, +} + +/** Logcat parser for APEX update related events. */ +class ApexUpdateLogcatEventParser extends GenericLogcatEventParser<ApexUpdateLogcatEventType> { + public ApexUpdateLogcatEventParser(ITestDevice device) { + super(device); + } +} + +/** Creates a ApexUpdateLogcatEventParser populated with event triggers for APEX update tests. */ +class ApexUpdateLogcatEventParserFactory { + + public static ApexUpdateLogcatEventParser buildParser(ITestDevice device) { + ApexUpdateLogcatEventParser parser = new ApexUpdateLogcatEventParser(device); + return registerEventTriggers(parser); + } + + public static ApexUpdateLogcatEventParser registerEventTriggers( + ApexUpdateLogcatEventParser parser) { + // Note: Events registered first are matched first. + + // Look for a line like: + // + // 04-17 16:22:09.776 1728 1728 D PackageInstallerSession: Marking session 530520784 as failed: Session reverted due to crashing native process: zygote + // + parser.registerEventTrigger( + LogLevel.DEBUG, + "PackageInstallerSession", + "Session reverted due to crashing native process: zygote", + ApexUpdateLogcatEventType.SESSION_REVERTED); + + // Look for a line like: + // + // 04-17 16:22:23.938 1728 1880 I WatchdogRollbackLogger: Watchdog event occurred with type: ROLLBACK_SUCCESS logPackage: VersionedPackage[com.google.android.modulemetadata/310000000] rollbackReason: REASON_NATIVE_CRASH_DURING_BOOT failedPackageName: zygote + // + parser.registerEventTrigger( + LogLevel.INFO, + "WatchdogRollbackLogger", + "Watchdog event occurred with type: ROLLBACK_SUCCESS", + ApexUpdateLogcatEventType.ROLLBACK_SUCCESS); + + return parser; + } +} |