1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.csuite.tests; 18 19 import com.android.csuite.core.ApkInstaller; 20 import com.android.csuite.core.ApkInstaller.ApkInstallerException; 21 import com.android.csuite.core.BlankScreenDetectorWithSameColorRectangle; 22 import com.android.csuite.core.BlankScreenDetectorWithSameColorRectangle.BlankScreen; 23 import com.android.csuite.core.DeviceUtils; 24 import com.android.csuite.core.DeviceUtils.DeviceTimestamp; 25 import com.android.csuite.core.DeviceUtils.DeviceUtilsException; 26 import com.android.csuite.core.DeviceUtils.DropboxEntry; 27 import com.android.csuite.core.DeviceUtils.RunnableThrowingDeviceNotAvailable; 28 import com.android.csuite.core.TestUtils; 29 import com.android.tradefed.config.Option; 30 import com.android.tradefed.device.DeviceNotAvailableException; 31 import com.android.tradefed.log.LogUtil.CLog; 32 import com.android.tradefed.result.InputStreamSource; 33 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 34 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData; 35 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 36 import com.android.tradefed.util.RunUtil; 37 38 import com.google.common.annotations.VisibleForTesting; 39 import com.google.common.base.Preconditions; 40 41 import org.junit.After; 42 import org.junit.Assert; 43 import org.junit.Before; 44 import org.junit.Rule; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 48 import java.awt.image.BufferedImage; 49 import java.io.File; 50 import java.io.IOException; 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.concurrent.atomic.AtomicReference; 54 import java.util.stream.Collectors; 55 56 import javax.imageio.ImageIO; 57 58 /** A test that verifies that a single app can be successfully launched. */ 59 @RunWith(DeviceJUnit4ClassRunner.class) 60 public class AppLaunchTest extends BaseHostJUnit4Test { 61 @VisibleForTesting static final String SCREENSHOT_AFTER_LAUNCH = "screenshot-after-launch"; 62 @VisibleForTesting static final String COLLECT_APP_VERSION = "collect-app-version"; 63 @VisibleForTesting static final String COLLECT_GMS_VERSION = "collect-gms-version"; 64 @VisibleForTesting static final String RECORD_SCREEN = "record-screen"; 65 @Rule public TestLogData mLogData = new TestLogData(); 66 private ApkInstaller mApkInstaller; 67 private boolean mIsLastTestPass; 68 private boolean mIsApkSaved = false; 69 70 @Option(name = RECORD_SCREEN, description = "Whether to record screen during test.") 71 private boolean mRecordScreen; 72 73 @Option( 74 name = SCREENSHOT_AFTER_LAUNCH, 75 description = "Whether to take a screenshost after a package is launched.") 76 private boolean mScreenshotAfterLaunch; 77 78 @Option( 79 name = COLLECT_APP_VERSION, 80 description = 81 "Whether to collect package version information and store the information in" 82 + " test log files.") 83 private boolean mCollectAppVersion; 84 85 @Option( 86 name = COLLECT_GMS_VERSION, 87 description = 88 "Whether to collect GMS core version information and store the information in" 89 + " test log files.") 90 private boolean mCollectGmsVersion; 91 92 @Option( 93 name = "install-apk", 94 description = 95 "The path to an apk file or a directory of apk files of a singe package to be" 96 + " installed on device. Can be repeated.") 97 private final List<File> mApkPaths = new ArrayList<>(); 98 99 @Option( 100 name = "install-arg", 101 description = "Arguments for the 'adb install-multiple' package installation command.") 102 private final List<String> mInstallArgs = new ArrayList<>(); 103 104 @Option( 105 name = "save-apk-when", 106 description = "When to save apk files to the test result artifacts.") 107 private TestUtils.TakeEffectWhen mSaveApkWhen = TestUtils.TakeEffectWhen.NEVER; 108 109 @Option(name = "package-name", description = "Package name of testing app.") 110 protected String mPackageName; 111 112 @Option( 113 name = "app-launch-timeout-ms", 114 description = "Time to wait for app to launch in msecs.") 115 private int mAppLaunchTimeoutMs = 15000; 116 117 @Option( 118 name = "blank-screen-same-color-area-threshold", 119 description = 120 "Percentage of the screen which, if occupied by a same-color rectangle " 121 + "area, indicates that the app has reached a blank screen.") 122 private double mBlankScreenSameColorThreshold = -1; 123 124 @Before setUp()125 public void setUp() throws DeviceNotAvailableException, ApkInstallerException, IOException { 126 Assert.assertNotNull("Package name cannot be null", mPackageName); 127 mIsLastTestPass = false; 128 129 DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice()); 130 TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData); 131 132 mApkInstaller = ApkInstaller.getInstance(getDevice()); 133 mApkInstaller.install( 134 mApkPaths.stream().map(File::toPath).collect(Collectors.toList()), mInstallArgs); 135 136 if (mCollectGmsVersion) { 137 testUtils.collectGmsVersion(mPackageName); 138 } 139 140 if (mCollectAppVersion) { 141 testUtils.collectAppVersion(mPackageName); 142 } 143 144 deviceUtils.freezeRotation(); 145 } 146 147 @Test testAppCrash()148 public void testAppCrash() throws DeviceNotAvailableException, IOException { 149 CLog.d("Launching package: %s.", mPackageName); 150 151 DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice()); 152 TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData); 153 154 try { 155 if (!deviceUtils.isPackageInstalled(mPackageName)) { 156 Assert.fail( 157 "Package " 158 + mPackageName 159 + " is not installed on the device. Aborting the test."); 160 } 161 } catch (DeviceUtilsException e) { 162 Assert.fail("Failed to check the installed package list: " + e.getMessage()); 163 } 164 165 AtomicReference<DeviceTimestamp> startTime = new AtomicReference<>(); 166 AtomicReference<DeviceTimestamp> videoStartTime = new AtomicReference<>(); 167 168 RunnableThrowingDeviceNotAvailable launchJob = 169 () -> { 170 startTime.set(deviceUtils.currentTimeMillis()); 171 try { 172 deviceUtils.launchPackage(mPackageName); 173 } catch (DeviceUtilsException e) { 174 Assert.fail( 175 "Failed to launch package " + mPackageName + ": " + e.getMessage()); 176 } 177 178 CLog.d( 179 "Waiting %s milliseconds for the app to launch fully.", 180 mAppLaunchTimeoutMs); 181 RunUtil.getDefault().sleep(mAppLaunchTimeoutMs); 182 }; 183 184 if (mRecordScreen) { 185 testUtils.collectScreenRecord( 186 launchJob, 187 mPackageName, 188 videoStartTimeOnDevice -> videoStartTime.set(videoStartTimeOnDevice)); 189 } else { 190 launchJob.run(); 191 } 192 193 CLog.d("Completed launching package: %s", mPackageName); 194 DeviceTimestamp endTime = deviceUtils.currentTimeMillis(); 195 196 try { 197 List<DropboxEntry> crashEntries = 198 deviceUtils.getDropboxEntries( 199 DeviceUtils.DROPBOX_APP_CRASH_TAGS, 200 mPackageName, 201 startTime.get(), 202 endTime); 203 String crashLog = 204 testUtils.compileTestFailureMessage( 205 mPackageName, crashEntries, true, videoStartTime.get()); 206 if (crashLog != null) { 207 Assert.fail(crashLog); 208 } 209 } catch (IOException e) { 210 Assert.fail("Error while getting dropbox crash log: " + e); 211 } 212 213 if (mBlankScreenSameColorThreshold > 0) { 214 BufferedImage screen; 215 try (InputStreamSource screenShot = 216 testUtils.getTestInformation().getDevice().getScreenshot()) { 217 Preconditions.checkNotNull(screenShot); 218 screen = ImageIO.read(screenShot.createInputStream()); 219 } 220 BlankScreen blankScreen = 221 BlankScreenDetectorWithSameColorRectangle.getBlankScreen(screen); 222 double blankScreenPercent = blankScreen.getBlankScreenPercent(); 223 if (blankScreenPercent > mBlankScreenSameColorThreshold) { 224 BlankScreenDetectorWithSameColorRectangle.saveBlankScreenArtifact( 225 mPackageName, 226 blankScreen, 227 testUtils.getTestArtifactReceiver(), 228 testUtils.getTestInformation().getDevice().getSerialNumber()); 229 Assert.fail( 230 String.format( 231 "Blank screen detected with same-color rectangle area percentage of" 232 + " %.2f%%", 233 blankScreenPercent * 100)); 234 } 235 } 236 237 mIsLastTestPass = true; 238 } 239 240 @After tearDown()241 public void tearDown() throws DeviceNotAvailableException, ApkInstallerException { 242 DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice()); 243 TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData); 244 245 if (!mIsApkSaved) { 246 mIsApkSaved = 247 testUtils.saveApks(mSaveApkWhen, mIsLastTestPass, mPackageName, mApkPaths); 248 } 249 250 if (mScreenshotAfterLaunch) { 251 testUtils.collectScreenshot(mPackageName); 252 } 253 254 deviceUtils.stopPackage(mPackageName); 255 deviceUtils.unfreezeRotation(); 256 257 mApkInstaller.uninstallAllInstalledPackages(); 258 } 259 } 260