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