diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-09 06:04:57 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-09 06:04:57 +0000 |
commit | a17351833c0afc5055a9b56bcf7647e48c454695 (patch) | |
tree | 34a81af095a69c5e94e06bc4bdedd0b0a9238b82 | |
parent | 6fdf80e1975e95d1edd3811815fb6b9c4e59164b (diff) | |
parent | f5008b11d543b2dbce00e0fa3a13f59775df05a2 (diff) | |
download | csuite-a17351833c0afc5055a9b56bcf7647e48c454695.tar.gz |
Snap for 8558685 from f5008b11d543b2dbce00e0fa3a13f59775df05a2 to tm-frc-documentsui-releaset_frc_doc_330543000t_frc_doc_330443060t_frc_doc_330443000android13-frc-documentsui-release
Change-Id: I030f831add81d7a6145f86d5dd1a8547793584ec
33 files changed, 1650 insertions, 83 deletions
diff --git a/harness/src/main/java/com/android/compatibility/targetprep/CheckGmsPreparer.java b/harness/src/main/java/com/android/compatibility/targetprep/CheckGmsPreparer.java index 24c2762..4e4d565 100644 --- a/harness/src/main/java/com/android/compatibility/targetprep/CheckGmsPreparer.java +++ b/harness/src/main/java/com/android/compatibility/targetprep/CheckGmsPreparer.java @@ -20,6 +20,7 @@ import com.android.tradefed.config.Option; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.invoker.TestInformation; import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.result.error.DeviceErrorIdentifier; import com.android.tradefed.targetprep.ITargetPreparer; import com.android.tradefed.targetprep.TargetSetupError; import com.android.tradefed.util.CommandResult; @@ -58,7 +59,8 @@ public final class CheckGmsPreparer implements ITargetPreparer { mEnable = false; throw new TargetSetupError( "GMS required but did not detect a running GMS process after device reboot", - testInfo.getDevice().getDeviceDescriptor()); + testInfo.getDevice().getDeviceDescriptor(), + DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE); } } diff --git a/harness/src/main/java/com/android/csuite/core/ApkInstaller.java b/harness/src/main/java/com/android/csuite/core/ApkInstaller.java new file mode 100644 index 0000000..2882164 --- /dev/null +++ b/harness/src/main/java/com/android/csuite/core/ApkInstaller.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2022 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.csuite.core; + +import com.android.csuite.core.TestUtils.TestUtilsException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.util.AaptParser; +import com.android.tradefed.util.CommandResult; +import com.android.tradefed.util.CommandStatus; +import com.android.tradefed.util.IRunUtil; +import com.android.tradefed.util.RunUtil; + +import com.google.common.annotations.VisibleForTesting; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** A utility class to install APKs. */ +public final class ApkInstaller { + private static long sCommandTimeOut = TimeUnit.MINUTES.toMillis(4); + private final String mDeviceSerial; + private final List<Path> mInstalledBaseApks = new ArrayList<>(); + private final IRunUtil mRunUtil; + private final PackageNameParser mPackageNameParser; + + public static ApkInstaller getInstance(ITestDevice device) { + return getInstance(device.getSerialNumber()); + } + + public static ApkInstaller getInstance(String deviceSerial) { + return new ApkInstaller(deviceSerial, new RunUtil(), new AaptPackageNameParser()); + } + + @VisibleForTesting + ApkInstaller(String deviceSerial, IRunUtil runUtil, PackageNameParser packageNameParser) { + mDeviceSerial = deviceSerial; + mRunUtil = runUtil; + mPackageNameParser = packageNameParser; + } + + /** + * Installs a package. + * + * @param apkPath Path to the apk files. Only accept file/directory path containing a single APK + * or split APK files for one package. + * @param args Install args for the 'adb install-multiple' command. + * @throws ApkInstallerException If the installation failed. + * @throws IOException If an IO exception occurred. + */ + public void install(Path apkPath, String... args) throws ApkInstallerException, IOException { + List<Path> apkFilePaths; + try { + apkFilePaths = TestUtils.listApks(apkPath); + } catch (TestUtilsException e) { + throw new ApkInstallerException("Failed to list APK files from the path " + apkPath, e); + } + + CLog.d("Installing a package from " + apkPath); + + String[] cmd = createInstallCommand(apkFilePaths, mDeviceSerial, args); + + CommandResult res = mRunUtil.runTimedCmd(sCommandTimeOut, cmd); + if (res.getStatus() != CommandStatus.SUCCESS) { + throw new ApkInstallerException( + String.format( + "Failed to install APKs from the path %s: %s", + apkPath, res.toString())); + } + + mInstalledBaseApks.add(apkFilePaths.get(0)); + + CLog.i("Successfully installed " + apkPath); + } + + /** + * Attempts to uninstall all the installed packages. + * + * <p>When failed to uninstall one of the installed packages, this method will still attempt to + * uninstall all other packages before throwing an exception. + * + * @throws ApkInstallerException when failed to uninstall a package. + */ + public void uninstallAllInstalledPackages() throws ApkInstallerException { + StringBuilder errorMessage = new StringBuilder(); + mInstalledBaseApks.forEach( + baseApk -> { + String packageName; + try { + packageName = mPackageNameParser.parsePackageName(baseApk); + } catch (IOException e) { + errorMessage.append( + String.format( + "Failed to parse the package name from %s. Reason: %s.\n", + baseApk, e.getMessage())); + return; + } + + String[] cmd = + new String[] {"adb", "-s", mDeviceSerial, "uninstall", packageName}; + + CommandResult res = mRunUtil.runTimedCmd(sCommandTimeOut, cmd); + if (res.getStatus() != CommandStatus.SUCCESS) { + errorMessage.append( + String.format( + "Failed to uninstall package %s from %s. Reason: %s.\n", + packageName, baseApk, res.toString())); + } + }); + + if (errorMessage.length() > 0) { + throw new ApkInstallerException(errorMessage.toString()); + } + } + + private String[] createInstallCommand( + List<Path> apkFilePaths, String deviceSerial, String[] args) { + ArrayList<String> cmd = new ArrayList<>(); + cmd.addAll(Arrays.asList("adb", "-s", deviceSerial, "install-multiple")); + + cmd.addAll(Arrays.asList(args)); + + apkFilePaths.stream().map(Path::toString).forEach(cmd::add); + + return cmd.toArray(new String[cmd.size()]); + } + + /** An exception class representing ApkInstaller error. */ + public static final class ApkInstallerException extends Exception { + /** + * Constructs a new {@link ApkInstallerException} with a meaningful error message. + * + * @param message A error message describing the cause of the error. + */ + private ApkInstallerException(String message) { + super(message); + } + + /** + * Constructs a new {@link ApkInstallerException} with a meaningful error message, and a + * cause. + * + * @param message A detailed error message. + * @param cause A {@link Throwable} capturing the original cause of the {@link + * ApkInstallerException}. + */ + private ApkInstallerException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new {@link ApkInstallerException} with a cause. + * + * @param cause A {@link Throwable} capturing the original cause of the {@link + * ApkInstallerException}. + */ + private ApkInstallerException(Throwable cause) { + super(cause); + } + } + + private static final class AaptPackageNameParser implements PackageNameParser { + @Override + public String parsePackageName(Path apkFile) throws IOException { + String packageName = AaptParser.parse(apkFile.toFile()).getPackageName(); + if (packageName == null) { + throw new IOException( + String.format("Failed to parse package name with AAPT for %s", apkFile)); + } + return packageName; + } + } + + @VisibleForTesting + interface PackageNameParser { + String parsePackageName(Path apkFile) throws IOException; + } +} diff --git a/harness/src/main/java/com/android/csuite/core/AppCrawlTester.java b/harness/src/main/java/com/android/csuite/core/AppCrawlTester.java index c26a011..96785ef 100644 --- a/harness/src/main/java/com/android/csuite/core/AppCrawlTester.java +++ b/harness/src/main/java/com/android/csuite/core/AppCrawlTester.java @@ -29,6 +29,7 @@ import com.android.tradefed.util.RunUtil; import com.android.tradefed.util.ZipUtil; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import com.google.common.io.MoreFiles; import org.junit.Assert; @@ -51,28 +52,26 @@ public final class AppCrawlTester { private final RunUtilProvider mRunUtilProvider; private final TestUtils mTestUtils; private final String mPackageName; - private final Path mApkRoot; private static final long COMMAND_TIMEOUT_MILLIS = 4 * 60 * 1000; private boolean mRecordScreen = false; private boolean mCollectGmsVersion = false; private boolean mCollectAppVersion = false; + private boolean mUiAutomatorMode = false; + private Path mApkRoot; /** * Creates an {@link AppCrawlTester} instance. * - * @param apkRoot The root path for an apk or a directory that contains apk files for a package. * @param packageName The package name of the apk files. * @param testInformation The TradeFed test information. * @param testLogData The TradeFed test output receiver. * @return an {@link AppCrawlTester} instance. */ public static AppCrawlTester newInstance( - Path apkRoot, String packageName, TestInformation testInformation, TestLogData testLogData) { return new AppCrawlTester( - apkRoot, packageName, TestUtils.getInstance(testInformation, testLogData), () -> new RunUtil()); @@ -80,12 +79,10 @@ public final class AppCrawlTester { @VisibleForTesting AppCrawlTester( - Path apkRoot, String packageName, TestUtils testUtils, RunUtilProvider runUtilProvider) { mRunUtilProvider = runUtilProvider; - mApkRoot = apkRoot; mPackageName = packageName; mTestUtils = testUtils; } @@ -190,8 +187,7 @@ public final class AppCrawlTester { throw new CrawlerException("Failed to create temp directory for output.", e); } - List<Path> apks = getApks(mApkRoot); - String[] command = createCrawlerRunCommand(mTestUtils.getTestInformation(), apks); + String[] command = createCrawlerRunCommand(mTestUtils.getTestInformation()); CLog.d("Launching package: %s.", mPackageName); @@ -348,7 +344,8 @@ public final class AppCrawlTester { } @VisibleForTesting - String[] createCrawlerRunCommand(TestInformation testInfo, List<Path> apks) { + String[] createCrawlerRunCommand(TestInformation testInfo) throws CrawlerException { + ArrayList<String> cmd = new ArrayList<>(); cmd.addAll( Arrays.asList( @@ -370,13 +367,23 @@ public final class AppCrawlTester { .toString(), "--key-store-password", // Using the publicly known default password of the debug keystore. - "android", - "--apk-file", - apks.get(0).toString())); + "android")); + + if (mUiAutomatorMode) { + cmd.addAll(Arrays.asList("--ui-automator-mode", "--app-package-name", mPackageName)); + } else { + Preconditions.checkNotNull( + mApkRoot, "Apk file path is required when not running in UIAutomator mode"); - for (int i = 1; i < apks.size(); i++) { - cmd.add("--split-apk-files"); - cmd.add(apks.get(i).toString()); + List<Path> apks = getApks(mApkRoot); + + cmd.add("--apk-file"); + cmd.add(apks.get(0).toString()); + + for (int i = 1; i < apks.size(); i++) { + cmd.add("--split-apk-files"); + cmd.add(apks.get(i).toString()); + } } return cmd.toArray(new String[cmd.size()]); @@ -410,6 +417,20 @@ public final class AppCrawlTester { mCollectAppVersion = collectAppVersion; } + /** Sets the option of whether to run the crawler with UIAutomator mode. */ + public void setUiAutomatorMode(boolean uiAutomatorMode) { + mUiAutomatorMode = uiAutomatorMode; + } + + /** + * Sets the apk file path. Required when not running in UIAutomator mode. + * + * @param apkRoot The root path for an apk or a directory that contains apk files for a package. + */ + public void setApkPath(Path apkRoot) { + mApkRoot = apkRoot; + } + @VisibleForTesting interface RunUtilProvider { IRunUtil get(); diff --git a/harness/src/test/java/com/android/csuite/core/ApkInstallerTest.java b/harness/src/test/java/com/android/csuite/core/ApkInstallerTest.java new file mode 100644 index 0000000..99962ab --- /dev/null +++ b/harness/src/test/java/com/android/csuite/core/ApkInstallerTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 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.csuite.core; + +import static org.junit.Assert.assertThrows; + +import com.android.csuite.core.ApkInstaller.ApkInstallerException; +import com.android.tradefed.util.CommandResult; +import com.android.tradefed.util.CommandStatus; +import com.android.tradefed.util.IRunUtil; + +import com.google.common.jimfs.Jimfs; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; + +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; + +@RunWith(JUnit4.class) +public final class ApkInstallerTest { + private final FileSystem mFileSystem = + Jimfs.newFileSystem(com.google.common.jimfs.Configuration.unix()); + + @Test + public void install_failedToListApks_throwsException() throws Exception { + Path root = mFileSystem.getPath("apk"); + Files.createDirectories(root); + Files.createFile(root.resolve("not_apk.file")); + ApkInstaller sut = + new ApkInstaller("serial", Mockito.mock(IRunUtil.class), apk -> "package.name"); + + assertThrows(ApkInstallerException.class, () -> sut.install(root)); + } + + @Test + public void install_installCommandFailed_throwsException() throws Exception { + Path root = mFileSystem.getPath("apk"); + Files.createDirectories(root); + Files.createFile(root.resolve("base.apk")); + IRunUtil runUtil = Mockito.mock(IRunUtil.class); + Mockito.when(runUtil.runTimedCmd(Mockito.anyLong(), ArgumentMatchers.<String>any())) + .thenReturn(createFailedCommandResult()); + ApkInstaller sut = new ApkInstaller("serial", runUtil, apk -> "package.name"); + + assertThrows(ApkInstallerException.class, () -> sut.install(root)); + } + + @Test + public void install_installCommandSucceed_doesNotThrow() throws Exception { + Path root = mFileSystem.getPath("apk"); + Files.createDirectories(root); + Files.createFile(root.resolve("base.apk")); + IRunUtil runUtil = Mockito.mock(IRunUtil.class); + Mockito.when(runUtil.runTimedCmd(Mockito.anyLong(), ArgumentMatchers.<String>any())) + .thenReturn(createSuccessfulCommandResultWithStdout("")); + ApkInstaller sut = new ApkInstaller("serial", runUtil, apk -> "package.name"); + + sut.install(root); + } + + private static CommandResult createSuccessfulCommandResultWithStdout(String stdout) { + CommandResult commandResult = new CommandResult(CommandStatus.SUCCESS); + commandResult.setExitCode(0); + commandResult.setStdout(stdout); + commandResult.setStderr(""); + return commandResult; + } + + private static CommandResult createFailedCommandResult() { + CommandResult commandResult = new CommandResult(CommandStatus.FAILED); + commandResult.setExitCode(1); + commandResult.setStdout(""); + commandResult.setStderr("error"); + return commandResult; + } +} diff --git a/harness/src/test/java/com/android/csuite/core/AppCrawlTesterTest.java b/harness/src/test/java/com/android/csuite/core/AppCrawlTesterTest.java index b3d0ccc..dc9132b 100644 --- a/harness/src/test/java/com/android/csuite/core/AppCrawlTesterTest.java +++ b/harness/src/test/java/com/android/csuite/core/AppCrawlTesterTest.java @@ -49,7 +49,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; -import java.util.List; @RunWith(JUnit4.class) public final class AppCrawlTesterTest { @@ -71,8 +70,17 @@ public final class AppCrawlTesterTest { } @Test + public void start_apkNotProvided_throwsException() throws Exception { + AppCrawlTester suj = createPreparedTestSubject(); + suj.setUiAutomatorMode(false); + + assertThrows(NullPointerException.class, () -> suj.start()); + } + + @Test public void startAndAssertAppNoCrash_noCrashDetected_doesNotThrow() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); Mockito.doReturn(new DeviceUtils.DeviceTimestamp(1L)) .when(mDeviceUtils) .currentTimeMillis(); @@ -87,7 +95,8 @@ public final class AppCrawlTesterTest { @Test public void startAndAssertAppNoCrash_dropboxEntriesDetected_throws() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); Mockito.doReturn(new DeviceUtils.DeviceTimestamp(1L)) .when(mDeviceUtils) .currentTimeMillis(); @@ -101,7 +110,8 @@ public final class AppCrawlTesterTest { @Test public void startAndAssertAppNoCrash_crawlerExceptionIsThrown_throws() throws Exception { - AppCrawlTester suj = createNotPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createNotPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); Mockito.doReturn(new DeviceUtils.DeviceTimestamp(1L)) .when(mDeviceUtils) .currentTimeMillis(); @@ -116,7 +126,8 @@ public final class AppCrawlTesterTest { @Test public void start_screenRecordEnabled_screenIsRecorded() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); suj.setRecordScreen(true); suj.start(); @@ -127,7 +138,8 @@ public final class AppCrawlTesterTest { @Test public void start_screenRecordDisabled_screenIsNotRecorded() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); suj.setRecordScreen(false); suj.start(); @@ -138,7 +150,8 @@ public final class AppCrawlTesterTest { @Test public void start_collectGmsVersionEnabled_versionIsCollected() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); suj.setCollectGmsVersion(true); suj.start(); @@ -148,7 +161,8 @@ public final class AppCrawlTesterTest { @Test public void start_collectGmsVersionDisabled_versionIsNotCollected() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); suj.setCollectGmsVersion(false); suj.start(); @@ -158,7 +172,8 @@ public final class AppCrawlTesterTest { @Test public void start_collectAppVersionEnabled_versionIsCollected() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); suj.setCollectAppVersion(true); suj.start(); @@ -168,7 +183,8 @@ public final class AppCrawlTesterTest { @Test public void start_collectAppVersionDisabled_versionIsNotCollected() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); suj.setCollectAppVersion(false); suj.start(); @@ -178,14 +194,16 @@ public final class AppCrawlTesterTest { @Test public void start_withSplitApksDirectory_doesNotThrowException() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); suj.start(); } @Test public void start_credentialIsProvidedToCrawler() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); suj.start(); @@ -200,7 +218,8 @@ public final class AppCrawlTesterTest { Files.createDirectories(root.resolve("sub")); Files.createFile(root.resolve("sub").resolve("base.apk")); Files.createFile(root.resolve("sub").resolve("config.apk")); - AppCrawlTester suj = createPreparedTestSubject(root); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(root); suj.start(); } @@ -210,7 +229,8 @@ public final class AppCrawlTesterTest { Path root = mFileSystem.getPath("apk"); Files.createDirectories(root); Files.createFile(root.resolve("base.apk")); - AppCrawlTester suj = createPreparedTestSubject(root); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(root); suj.start(); } @@ -220,7 +240,8 @@ public final class AppCrawlTesterTest { Path root = mFileSystem.getPath("apk"); Files.createDirectories(root); Files.createFile(root.resolve("single.apk")); - AppCrawlTester suj = createPreparedTestSubject(root); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(root); suj.start(); } @@ -229,7 +250,8 @@ public final class AppCrawlTesterTest { public void start_withSingleApkFile_doesNotThrowException() throws Exception { Path root = mFileSystem.getPath("single.apk"); Files.createFile(root); - AppCrawlTester suj = createPreparedTestSubject(root); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(root); suj.start(); } @@ -241,7 +263,8 @@ public final class AppCrawlTesterTest { Files.createDirectories(root); Files.createFile(root.resolve("single.apk")); Files.createFile(root.resolve("single.not_apk")); - AppCrawlTester suj = createPreparedTestSubject(root); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(root); suj.start(); } @@ -251,7 +274,8 @@ public final class AppCrawlTesterTest { Path root = mFileSystem.getPath("apk"); Files.createDirectories(root); Files.createFile(root.resolve("single.not_apk")); - AppCrawlTester suj = createPreparedTestSubject(root); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(root); assertThrows(AppCrawlTester.CrawlerException.class, () -> suj.start()); } @@ -260,7 +284,8 @@ public final class AppCrawlTesterTest { public void start_withNonApkPath_throwException() throws Exception { Path root = mFileSystem.getPath("single.not_apk"); Files.createFile(root); - AppCrawlTester suj = createPreparedTestSubject(root); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(root); assertThrows(AppCrawlTester.CrawlerException.class, () -> suj.start()); } @@ -273,21 +298,24 @@ public final class AppCrawlTesterTest { Files.createDirectories(root.resolve("2")); Files.createFile(root.resolve("1").resolve("single.apk")); Files.createFile(root.resolve("2").resolve("single.apk")); - AppCrawlTester suj = createPreparedTestSubject(root); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(root); assertThrows(AppCrawlTester.CrawlerException.class, () -> suj.start()); } @Test public void start_preparerNotRun_throwsException() throws Exception { - AppCrawlTester suj = createNotPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createNotPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); assertThrows(AppCrawlTester.CrawlerException.class, () -> suj.start()); } @Test public void start_alreadyRun_throwsException() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); suj.start(); assertThrows(AppCrawlTester.CrawlerException.class, () -> suj.start()); @@ -295,7 +323,8 @@ public final class AppCrawlTesterTest { @Test public void cleanUp_removesOutputDirectory() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(createApkPathWithSplitApks()); suj.start(); assertTrue(Files.exists(suj.mOutput)); @@ -306,11 +335,14 @@ public final class AppCrawlTesterTest { @Test public void createCrawlerRunCommand_containsRequiredCrawlerParams() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + Path apkRoot = mFileSystem.getPath("apk"); + Files.createDirectories(apkRoot); + Files.createFile(apkRoot.resolve("some.apk")); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(apkRoot); suj.start(); - List<Path> apks = Arrays.asList(Path.of("some.apk")); - String[] result = suj.createCrawlerRunCommand(mTestInfo, apks); + String[] result = suj.createCrawlerRunCommand(mTestInfo); assertThat(result).asList().contains("--key-store-file"); assertThat(result).asList().contains("--key-store-password"); @@ -320,11 +352,14 @@ public final class AppCrawlTesterTest { @Test public void createCrawlerRunCommand_crawlerIsExecutedThroughJavaJar() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + Path apkRoot = mFileSystem.getPath("apk"); + Files.createDirectories(apkRoot); + Files.createFile(apkRoot.resolve("some.apk")); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(apkRoot); suj.start(); - List<Path> apks = Arrays.asList(Path.of("some.apk")); - String[] result = suj.createCrawlerRunCommand(mTestInfo, apks); + String[] result = suj.createCrawlerRunCommand(mTestInfo); assertThat(result).asList().contains("java"); assertThat(result).asList().contains("-jar"); @@ -333,12 +368,16 @@ public final class AppCrawlTesterTest { @Test public void createCrawlerRunCommand_splitApksProvided_useApkFileAndSplitApkFilesParams() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + Path apkRoot = mFileSystem.getPath("apk"); + Files.createDirectories(apkRoot); + Files.createFile(apkRoot.resolve("base.apk")); + Files.createFile(apkRoot.resolve("config1.apk")); + Files.createFile(apkRoot.resolve("config2.apk")); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(apkRoot); suj.start(); - List<Path> apks = - Arrays.asList(Path.of("base.apk"), Path.of("config1.apk"), Path.of("config2.apk")); - String[] result = suj.createCrawlerRunCommand(mTestInfo, apks); + String[] result = suj.createCrawlerRunCommand(mTestInfo); assertThat(Arrays.asList(result).stream().filter(s -> s.equals("--apk-file")).count()) .isEqualTo(1); @@ -350,15 +389,71 @@ public final class AppCrawlTesterTest { } @Test + public void createCrawlerRunCommand_uiAutomatorModeEnabled_doesNotContainApks() + throws Exception { + Path apkRoot = mFileSystem.getPath("apk"); + Files.createDirectories(apkRoot); + Files.createFile(apkRoot.resolve("base.apk")); + Files.createFile(apkRoot.resolve("config1.apk")); + Files.createFile(apkRoot.resolve("config2.apk")); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(apkRoot); + suj.setUiAutomatorMode(true); + suj.start(); + + String[] result = suj.createCrawlerRunCommand(mTestInfo); + + assertThat(Arrays.asList(result).stream().filter(s -> s.equals("--apk-file")).count()) + .isEqualTo(0); + assertThat( + Arrays.asList(result).stream() + .filter(s -> s.equals("--split-apk-files")) + .count()) + .isEqualTo(0); + } + + @Test + public void createCrawlerRunCommand_uiAutomatorModeEnabled_containsUiAutomatorParam() + throws Exception { + Path apkRoot = mFileSystem.getPath("apk"); + Files.createDirectories(apkRoot); + Files.createFile(apkRoot.resolve("base.apk")); + Files.createFile(apkRoot.resolve("config1.apk")); + Files.createFile(apkRoot.resolve("config2.apk")); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(apkRoot); + suj.setUiAutomatorMode(true); + suj.start(); + + String[] result = suj.createCrawlerRunCommand(mTestInfo); + + assertThat( + Arrays.asList(result).stream() + .filter(s -> s.equals("--ui-automator-mode")) + .count()) + .isEqualTo(1); + assertThat( + Arrays.asList(result).stream() + .filter(s -> s.equals("--app-package-name")) + .count()) + .isEqualTo(1); + } + + @Test public void createCrawlerRunCommand_doesNotContainNullOrEmptyStrings() throws Exception { - AppCrawlTester suj = createPreparedTestSubject(createApkPathWithSplitApks()); + Path apkRoot = mFileSystem.getPath("apk"); + Files.createDirectories(apkRoot); + Files.createFile(apkRoot.resolve("base.apk")); + Files.createFile(apkRoot.resolve("config1.apk")); + Files.createFile(apkRoot.resolve("config2.apk")); + AppCrawlTester suj = createPreparedTestSubject(); + suj.setApkPath(apkRoot); suj.start(); - List<Path> apks = - Arrays.asList(Path.of("base.apk"), Path.of("config1.apk"), Path.of("config2.apk")); - String[] result = suj.createCrawlerRunCommand(mTestInfo, apks); + String[] result = suj.createCrawlerRunCommand(mTestInfo); assertThat(Arrays.asList(result).stream().filter(s -> s == null).count()).isEqualTo(0); + assertThat(Arrays.asList(result).stream().map(String::trim).filter(String::isEmpty).count()) .isEqualTo(0); } @@ -382,19 +477,19 @@ public final class AppCrawlTesterTest { preparer.setUp(mTestInfo); } - private AppCrawlTester createNotPreparedTestSubject(Path apkPath) { + private AppCrawlTester createNotPreparedTestSubject() { Mockito.when(mRunUtil.runTimedCmd(Mockito.anyLong(), ArgumentMatchers.<String>any())) .thenReturn(createSuccessfulCommandResult()); Mockito.when(mDevice.getSerialNumber()).thenReturn("serial"); - return new AppCrawlTester(apkPath, "package.name", mTestUtils, () -> mRunUtil); + return new AppCrawlTester("package.name", mTestUtils, () -> mRunUtil); } - private AppCrawlTester createPreparedTestSubject(Path apkPath) + private AppCrawlTester createPreparedTestSubject() throws IOException, ConfigurationException, TargetSetupError { simulatePreparerWasExecutedSuccessfully(); Mockito.when(mRunUtil.runTimedCmd(Mockito.anyLong(), ArgumentMatchers.<String>any())) .thenReturn(createSuccessfulCommandResult()); - return new AppCrawlTester(apkPath, "package.name", mTestUtils, () -> mRunUtil); + return new AppCrawlTester("package.name", mTestUtils, () -> mRunUtil); } private TestUtils createTestUtils() throws DeviceNotAvailableException { diff --git a/test_scripts/Android.bp b/test_scripts/Android.bp index 5bf5f24..62bfcf0 100644 --- a/test_scripts/Android.bp +++ b/test_scripts/Android.bp @@ -21,6 +21,9 @@ java_library_host { srcs: [ "src/main/java/**/*.java", ], + exclude_srcs: [ + "src/main/java/com/android/pixel/**/*.java", + ], java_resource_dirs: [ "src/main/resources", ], @@ -28,4 +31,4 @@ java_library_host { "tradefed", "csuite-harness", ], -}
\ No newline at end of file +} diff --git a/test_scripts/src/main/java/com/android/csuite/tests/AppCrawlTest.java b/test_scripts/src/main/java/com/android/csuite/tests/AppCrawlTest.java index 3af69c1..ae7bb09 100644 --- a/test_scripts/src/main/java/com/android/csuite/tests/AppCrawlTest.java +++ b/test_scripts/src/main/java/com/android/csuite/tests/AppCrawlTest.java @@ -23,6 +23,8 @@ import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.google.common.base.Preconditions; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -59,7 +61,7 @@ public class AppCrawlTest extends BaseHostJUnit4Test { @Option( name = "apk", - mandatory = true, + mandatory = false, description = "Path to an apk file or a directory containing apk files of a single package.") private File mApk; @@ -67,14 +69,27 @@ public class AppCrawlTest extends BaseHostJUnit4Test { @Option(name = "package-name", mandatory = true, description = "Package name of testing app.") private String mPackageName; + @Option( + name = "ui-automator-mode", + mandatory = false, + description = + "Run the crawler with UIAutomator mode. Apk option is not required in this" + + " mode.") + private boolean mUiAutomatorMode = false; + @Before public void setUp() { - mCrawler = - AppCrawlTester.newInstance( - mApk.toPath(), mPackageName, getTestInformation(), mLogData); + if (!mUiAutomatorMode) { + Preconditions.checkNotNull( + mApk, "Apk file path is required when not running in UIAutomator mode"); + } + + mCrawler = AppCrawlTester.newInstance(mPackageName, getTestInformation(), mLogData); mCrawler.setRecordScreen(mRecordScreen); mCrawler.setCollectGmsVersion(mCollectGmsVersion); mCrawler.setCollectAppVersion(mCollectAppVersion); + mCrawler.setUiAutomatorMode(mUiAutomatorMode); + mCrawler.setApkPath(mApk.toPath()); } @Test diff --git a/test_scripts/src/main/java/com/android/csuite/tests/AppLaunchTest.java b/test_scripts/src/main/java/com/android/csuite/tests/AppLaunchTest.java index 507f5b1..49f27fa 100644 --- a/test_scripts/src/main/java/com/android/csuite/tests/AppLaunchTest.java +++ b/test_scripts/src/main/java/com/android/csuite/tests/AppLaunchTest.java @@ -16,6 +16,8 @@ package com.android.csuite.tests; +import com.android.csuite.core.ApkInstaller; +import com.android.csuite.core.ApkInstaller.ApkInstallerException; import com.android.csuite.core.DeviceUtils; import com.android.csuite.core.DeviceUtils.DeviceTimestamp; import com.android.csuite.core.DeviceUtils.DeviceUtilsException; @@ -37,7 +39,10 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; /** A test that verifies that a single app can be successfully launched. */ @RunWith(DeviceJUnit4ClassRunner.class) @@ -47,6 +52,7 @@ public class AppLaunchTest extends BaseHostJUnit4Test { @VisibleForTesting static final String COLLECT_GMS_VERSION = "collect-gms-version"; @VisibleForTesting static final String RECORD_SCREEN = "record-screen"; @Rule public TestLogData mLogData = new TestLogData(); + private ApkInstaller mApkInstaller; @Option(name = RECORD_SCREEN, description = "Whether to record screen during test.") private boolean mRecordScreen; @@ -70,6 +76,18 @@ public class AppLaunchTest extends BaseHostJUnit4Test { + " test log files.") private boolean mCollectGmsVersion; + @Option( + name = "install-apk", + description = + "The path to an apk file or a directory of apk files of a singe package to be" + + " installed on device. Can be repeated.") + private final List<File> mApkPaths = new ArrayList<>(); + + @Option( + name = "install-arg", + description = "Arguments for the 'adb install-multiple' package installation command.") + private final List<String> mInstallArgs = new ArrayList<>(); + @Option(name = "package-name", description = "Package name of testing app.") private String mPackageName; @@ -79,12 +97,19 @@ public class AppLaunchTest extends BaseHostJUnit4Test { private int mAppLaunchTimeoutMs = 15000; @Before - public void setUp() throws DeviceNotAvailableException { + public void setUp() throws DeviceNotAvailableException, ApkInstallerException, IOException { Assert.assertNotNull("Package name cannot be null", mPackageName); DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice()); TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData); + mApkInstaller = ApkInstaller.getInstance(getDevice()); + for (File apkPath : mApkPaths) { + CLog.d("Installing " + apkPath); + mApkInstaller.install( + apkPath.toPath(), mInstallArgs.toArray(new String[mInstallArgs.size()])); + } + if (mCollectGmsVersion) { testUtils.collectGmsVersion(mPackageName); } @@ -113,7 +138,7 @@ public class AppLaunchTest extends BaseHostJUnit4Test { } @After - public void tearDown() throws DeviceNotAvailableException { + public void tearDown() throws DeviceNotAvailableException, ApkInstallerException { DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice()); TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData); @@ -123,6 +148,8 @@ public class AppLaunchTest extends BaseHostJUnit4Test { deviceUtils.stopPackage(mPackageName); deviceUtils.unfreezeRotation(); + + mApkInstaller.uninstallAllInstalledPackages(); } private void launchPackageAndCheckForCrash() throws DeviceNotAvailableException { @@ -145,9 +172,7 @@ public class AppLaunchTest extends BaseHostJUnit4Test { try { String crashLog = testUtils.getDropboxPackageCrashLog(mPackageName, startTime, true); - if (crashLog != null) { - Assert.fail(crashLog); - } + Assert.assertNull(crashLog, crashLog); } catch (IOException e) { Assert.fail("Error while getting dropbox crash log: " + e); } diff --git a/test_scripts/src/main/java/com/android/pixel/Android.bp b/test_scripts/src/main/java/com/android/pixel/Android.bp new file mode 100644 index 0000000..2f3e2e8 --- /dev/null +++ b/test_scripts/src/main/java/com/android/pixel/Android.bp @@ -0,0 +1,47 @@ +// Copyright (C) 2022 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: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "PixelAppCompTests", + compile_multilib: "both", + libs: [ + "android.test.base", + "android.test.runner", + ], + static_libs: [ + "androidx.test.rules", + "androidx.test.ext.junit", + "compatibility-device-util-axt", + "collector-device-lib", + "ub-uiautomator", + ], + // Use multi-dex as the compatibility-common-util-devicesidelib dependency + // on compatibility-device-util-axt pushes us beyond 64k methods. + dxflags: ["--multi-dex"], + srcs: [ + "tests/*.java", + "utils/*.java", + ], + platform_apis: true, + // Tag this module as a cts test artifact + test_suites: [ + "device-tests", + "csuite", + ], + min_sdk_version: "29", +} diff --git a/test_scripts/src/main/java/com/android/pixel/AndroidManifest.xml b/test_scripts/src/main/java/com/android/pixel/AndroidManifest.xml new file mode 100644 index 0000000..2b88677 --- /dev/null +++ b/test_scripts/src/main/java/com/android/pixel/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.pixel.tests"> + <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.pixel.tests" /> + +</manifest> diff --git a/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchLockTest.java b/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchLockTest.java new file mode 100644 index 0000000..92c2f59 --- /dev/null +++ b/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchLockTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 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.pixel.tests; + +import android.os.SystemClock; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.Until; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class AppLaunchLockTest extends PixelAppCompatTestBase { + private static final int LAUNCH_TIME_MS = 30000; // 30 seconds + private static final long WAIT_ONE_SECOND_IN_MS = 1000; + private static final String DISMISS_KEYGUARD = "wm dismiss-keyguard"; + + @Test + public void testLockDevice() throws Exception { + // Launch the 3P app + getDeviceUtils().launchApp(getPackage()); + + // Wait for the 3P app to appear + getUiDevice().wait(Until.hasObject(By.pkg(getPackage()).depth(0)), LAUNCH_TIME_MS); + getUiDevice().waitForIdle(); + Assert.assertTrue( + "3P app main page should show up", + getUiDevice().hasObject(By.pkg(getPackage()).depth(0))); + + if (getUiDevice().isScreenOn()) { + getUiDevice().sleep(); + SystemClock.sleep(WAIT_ONE_SECOND_IN_MS); + } + getDeviceUtils().takeScreenshot(getPackage(), "sleep_device"); + Assert.assertFalse("The screen should be off", getUiDevice().isScreenOn()); + + getUiDevice().wakeUp(); + SystemClock.sleep(WAIT_ONE_SECOND_IN_MS); + getDeviceUtils().takeScreenshot(getPackage(), "wake_up_device"); + Assert.assertTrue("The screen should be off", getUiDevice().isScreenOn()); + Assert.assertTrue("The keyguard should show up", getKeyguardManager().isKeyguardLocked()); + + getUiDevice().executeShellCommand(DISMISS_KEYGUARD); + SystemClock.sleep(WAIT_ONE_SECOND_IN_MS); + getDeviceUtils().takeScreenshot(getPackage(), "dismiss_keyguard"); + getUiDevice().wait(Until.hasObject(By.pkg(getPackage()).depth(0)), LAUNCH_TIME_MS); + Assert.assertFalse( + "The keyguard should be dismissed", getKeyguardManager().isKeyguardLocked()); + Assert.assertTrue( + "3P app main page should show up after unlocking the screen", + getUiDevice().hasObject(By.pkg(getPackage()).depth(0))); + } +} diff --git a/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRecentAppTest.java b/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRecentAppTest.java new file mode 100644 index 0000000..6c694c8 --- /dev/null +++ b/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRecentAppTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022 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.pixel.tests; + +import android.os.SystemClock; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.Until; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class AppLaunchRecentAppTest extends PixelAppCompatTestBase { + private static final int WAIT_FIFTEEN_SECONDS_IN_MS = 15000; + private static final long WAIT_ONE_SECOND_IN_MS = 1000; + private static final String CLEAR_ALL = "Clear all"; + private static final String NO_RECENT_ITEMS = "No recent items"; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + getUiDevice().pressRecentApps(); + getUiDevice() + .waitForWindowUpdate( + getUiDevice().getLauncherPackageName(), WAIT_FIFTEEN_SECONDS_IN_MS); + getUiDevice().wait(Until.findObject(By.text(NO_RECENT_ITEMS)), WAIT_FIFTEEN_SECONDS_IN_MS); + if (!getUiDevice().hasObject(By.text(NO_RECENT_ITEMS))) { + int midY = getUiDevice().getDisplayHeight() / 2; + int startX = getUiDevice().getDisplayWidth() * 1 / 10; + int endX = getUiDevice().getDisplayWidth() * 9 / 10; + for (int i = 0; i < 20; i++) { + getUiDevice().swipe(startX, midY, endX, midY, (endX - startX) / 100); + getUiDevice().waitForIdle(); + if (getUiDevice().hasObject(By.text(CLEAR_ALL))) { + break; + } + } + if (getUiDevice().hasObject(By.text(CLEAR_ALL))) { + getUiDevice().findObject(By.text(CLEAR_ALL)).click(); + } + SystemClock.sleep(WAIT_ONE_SECOND_IN_MS); + } + getDeviceUtils().backToHome(getUiDevice().getLauncherPackageName()); + } + + @Test + public void testLaunchFromRecentApps() throws Exception { + // Launch the 3P app + getDeviceUtils().launchApp(getPackage()); + + // Wait for the 3P app to appear + getUiDevice() + .wait(Until.hasObject(By.pkg(getPackage()).depth(0)), WAIT_FIFTEEN_SECONDS_IN_MS); + getUiDevice().waitForIdle(); + Assert.assertTrue( + "3P app main page should show up", + getUiDevice().hasObject(By.pkg(getPackage()).depth(0))); + + getUiDevice().pressRecentApps(); + getUiDevice().wait(Until.hasObject(By.text("Screenshot")), WAIT_FIFTEEN_SECONDS_IN_MS); + getDeviceUtils().takeScreenshot(getPackage(), "press_recent_apps_1"); + Assert.assertTrue( + "3P app should be in background", getUiDevice().hasObject(By.text("Screenshot"))); + + getUiDevice().pressRecentApps(); + getUiDevice() + .wait(Until.hasObject(By.pkg(getPackage()).depth(0)), WAIT_FIFTEEN_SECONDS_IN_MS); + getDeviceUtils().takeScreenshot(getPackage(), "press_recent_apps_2"); + Assert.assertTrue( + "3P app main page should be re-launched", + getUiDevice().hasObject(By.pkg(getPackage()).depth(0))); + } +} diff --git a/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRotateTest.java b/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRotateTest.java new file mode 100644 index 0000000..62f6e1a --- /dev/null +++ b/test_scripts/src/main/java/com/android/pixel/tests/AppLaunchRotateTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 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.pixel.tests; + +import android.os.SystemClock; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.Until; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class AppLaunchRotateTest extends PixelAppCompatTestBase { + private static final String ROTATE_LANDSCAPE = + "content insert --uri content://settings/system" + + " --bind name:s:user_rotation --bind value:i:1"; + private static final String ROTATE_PORTRAIT = + "content insert --uri content://settings/system" + + " --bind name:s:user_rotation --bind value:i:0"; + private static final int LAUNCH_TIME_MS = 30000; // 30 seconds + private static final long WAIT_ONE_SECOND_IN_MS = 1000; + + @Override + @After + public void tearDown() throws Exception { + super.tearDown(); + getUiDevice().unfreezeRotation(); + } + + @Test + public void testRotateDevice() throws Exception { + // Launch the 3P app + getDeviceUtils().launchApp(getPackage()); + + // Wait for the 3P app to appear + getUiDevice().wait(Until.hasObject(By.pkg(getPackage()).depth(0)), LAUNCH_TIME_MS); + getUiDevice().waitForIdle(); + Assert.assertTrue( + "3P app main page should show up", + getUiDevice().hasObject(By.pkg(getPackage()).depth(0))); + + // Turn off the automatic rotation + getUiDevice().freezeRotation(); + getUiDevice().executeShellCommand(ROTATE_PORTRAIT); + SystemClock.sleep(WAIT_ONE_SECOND_IN_MS); + getDeviceUtils().takeScreenshot(getPackage(), "set_portrait_mode"); + Assert.assertTrue( + "Screen should be in portrait mode", getUiDevice().isNaturalOrientation()); + + getUiDevice().executeShellCommand(ROTATE_LANDSCAPE); + SystemClock.sleep(WAIT_ONE_SECOND_IN_MS); + getDeviceUtils().takeScreenshot(getPackage(), "rotate_landscape"); + Assert.assertFalse( + "Screen should be in landscape mode", getUiDevice().isNaturalOrientation()); + + getUiDevice().executeShellCommand(ROTATE_PORTRAIT); + SystemClock.sleep(WAIT_ONE_SECOND_IN_MS); + getDeviceUtils().takeScreenshot(getPackage(), "rotate_portrait"); + Assert.assertTrue( + "Screen should be in portrait mode", getUiDevice().isNaturalOrientation()); + } +} diff --git a/test_scripts/src/main/java/com/android/pixel/tests/PixelAppCompatTestBase.java b/test_scripts/src/main/java/com/android/pixel/tests/PixelAppCompatTestBase.java new file mode 100644 index 0000000..d5fb892 --- /dev/null +++ b/test_scripts/src/main/java/com/android/pixel/tests/PixelAppCompatTestBase.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 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.pixel.tests; + +import static androidx.test.platform.app.InstrumentationRegistry.getArguments; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import android.app.KeyguardManager; +import android.support.test.uiautomator.UiDevice; + +import com.android.pixel.utils.DeviceUtils; + +import org.junit.After; +import org.junit.Before; + +/** Base class for Pixel app compatibility tests. */ +public abstract class PixelAppCompatTestBase { + private static final String KEY_PACKAGE_NAME = "package"; + + private DeviceUtils mDeviceUtils; + private UiDevice mDevice; + private KeyguardManager mKeyguardManager; + private String mPackage; + + @Before + public void setUp() throws Exception { + getDeviceUtils().createLogDataDir(); + getDeviceUtils().wakeAndUnlockScreen(); + // Start from the home screen + getDeviceUtils().backToHome(getUiDevice().getLauncherPackageName()); + getDeviceUtils().startRecording(getPackage()); + } + + @After + public void tearDown() throws Exception { + getDeviceUtils().stopRecording(); + } + + protected UiDevice getUiDevice() { + if (mDevice == null) { + mDevice = UiDevice.getInstance(getInstrumentation()); + } + return mDevice; + } + + protected DeviceUtils getDeviceUtils() { + if (mDeviceUtils == null) { + mDeviceUtils = new DeviceUtils(getUiDevice()); + } + return mDeviceUtils; + } + + protected KeyguardManager getKeyguardManager() { + if (mKeyguardManager == null) { + mKeyguardManager = + getInstrumentation().getContext().getSystemService(KeyguardManager.class); + } + return mKeyguardManager; + } + + protected String getPackage() { + if (mPackage == null) { + mPackage = getArguments().getString(KEY_PACKAGE_NAME); + } + return mPackage; + } +} diff --git a/test_scripts/src/main/java/com/android/pixel/utils/DeviceUtils.java b/test_scripts/src/main/java/com/android/pixel/utils/DeviceUtils.java new file mode 100644 index 0000000..9d29dbc --- /dev/null +++ b/test_scripts/src/main/java/com/android/pixel/utils/DeviceUtils.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2022 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.pixel.utils; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import android.content.Context; +import android.content.Intent; +import android.os.SystemClock; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.UiDevice; +import android.util.Log; + +import org.junit.Assert; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; + +public class DeviceUtils { + private static final String TAG = DeviceUtils.class.getSimpleName(); + private static final String LOG_DATA_DIR = "/sdcard/logData"; + private static final int MAX_RECORDING_PARTS = 5; + private static final long WAIT_ONE_SECOND_IN_MS = 1000; + private static final long VIDEO_TAIL_BUFFER = 500; + private static final String DISMISS_KEYGUARD = "wm dismiss-keyguard"; + + private RecordingThread mCurrentThread; + private File mLogDataDir; + private UiDevice mDevice; + + public DeviceUtils(UiDevice device) { + mDevice = device; + } + + /** Create a directory to save test screenshots, screenrecord and text files. */ + public void createLogDataDir() { + mLogDataDir = new File(LOG_DATA_DIR); + if (mLogDataDir.exists()) { + String[] children = mLogDataDir.list(); + for (String file : children) { + new File(mLogDataDir, file).delete(); + } + } else { + mLogDataDir.mkdirs(); + } + } + + /** Wake up the device and dismiss the keyguard. */ + public void wakeAndUnlockScreen() throws Exception { + mDevice.wakeUp(); + SystemClock.sleep(WAIT_ONE_SECOND_IN_MS); + mDevice.executeShellCommand(DISMISS_KEYGUARD); + SystemClock.sleep(WAIT_ONE_SECOND_IN_MS); + } + + /** + * Go back to home screen by pressing back key five times and home key to avoid the infinite + * loop since some apps' activities cannot be exited to home screen by back key event. + */ + public void backToHome(String launcherPkg) { + for (int i = 0; i < 5; i++) { + mDevice.pressBack(); + mDevice.waitForIdle(); + if (mDevice.hasObject(By.pkg(launcherPkg))) { + break; + } + } + mDevice.pressHome(); + } + + /** + * Launch an app with the given package name + * + * @param packageName Name of package to be launched + */ + public void launchApp(String packageName) { + Context context = getInstrumentation().getContext(); + Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + + /** + * Take a screenshot on the device and save it in {@code logDataDir}. + * + * @param packageName The package name of 3P apps screenshotted. + * @param description The description of actions or operations on the device. + */ + public void takeScreenshot(String packageName, String description) { + File screenshot = + new File( + LOG_DATA_DIR, + String.format("%s_screenshot_%s.png", packageName, description)); + mDevice.takeScreenshot(screenshot); + } + + /** + * Start the screen recording. + * + * @param packageName The package name of 3P apps screenrecorded. + */ + public void startRecording(String packageName) { + Log.v(TAG, "Started Recording"); + mCurrentThread = + new RecordingThread( + "test-screen-record", String.format("%s_screenrecord", packageName)); + mCurrentThread.start(); + } + + /** Stop already started screen recording. */ + public void stopRecording() { + // Skip if not directory. + if (mLogDataDir == null) { + return; + } + // Add some extra time to the video end. + SystemClock.sleep(VIDEO_TAIL_BUFFER); + // Ctrl + C all screen record processes. + mCurrentThread.cancel(); + // Wait for the thread to completely die. + try { + mCurrentThread.join(); + } catch (InterruptedException ex) { + Log.e(TAG, "Interrupted when joining the recording thread.", ex); + } + Log.v(TAG, "Stopped Recording"); + } + + /** Returns the recording's name for {@code part} of launch description. */ + public File getOutputFile(String description, int part) { + // Omit the iteration number for the first iteration. + final String fileName = String.format("%s-video%s.mp4", description, part == 1 ? "" : part); + return Paths.get(mLogDataDir.getAbsolutePath(), fileName).toFile(); + } + + /** + * Encapsulates the start and stop screen recording logic. Copied from ScreenRecordCollector. + */ + private class RecordingThread extends Thread { + private final String mDescription; + + private boolean mContinue; + + RecordingThread(String name, String description) { + super(name); + + mContinue = true; + + Assert.assertNotNull("No test description provided for recording.", description); + mDescription = description; + } + + @Override + public void run() { + try { + // Start at i = 1 to encode parts as X.mp4, X2.mp4, X3.mp4, etc. + for (int i = 1; i <= MAX_RECORDING_PARTS && mContinue; i++) { + File output = getOutputFile(mDescription, i); + Log.d(TAG, String.format("Recording screen to %s", output.getAbsolutePath())); + // Make sure not to block on this background command in the main thread so + // that the test continues to run, but block in this thread so it does not + // trigger a new screen recording session before the prior one completes. + mDevice.executeShellCommand( + String.format("screenrecord %s", output.getAbsolutePath())); + } + } catch (IOException e) { + throw new RuntimeException("Caught exception while screen recording."); + } + } + + public void cancel() { + mContinue = false; + // Identify the screenrecord PIDs and send SIGINT 2 (Ctrl + C) to each. + try { + String[] pids = mDevice.executeShellCommand("pidof screenrecord").split(" "); + for (String pid : pids) { + // Avoid empty process ids, because of weird splitting behavior. + if (pid.isEmpty()) { + continue; + } + mDevice.executeShellCommand(String.format("kill -2 %s", pid)); + Log.d(TAG, String.format("Sent SIGINT 2 to screenrecord process (%s)", pid)); + } + } catch (IOException e) { + throw new RuntimeException("Failed to kill screen recording process."); + } + } + } +} diff --git a/test_scripts/src/main/java/com/android/webview/OWNERS b/test_scripts/src/main/java/com/android/webview/OWNERS new file mode 100644 index 0000000..af3a7c8 --- /dev/null +++ b/test_scripts/src/main/java/com/android/webview/OWNERS @@ -0,0 +1,2 @@ +amitku@google.com +rmhasan@google.com
\ No newline at end of file diff --git a/test_scripts/src/main/java/com/android/webview/tests/WebviewAppLaunchTest.java b/test_scripts/src/main/java/com/android/webview/tests/WebviewAppLaunchTest.java new file mode 100644 index 0000000..d08d2e3 --- /dev/null +++ b/test_scripts/src/main/java/com/android/webview/tests/WebviewAppLaunchTest.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2022 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.webview.tests; + +import com.android.csuite.core.ApkInstaller; +import com.android.csuite.core.ApkInstaller.ApkInstallerException; +import com.android.csuite.core.DeviceUtils; +import com.android.csuite.core.DeviceUtils.DeviceTimestamp; +import com.android.csuite.core.DeviceUtils.DeviceUtilsException; +import com.android.csuite.core.TestUtils; +import com.android.tradefed.config.Option; +import com.android.tradefed.config.Option.Importance; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.android.tradefed.util.AaptParser; +import com.android.tradefed.util.CommandResult; +import com.android.tradefed.util.CommandStatus; +import com.android.tradefed.util.RunUtil; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** A test that verifies that a single app can be successfully launched. */ +@RunWith(DeviceJUnit4ClassRunner.class) +public class WebviewAppLaunchTest extends BaseHostJUnit4Test { + @Rule public TestLogData mLogData = new TestLogData(); + private ApkInstaller mApkInstaller; + private List<File> mOrderedWebviewApks = new ArrayList<>(); + + @Option(name = "record-screen", description = "Whether to record screen during test.") + private boolean mRecordScreen; + + @Option(name = "package-name", description = "Package name of testing app.") + private String mPackageName; + + @Option( + name = "install-apk", + description = + "The path to an apk file or a directory of apk files of a singe package to be" + + " installed on device. Can be repeated.") + private List<File> mApkPaths = new ArrayList<>(); + + @Option( + name = "install-arg", + description = "Arguments for the 'adb install-multiple' package installation command.") + private final List<String> mInstallArgs = new ArrayList<>(); + + @Option( + name = "app-launch-timeout-ms", + description = "Time to wait for an app to launch in msecs.") + private int mAppLaunchTimeoutMs = 20000; + + @Option( + name = "webview-apk-dir", + description = "The path to the webview apk.", + importance = Importance.ALWAYS) + private File mWebviewApkDir; + + @Before + public void setUp() throws DeviceNotAvailableException, ApkInstallerException, IOException { + Assert.assertNotNull("Package name cannot be null", mPackageName); + + readWebviewApkDirectory(); + + mApkInstaller = ApkInstaller.getInstance(getDevice()); + for (File apkPath : mApkPaths) { + CLog.d("Installing " + apkPath); + mApkInstaller.install( + apkPath.toPath(), mInstallArgs.toArray(new String[mInstallArgs.size()])); + } + + DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice()); + deviceUtils.freezeRotation(); + + printWebviewVersion(); + } + + @Test + public void testAppLaunch() + throws DeviceNotAvailableException, ApkInstallerException, IOException { + AssertionError lastError = null; + // Try the latest webview version + WebviewPackage lastWebviewInstalled = installWebview(mOrderedWebviewApks.get(0)); + try { + assertAppLaunchNoCrash(); + } catch (AssertionError e) { + lastError = e; + } finally { + uninstallWebview(); + } + + // If the app doesn't crash, complete the test. + if (lastError == null) { + return; + } + + // If the app crashes, try the app with the original webview version that comes with the + // device. + try { + assertAppLaunchNoCrash(); + } catch (AssertionError newError) { + CLog.w( + "The app %s crashed both with and without the webview installation," + + " ignoring the failure...", + mPackageName); + return; + } + + for (int idx = 1; idx < mOrderedWebviewApks.size(); idx++) { + lastWebviewInstalled = installWebview(mOrderedWebviewApks.get(idx)); + try { + assertAppLaunchNoCrash(); + } catch (AssertionError e) { + lastError = e; + continue; + } finally { + uninstallWebview(); + } + break; + } + + throw new AssertionError( + String.format( + "Package %s crashed since webview version %s", + mPackageName, lastWebviewInstalled.getVersion()), + lastError); + } + + @After + public void tearDown() throws DeviceNotAvailableException, ApkInstallerException { + TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData); + testUtils.collectScreenshot(mPackageName); + + DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice()); + deviceUtils.stopPackage(mPackageName); + deviceUtils.unfreezeRotation(); + + mApkInstaller.uninstallAllInstalledPackages(); + printWebviewVersion(); + } + + private void readWebviewApkDirectory() { + mOrderedWebviewApks = Arrays.asList(mWebviewApkDir.listFiles()); + Collections.sort( + mOrderedWebviewApks, + new Comparator<File>() { + @Override + public int compare(File apk1, File apk2) { + return getVersionCode(apk2).compareTo(getVersionCode(apk1)); + } + + private Long getVersionCode(File apk) { + return Long.parseLong(AaptParser.parse(apk).getVersionCode()); + } + }); + } + + private void printWebviewVersion(WebviewPackage currentWebview) + throws DeviceNotAvailableException { + CLog.i("Current webview implementation: %s", currentWebview.getPackageName()); + CLog.i("Current webview version: %s", currentWebview.getVersion()); + } + + private void printWebviewVersion() throws DeviceNotAvailableException { + WebviewPackage currentWebview = getCurrentWebviewPackage(); + printWebviewVersion(currentWebview); + } + + private WebviewPackage installWebview(File apk) + throws ApkInstallerException, IOException, DeviceNotAvailableException { + ApkInstaller.getInstance(getDevice()).install(apk.toPath()); + CommandResult res = + getDevice() + .executeShellV2Command( + "cmd webviewupdate set-webview-implementation com.android.webview"); + Assert.assertEquals( + "Failed to set webview update: " + res, res.getStatus(), CommandStatus.SUCCESS); + WebviewPackage currentWebview = getCurrentWebviewPackage(); + printWebviewVersion(currentWebview); + return currentWebview; + } + + private void uninstallWebview() throws DeviceNotAvailableException { + getDevice() + .executeShellCommand( + "cmd webviewupdate set-webview-implementation com.google.android.webview"); + getDevice().executeAdbCommand("uninstall", "com.android.webview"); + } + + private WebviewPackage getCurrentWebviewPackage() throws DeviceNotAvailableException { + String dumpsys = getDevice().executeShellCommand("dumpsys webviewupdate"); + return WebviewPackage.parseFrom(dumpsys); + } + + private static class WebviewPackage { + private final String mPackageName; + private final String mVersion; + + private WebviewPackage(String packageName, String version) { + mPackageName = packageName; + mVersion = version; + } + + static WebviewPackage parseFrom(String dumpsys) { + Pattern pattern = + Pattern.compile("Current WebView package \\(name, version\\): \\((.*?)\\)"); + Matcher matcher = pattern.matcher(dumpsys); + Assert.assertTrue("Cannot parse webview package info from: " + dumpsys, matcher.find()); + String[] packageInfo = matcher.group(1).split(","); + return new WebviewPackage(packageInfo[0].strip(), packageInfo[1].strip()); + } + + String getPackageName() { + return mPackageName; + } + + String getVersion() { + return mVersion; + } + } + + private void assertAppLaunchNoCrash() throws DeviceNotAvailableException { + DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice()); + deviceUtils.resetPackage(mPackageName); + TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData); + + if (mRecordScreen) { + testUtils.collectScreenRecord( + () -> { + launchPackageAndCheckForCrash(); + }, + mPackageName); + } else { + launchPackageAndCheckForCrash(); + } + } + + private void launchPackageAndCheckForCrash() throws DeviceNotAvailableException { + CLog.d("Launching package: %s.", mPackageName); + + DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice()); + TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData); + + DeviceTimestamp startTime = deviceUtils.currentTimeMillis(); + try { + deviceUtils.launchPackage(mPackageName); + } catch (DeviceUtilsException e) { + Assert.fail(e.getMessage()); + } + + CLog.d("Waiting %s milliseconds for the app to launch fully.", mAppLaunchTimeoutMs); + RunUtil.getDefault().sleep(mAppLaunchTimeoutMs); + + CLog.d("Completed launching package: %s", mPackageName); + + try { + String crashLog = testUtils.getDropboxPackageCrashLog(mPackageName, startTime, true); + if (crashLog != null) { + Assert.fail(crashLog); + } + } catch (IOException e) { + Assert.fail("Error while getting dropbox crash log: " + e); + } + } +} diff --git a/test_targets/csuite-app-crawl/template.xml b/test_targets/csuite-app-crawl/template.xml index 42c91a4..406cf68 100644 --- a/test_targets/csuite-app-crawl/template.xml +++ b/test_targets/csuite-app-crawl/template.xml @@ -14,14 +14,14 @@ limitations under the License. --> <configuration description="C-Suite Crawler test configuration"> - <option name="package-name" value="{package}"/> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/> <option name="run-command" value="input keyevent KEYCODE_MENU"/> <option name="run-command" value="input keyevent KEYCODE_HOME"/> </target_preparer> <test class="com.android.tradefed.testtype.HostTest" > - <option name="set-option" value="apk:app://{package}"/> + <option name="set-option" value="package-name:{package}"/> + <option name="set-option" value="apk:app\://{package}"/> <option name="class" value="com.android.csuite.tests.AppCrawlTest" /> </test> </configuration>
\ No newline at end of file diff --git a/test_targets/csuite-app-launch/default.xml b/test_targets/csuite-app-launch/default.xml index d012df9..e583002 100644 --- a/test_targets/csuite-app-launch/default.xml +++ b/test_targets/csuite-app-launch/default.xml @@ -14,10 +14,6 @@ limitations under the License. --> <configuration description="Launches an app and check for crashes"> - <option name="package-name" value="{package}"/> - <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer"> - <option name="test-file-name" value="app://{package}"/> - </target_preparer> <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/> @@ -26,6 +22,7 @@ </target_preparer> <test class="com.android.tradefed.testtype.HostTest" > <option name="set-option" value="package-name:{package}"/> + <option name="set-option" value="install-apk:app\://{package}"/> <option name="class" value="com.android.csuite.tests.AppLaunchTest" /> </test> </configuration>
\ No newline at end of file diff --git a/test_targets/csuite-test-package-launch/template.xml b/test_targets/csuite-test-package-launch/template.xml index f77ddc3..34ba321 100644 --- a/test_targets/csuite-test-package-launch/template.xml +++ b/test_targets/csuite-test-package-launch/template.xml @@ -14,11 +14,6 @@ limitations under the License. --> <configuration description="Installs a test package with -t arg and check for launch crashes"> - <option name="package-name" value="{package}"/> - <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer"> - <option name="test-file-name" value="app://{package}"/> - <option name="install-arg" value="-t"/> - </target_preparer> <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/> @@ -27,6 +22,8 @@ </target_preparer> <test class="com.android.tradefed.testtype.HostTest" > <option name="set-option" value="package-name:{package}"/> + <option name="set-option" value="install-apk:app\://{package}"/> + <option name="set-option" value="install-arg:-t"/> <option name="class" value="com.android.csuite.tests.AppLaunchTest" /> </test> </configuration>
\ No newline at end of file diff --git a/test_targets/drm-app-launch/template.xml b/test_targets/drm-app-launch/template.xml index e723cac..f7abcdd 100644 --- a/test_targets/drm-app-launch/template.xml +++ b/test_targets/drm-app-launch/template.xml @@ -14,11 +14,6 @@ limitations under the License. --> <configuration description="DRM launch test configuration"> - <option name="package-name" value="{package}"/> - <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer"> - <option name="test-file-name" value="{package_install_file}"/> - <option name="install-arg" value="-t"/> - </target_preparer> <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/> @@ -27,6 +22,8 @@ </target_preparer> <test class="com.android.tradefed.testtype.HostTest" > <option name="set-option" value="package-name:{package}"/> + <option name="set-option" value="install-apk:{package_install_file}"/> + <option name="set-option" value="install-arg:-t"/> <option name="class" value="com.android.csuite.tests.AppLaunchTest" /> </test> </configuration>
\ No newline at end of file diff --git a/test_targets/pixel-app-launch-lock/Android.bp b/test_targets/pixel-app-launch-lock/Android.bp new file mode 100644 index 0000000..8261309 --- /dev/null +++ b/test_targets/pixel-app-launch-lock/Android.bp @@ -0,0 +1,22 @@ +// Copyright (C) 2022 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: ["Android-Apache-2.0"], +} + +csuite_test { + name: "pixel-app-launch-lock", + test_config_template: "template.xml", +} diff --git a/test_targets/pixel-app-launch-lock/OWNERS b/test_targets/pixel-app-launch-lock/OWNERS new file mode 100644 index 0000000..05ffe9a --- /dev/null +++ b/test_targets/pixel-app-launch-lock/OWNERS @@ -0,0 +1,2 @@ +murphykuo@google.com +huilingchi@google.com diff --git a/test_targets/pixel-app-launch-lock/template.xml b/test_targets/pixel-app-launch-lock/template.xml new file mode 100644 index 0000000..7751426 --- /dev/null +++ b/test_targets/pixel-app-launch-lock/template.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 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 + + + 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="Launches an app and lock/unlock the device"> + <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="app://{package}"/> + <option name="test-file-name" value="PixelAppCompTests.apk" /> + </target_preparer> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <!-- repeatable: The key of the DIRECTORY to pull --> + <option name = "directory-keys" value = "/sdcard/logData" /> + <option name="collect-on-run-ended-only" value="true" /> + </metrics_collector> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="package" value="com.android.pixel.tests" /> + <option name="include-filter" value="com.android.pixel.tests.AppLaunchLockTest" /> + <option name="instrumentation-arg" key="package" value="{package}" /> + <option name="isolated-storage" value="false" /> + </test> +</configuration> diff --git a/test_targets/pixel-app-launch-recentapp/Android.bp b/test_targets/pixel-app-launch-recentapp/Android.bp new file mode 100644 index 0000000..4458cf3 --- /dev/null +++ b/test_targets/pixel-app-launch-recentapp/Android.bp @@ -0,0 +1,22 @@ +// Copyright (C) 2022 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: ["Android-Apache-2.0"], +} + +csuite_test { + name: "pixel-app-launch-recentapp", + test_config_template: "template.xml", +} diff --git a/test_targets/pixel-app-launch-recentapp/OWNERS b/test_targets/pixel-app-launch-recentapp/OWNERS new file mode 100644 index 0000000..05ffe9a --- /dev/null +++ b/test_targets/pixel-app-launch-recentapp/OWNERS @@ -0,0 +1,2 @@ +murphykuo@google.com +huilingchi@google.com diff --git a/test_targets/pixel-app-launch-recentapp/template.xml b/test_targets/pixel-app-launch-recentapp/template.xml new file mode 100644 index 0000000..86742b7 --- /dev/null +++ b/test_targets/pixel-app-launch-recentapp/template.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 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 + + + 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="Launches an app by tapping recent app key"> + <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="app://{package}"/> + <option name="test-file-name" value="PixelAppCompTests.apk" /> + </target_preparer> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <!-- repeatable: The key of the DIRECTORY to pull --> + <option name = "directory-keys" value = "/sdcard/logData" /> + <option name="collect-on-run-ended-only" value="true" /> + </metrics_collector> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="package" value="com.android.pixel.tests" /> + <option name="include-filter" value="com.android.pixel.tests.AppLaunchRecentAppTest" /> + <option name="instrumentation-arg" key="package" value="{package}" /> + <option name="isolated-storage" value="false" /> + </test> +</configuration> diff --git a/test_targets/pixel-app-launch-rotate/Android.bp b/test_targets/pixel-app-launch-rotate/Android.bp new file mode 100644 index 0000000..89419ae --- /dev/null +++ b/test_targets/pixel-app-launch-rotate/Android.bp @@ -0,0 +1,22 @@ +// Copyright (C) 2022 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: ["Android-Apache-2.0"], +} + +csuite_test { + name: "pixel-app-launch-rotate", + test_config_template: "template.xml", +} diff --git a/test_targets/pixel-app-launch-rotate/OWNERS b/test_targets/pixel-app-launch-rotate/OWNERS new file mode 100644 index 0000000..05ffe9a --- /dev/null +++ b/test_targets/pixel-app-launch-rotate/OWNERS @@ -0,0 +1,2 @@ +murphykuo@google.com +huilingchi@google.com diff --git a/test_targets/pixel-app-launch-rotate/template.xml b/test_targets/pixel-app-launch-rotate/template.xml new file mode 100644 index 0000000..ff7871a --- /dev/null +++ b/test_targets/pixel-app-launch-rotate/template.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 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 + + + 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="Launches an app, rotate screen, and check for crashes"> + <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="app://{package}"/> + <option name="test-file-name" value="PixelAppCompTests.apk" /> + </target_preparer> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <!-- repeatable: The key of the DIRECTORY to pull --> + <option name = "directory-keys" value = "/sdcard/logData" /> + <option name="collect-on-run-ended-only" value="true" /> + </metrics_collector> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="package" value="com.android.pixel.tests" /> + <option name="include-filter" value="com.android.pixel.tests.AppLaunchRotateTest" /> + <option name="instrumentation-arg" key="package" value="{package}" /> + <option name="isolated-storage" value="false" /> + </test> +</configuration> diff --git a/test_targets/webview-app-launch/Android.bp b/test_targets/webview-app-launch/Android.bp new file mode 100644 index 0000000..3658018 --- /dev/null +++ b/test_targets/webview-app-launch/Android.bp @@ -0,0 +1,22 @@ +// Copyright (C) 2022 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: ["Android-Apache-2.0"], +} + +csuite_test { + name: "webview-app-launch", + test_config_template: "default.xml", +} diff --git a/test_targets/webview-app-launch/OWNERS b/test_targets/webview-app-launch/OWNERS new file mode 100644 index 0000000..af3a7c8 --- /dev/null +++ b/test_targets/webview-app-launch/OWNERS @@ -0,0 +1,2 @@ +amitku@google.com +rmhasan@google.com
\ No newline at end of file diff --git a/test_targets/webview-app-launch/default.xml b/test_targets/webview-app-launch/default.xml new file mode 100644 index 0000000..0b5470b --- /dev/null +++ b/test_targets/webview-app-launch/default.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 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="Launches an app and check for crashes"> + <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/> + <option name="run-command" value="input keyevent KEYCODE_MENU"/> + <option name="run-command" value="input keyevent KEYCODE_HOME"/> + </target_preparer> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="set-option" value="package-name:{package}"/> + <option name="set-option" value="install-apk:app\://{package}"/> + <option name="class" value="com.android.webview.tests.WebviewAppLaunchTest" /> + </test> +</configuration>
\ No newline at end of file |