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.AppCrawlTester;
22 import com.android.csuite.core.DeviceUtils;
23 import com.android.csuite.core.TestUtils;
24 import com.android.tradefed.config.Option;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
28 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
29 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
30 
31 import com.google.common.base.Preconditions;
32 
33 import org.junit.After;
34 import org.junit.Before;
35 import org.junit.Rule;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 
39 import java.io.File;
40 import java.io.IOException;
41 import java.nio.file.Path;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.List;
45 import java.util.stream.Collectors;
46 
47 import javax.annotation.Nullable;
48 
49 /** A test that verifies that a single app can be successfully launched. */
50 @RunWith(DeviceJUnit4ClassRunner.class)
51 public class AppCrawlTest extends BaseHostJUnit4Test {
52     private static final String COLLECT_APP_VERSION = "collect-app-version";
53     private static final String COLLECT_GMS_VERSION = "collect-gms-version";
54     private static final String RECORD_SCREEN = "record-screen";
55 
56     @Rule public TestLogData mLogData = new TestLogData();
57     private boolean mIsLastTestPass;
58     private boolean mIsApkSaved = false;
59 
60     private ApkInstaller mApkInstaller;
61     private AppCrawlTester mCrawler;
62 
63     @Option(name = RECORD_SCREEN, description = "Whether to record screen during test.")
64     private boolean mRecordScreen;
65 
66     @Option(
67             name = COLLECT_APP_VERSION,
68             description =
69                     "Whether to collect package version information and store the information in"
70                             + " test log files.")
71     private boolean mCollectAppVersion;
72 
73     @Option(
74             name = COLLECT_GMS_VERSION,
75             description =
76                     "Whether to collect GMS core version information and store the information in"
77                             + " test log files.")
78     private boolean mCollectGmsVersion;
79 
80     @Option(
81             name = "repack-apk",
82             mandatory = false,
83             description =
84                     "Path to an apk file or a directory containing apk files of a single package "
85                             + "to repack and install in Espresso mode")
86     private File mRepackApk;
87 
88     @Option(
89             name = "install-apk",
90             mandatory = false,
91             description =
92                     "The path to an apk file or a directory of apk files to be installed on the"
93                             + " device. In Ui-automator mode, this includes both the target apk to"
94                             + " install and any dependencies. In Espresso mode this can include"
95                             + " additional libraries or dependencies.")
96     private final List<File> mInstallApkPaths = new ArrayList<>();
97 
98     @Option(
99             name = "install-arg",
100             description =
101                     "Arguments for the 'adb install-multiple' package installation command for"
102                             + " UI-automator mode.")
103     private final List<String> mInstallArgs = new ArrayList<>();
104 
105     @Option(name = "package-name", mandatory = true, description = "Package name of testing app.")
106     private String mPackageName;
107 
108     @Option(
109             name = "crawl-controller-endpoint",
110             mandatory = false,
111             description = "The crawl controller endpoint to target.")
112     private String mCrawlControllerEndpoint;
113 
114     @Option(
115             name = "ui-automator-mode",
116             mandatory = false,
117             description =
118                     "Run the crawler with UIAutomator mode. Apk option is not required in this"
119                             + " mode.")
120     private boolean mUiAutomatorMode = false;
121 
122     @Option(
123             name = "timeout-sec",
124             mandatory = false,
125             description = "The timeout for the crawl test.")
126     private int mTimeoutSec = 60;
127 
128     @Option(
129             name = "robo-script-file",
130             description = "A Roboscript file to be executed by the crawler.")
131     private File mRoboscriptFile;
132 
133     // TODO(b/234512223): add support for contextual roboscript files
134 
135     @Option(
136             name = "crawl-guidance-proto-file",
137             description = "A CrawlGuidance file to be executed by the crawler.")
138     private File mCrawlGuidanceProtoFile;
139 
140     @Option(
141             name = "login-config-dir",
142             description =
143                     "A directory containing Roboscript and CrawlGuidance files with login"
144                         + " credentials that are passed to the crawler. There should be one config"
145                         + " file per package name. If both Roboscript and CrawlGuidance files are"
146                         + " present, only the Roboscript file will be used.")
147     private File mLoginConfigDir;
148 
149     @Option(
150             name = "save-apk-when",
151             description = "When to save apk files to the test result artifacts.")
152     private TestUtils.TakeEffectWhen mSaveApkWhen = TestUtils.TakeEffectWhen.NEVER;
153 
154     @Option(
155             name = "grant-external-storage",
156             mandatory = false,
157             description = "After an apks are installed, grant MANAGE_EXTERNAL_STORAGE permissions.")
158     private boolean mGrantExternalStoragePermission = false;
159 
160     @Before
setUp()161     public void setUp()
162             throws ApkInstaller.ApkInstallerException, IOException, DeviceNotAvailableException {
163         DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice());
164         mIsLastTestPass = false;
165         mCrawler = AppCrawlTester.newInstance(mPackageName, getTestInformation(), mLogData);
166         if (!mUiAutomatorMode) {
167             setApkForEspressoMode();
168         }
169         mCrawler.setCrawlControllerEndpoint(mCrawlControllerEndpoint);
170         mCrawler.setRecordScreen(mRecordScreen);
171         mCrawler.setCollectGmsVersion(mCollectGmsVersion);
172         mCrawler.setCollectAppVersion(mCollectAppVersion);
173         mCrawler.setUiAutomatorMode(mUiAutomatorMode);
174         mCrawler.setRoboscriptFile(toPathOrNull(mRoboscriptFile));
175         mCrawler.setCrawlGuidanceProtoFile(toPathOrNull(mCrawlGuidanceProtoFile));
176         mCrawler.setLoginConfigDir(toPathOrNull(mLoginConfigDir));
177         mCrawler.setTimeoutSec(mTimeoutSec);
178 
179         mApkInstaller = ApkInstaller.getInstance(getDevice());
180         mApkInstaller.install(
181                 mInstallApkPaths.stream().map(File::toPath).collect(Collectors.toList()),
182                 mInstallArgs);
183         if (mGrantExternalStoragePermission) {
184             deviceUtils.grantExternalStoragePermissions(mPackageName);
185         }
186     }
187 
188     /** Helper method to fetch the path of optional File variables. */
toPathOrNull(@ullable File f)189     private static Path toPathOrNull(@Nullable File f) {
190         return f == null ? null : f.toPath();
191     }
192 
193     /**
194      * For Espresso mode, checks that a path with the location of the apk to repackage was provided
195      */
setApkForEspressoMode()196     private void setApkForEspressoMode() {
197         Preconditions.checkNotNull(
198                 mRepackApk, "Apk file path is required when not running in UIAutomator mode");
199         // set the root path of the target apk for Espresso mode
200         mCrawler.setApkPath(mRepackApk.toPath());
201     }
202 
203     @Test
testAppCrash()204     public void testAppCrash() throws DeviceNotAvailableException {
205         mCrawler.startAndAssertAppNoCrash();
206         mIsLastTestPass = true;
207     }
208 
209     @After
tearDown()210     public void tearDown() throws DeviceNotAvailableException {
211         TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData);
212 
213         if (!mIsApkSaved) {
214             mIsApkSaved =
215                     testUtils.saveApks(
216                             mSaveApkWhen, mIsLastTestPass, mPackageName, mInstallApkPaths);
217             if (mRepackApk != null) {
218                 mIsApkSaved &=
219                         testUtils.saveApks(
220                                 mSaveApkWhen,
221                                 mIsLastTestPass,
222                                 mPackageName,
223                                 Arrays.asList(mRepackApk));
224             }
225         }
226 
227         try {
228             mApkInstaller.uninstallAllInstalledPackages();
229         } catch (ApkInstallerException e) {
230             CLog.w("Uninstallation of installed apps failed during teardown: %s", e.getMessage());
231         }
232         if (!mUiAutomatorMode) {
233             getDevice().uninstallPackage(mPackageName);
234         }
235 
236         mCrawler.cleanUp();
237     }
238 }
239