diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index d3cd1a90b0b63..2614076e9b6c3 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -118,6 +118,9 @@ }, { "include-filter": "com.android.server.pm.UserSystemPackageInstallerTest" + }, + { + "include-filter": "com.android.server.pm.parsing.SystemPartitionParseTest" } ] } diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt index 086c845fa4b61..0f028f05d5141 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt @@ -23,7 +23,6 @@ import android.content.pm.ConfigurationInfo import android.content.pm.FeatureInfo import android.content.pm.InstrumentationInfo import android.content.pm.PackageInfo -import android.content.pm.PackageManager import android.content.pm.PackageParser import android.content.pm.PackageUserState import android.content.pm.PermissionInfo @@ -38,7 +37,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.pkg.PackageStateUnserialized import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever -import org.junit.After import org.junit.BeforeClass import org.mockito.Mockito import org.mockito.Mockito.anyInt @@ -49,7 +47,7 @@ open class AndroidPackageParsingTestBase { companion object { - private const val VERIFY_ALL_APKS = true + private const val VERIFY_ALL_APKS = false /** For auditing memory usage differences */ private const val DUMP_HPROF_TO_EXTERNAL = false @@ -93,21 +91,25 @@ open class AndroidPackageParsingTestBase { lateinit var newPackages: List - private val thrownInSetUp = mutableListOf() - @Suppress("ConstantConditionIf") @JvmStatic @BeforeClass fun setUpPackages() { this.oldPackages = apks.mapNotNull { - tryOrNull { + try { packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false) + } catch (ignored: Exception) { + // Parsing issues will be caught by SystemPartitionParseTest + null } } this.newPackages = apks.mapNotNull { - tryOrNull { + try { packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false) + } catch (ignored: Exception) { + // Parsing issues will be caught by SystemPartitionParseTest + null } } @@ -144,41 +146,6 @@ open class AndroidPackageParsingTestBase { this.pkg = aPkg whenever(pkgState) { PackageStateUnserialized() } } - - private fun tryOrNull(block: () -> T) = try { - block() - } catch (e: PackageParser.PackageParserException) { - if (e.error != PackageManager.INSTALL_PARSE_FAILED_SKIPPED) { - thrownInSetUp.add(e) - } - null - } catch (t: Throwable) { - thrownInSetUp.add(t) - null - } - } - - @After - fun verifySetUpPackages() { - if (thrownInSetUp.isEmpty()) return - val exception = AssertionError("setUpPackages failed with ${thrownInSetUp.size} errors:\n" + - thrownInSetUp.joinToString(separator = "\n") { it.message.orEmpty() }) - - /* - Testing infrastructure doesn't currently support errors thrown in @AfterClass, - so instead it's thrown here. But to avoid throwing a massive repeated stack for every - test method, only throw on the first method run in the class, clearing the list so that - subsequent methods can run without failing. Doing this in @After lets true method - failures propagate, as those should throw before this does. - - This will cause the failure to be attached to a different method depending on run order, - which could make comparisons difficult. So if a failure points here, it's worth - checking failures for all methods in all subclasses. - - TODO: When infrastructure supports @AfterClass errors, move this - */ - thrownInSetUp.clear() - throw exception } // The following methods dump an exact set of fields from the object to compare, because @@ -285,7 +252,8 @@ open class AndroidPackageParsingTestBase { secondaryCpuAbi=${this.secondaryCpuAbi} secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} sourceDir=${this.sourceDir} - splitDependencies=${this.splitDependencies.sequence().map { it.first to it.second?.contentToString() }.joinToString()} + splitDependencies=${this.splitDependencies.sequence() + .map { it.first to it.second?.contentToString() }.joinToString()} splitNames=${this.splitNames?.contentToString()} splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()} splitSourceDirs=${this.splitSourceDirs?.contentToString()} @@ -348,7 +316,9 @@ open class AndroidPackageParsingTestBase { initOrder=${this.initOrder} isSyncable=${this.isSyncable} multiprocess=${this.multiprocess} - pathPermissions=${this.pathPermissions?.joinToString { "readPermission=${it.readPermission}\nwritePermission=${it.writePermission}" }} + pathPermissions=${this.pathPermissions?.joinToString { + "readPermission=${it.readPermission}\nwritePermission=${it.writePermission}" + }} readPermission=${this.readPermission} uriPermissionPatterns=${this.uriPermissionPatterns?.contentToString()} writePermission=${this.writePermission} @@ -370,7 +340,9 @@ open class AndroidPackageParsingTestBase { compileSdkVersionCodename=${this.compileSdkVersionCodename} configPreferences=${this.configPreferences?.joinToString { it.dumpToString() }} coreApp=${this.coreApp} - featureGroups=${this.featureGroups?.joinToString { it.features?.joinToString { featureInfo -> featureInfo.dumpToString() }.orEmpty() }} + featureGroups=${this.featureGroups?.joinToString { + it.features?.joinToString { featureInfo -> featureInfo.dumpToString() }.orEmpty() + }} firstInstallTime=${this.firstInstallTime} gids=${gids?.contentToString()} installLocation=${this.installLocation} @@ -396,7 +368,8 @@ open class AndroidPackageParsingTestBase { sharedUserId=${this.sharedUserId} sharedUserLabel=${this.sharedUserLabel} signatures=${this.signatures?.joinToString { it.toCharsString() }} - signingInfo=${this.signingInfo?.signingCertificateHistory?.joinToString { it.toCharsString() }.orEmpty()} + signingInfo=${this.signingInfo?.signingCertificateHistory + ?.joinToString { it.toCharsString() }.orEmpty()} splitNames=${this.splitNames?.contentToString()} splitRevisionCodes=${this.splitRevisionCodes?.contentToString()} targetOverlayableName=${this.targetOverlayableName} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt new file mode 100644 index 0000000000000..605841df0c5a4 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 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.parsing + +import android.content.pm.PackageManager +import android.content.pm.PackageParser +import android.platform.test.annotations.Postsubmit +import com.android.server.pm.PackageManagerService +import org.junit.Test + +/** + * This test parses all the system APKs on the device image to ensure that they succeed. + * + * Any invalid APKs should be removed from the device or marked as skipped through any mechanism + * for ignoring packages. + * + * This test must run on deferred postsubmit. Targeted presubmit will not catch errors fast enough, + * and the low failure rate does not warrant global presubmit. + */ +@Postsubmit +class SystemPartitionParseTest { + + private val APKS = PackageManagerService.SYSTEM_PARTITIONS + .flatMap { listOfNotNull(it.appFolder, it.privAppFolder, it.overlayFolder) } + .flatMap { + it.walkTopDown() + .filter { it.name.endsWith(".apk") } + .toList() + } + .distinct() + + private val parser = PackageParser2.forParsingFileWithDefaults() + + @Test + fun verify() { + val exceptions = APKS + .map { + runCatching { + parser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false) + } + } + .mapNotNull { it.exceptionOrNull() } + .filterNot { (it as? PackageParser.PackageParserException)?.error == + PackageManager.INSTALL_PARSE_FAILED_SKIPPED } + + if (exceptions.isEmpty()) return + + throw AssertionError("verify failed with ${exceptions.size} errors:\n" + + exceptions.joinToString(separator = "\n") { it.message.orEmpty() }) + } +}