diff options
| author | 2021-10-05 15:57:49 +0100 | |
|---|---|---|
| committer | 2021-11-23 09:23:59 +0000 | |
| commit | 8e32399d7a9c0acf1c8b3e059df58783e17da45b (patch) | |
| tree | c30cf80f2c14c6d4dac6e8a2959ba865bd395263 | |
| parent | 0a74d278eb84dae4935b24d72669341542272d2b (diff) | |
Add test checking that broken ART APEX updates are properly rolled back.
Introduce test module `art-apex-update-rollback`, using test APEX
module `test_broken_com.android.art`. This test installs
`test_broken_com.android.art` (which contains a "broken" version of
`libart`, crashing during the creation of a VM) on the device under
test, reboots, and checks that the test APEX has been correctly rolled
back (by checking logcat messages).
When tombstones and logcat outputs are preserved across rollbacks,
this test should be extended to check for the presence of these
artifacts as well.
Test: atest art-apex-update-rollback
Bug: 204887479
Bug: 185672266
Change-Id: Icc4f4af226b3d1253249bd2b3cb6795881677e5a
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; + } +} |