• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 package com.google.android.angleallowlists.vts;
17 
18 import static org.junit.Assert.assertFalse;
19 import static org.junit.Assert.fail;
20 
21 import com.android.ddmlib.Log;
22 import com.android.tradefed.device.DeviceNotAvailableException;
23 import com.android.tradefed.invoker.TestInformation;
24 import com.android.tradefed.log.LogUtil;
25 import com.android.tradefed.metrics.proto.MetricMeasurement;
26 import com.android.tradefed.result.FileInputStreamSource;
27 import com.android.tradefed.result.LogDataType;
28 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
29 import com.android.tradefed.util.ArrayUtil;
30 import com.android.tradefed.util.CommandResult;
31 import com.android.tradefed.util.CommandStatus;
32 import com.android.tradefed.util.FileUtil;
33 import com.android.tradefed.util.IRunUtil;
34 import com.android.tradefed.util.RunUtil;
35 import com.google.common.io.Files;
36 import java.io.BufferedWriter;
37 import java.io.File;
38 import java.io.FileNotFoundException;
39 import java.io.FileOutputStream;
40 import java.io.FileWriter;
41 import java.io.IOException;
42 import java.io.OutputStream;
43 import java.nio.charset.StandardCharsets;
44 import org.junit.rules.TemporaryFolder;
45 
46 public class Helper {
47     /** Runs commands, like adb. */
48     private final IRunUtil mRunUtil;
49 
50     /** The serial number of the device, so we can execute adb commands on this device. */
51     private final String mDeviceSerialNumber;
52 
53     /** TradeFed object for saving metrics (key-value pairs). */
54     private final DeviceJUnit4ClassRunner.TestMetrics mMetrics;
55 
56     /**
57      * TradeFed object for saving "artifacts" (i.e. files) that TradeFed saves for inspection when
58      * investigating the results of tests.
59      */
60     private final DeviceJUnit4ClassRunner.TestLogData mLogData;
61 
62     /**
63      * The test method name. Used so that logged metrics (see {@link
64      * Helper#saveMetricsAsArtifact()}) include the test method name.
65      */
66     private final String mMethodName;
67 
68     /** The file to which metrics are output. See {@link Helper#mMetricsLog}. */
69     private final File mMetricsTextLogFile;
70 
71     /**
72      * Used to store all metrics (i.e. key-value pairs) in CSV format so that all metrics can be
73      * saved as an artifact, which TradeFed saves for inspection when investigating the results of
74      * tests. This is useful for local runs and may be useful in the future because the newer
75      * Android test infrastructure does not ingest metrics.
76      */
77     private final BufferedWriter mMetricsLog;
78 
79     // These are timeout values used to wait for certain types of actions to complete.
80     public static final int WAIT_INSTALL_APK_MILLIS = 120 * 1000;
81     public static final int WAIT_SET_GLOBAL_SETTING_MILLIS = 5 * 1000;
82     public static final int WAIT_ADB_SHELL_FILE_OP_MILLIS = 5 * 1000;
83     public static final int WAIT_ADB_LARGE_FILE_OP_MILLIS = 5 * 60 * 1000;
84     public static final int WAIT_APP_UNINSTALL_MILLIS = 5 * 1000;
85 
86     public static final int PAUSE_AFTER_REBOOT_MILLIS = 20 * 1000;
87     public static final int PAUSE_AFTER_ADB_COMMAND_MILLIS = 2 * 1000;
88 
89     public static final int NUM_RETRIES = 3;
90 
Helper(final TestInformation testInformation, final TemporaryFolder temporaryFolder, final DeviceJUnit4ClassRunner.TestMetrics metrics, final DeviceJUnit4ClassRunner.TestLogData logData, final String methodName)91     public Helper(final TestInformation testInformation, final TemporaryFolder temporaryFolder,
92             final DeviceJUnit4ClassRunner.TestMetrics metrics,
93             final DeviceJUnit4ClassRunner.TestLogData logData, final String methodName)
94             throws IOException, DeviceNotAvailableException, CommandException,
95                    InterruptedException {
96         mRunUtil = RunUtil.getDefault();
97         mDeviceSerialNumber = testInformation.getDevice().getSerialNumber();
98         mMetrics = metrics;
99         mLogData = logData;
100 
101         mMethodName = methodName;
102         mMetricsTextLogFile = temporaryFolder.newFile(String.format("metrics_%s.txt", mMethodName));
103         mMetricsLog = new BufferedWriter(new FileWriter(mMetricsTextLogFile));
104 
105         preTestSetup();
106         assertDeviceStateOk();
107     }
108 
109     /** verify that device is ready to run tests */
assertDeviceStateOk()110     public void assertDeviceStateOk() throws CommandException, DeviceNotAvailableException {
111         // Check if device is locked or screen is off.
112         CommandResult result = adbShellCommandCheck(
113                 WAIT_ADB_SHELL_FILE_OP_MILLIS, /*logOutput*/ false, "dumpsys window");
114         try {
115             assertFalse(
116                     "Screen was off", result.getStdout().contains("screenState=SCREEN_STATE_OFF"));
117 
118             assertFalse("Screen was locked",
119                     result.getStdout().contains("KeyguardStateMonitor\n        mIsShowing=true"));
120 
121         } catch (final Throwable ex) {
122             LogUtil.CLog.e(
123                     "Details of dumpsys window: %s", Helper.getCommandResultAsString(result));
124 
125             throw ex;
126         }
127     }
128 
preTestSetup()129     private void preTestSetup()
130             throws DeviceNotAvailableException, CommandException, InterruptedException {
131         // Keep screen on while plugged in.
132         adbShellCommandCheck(WAIT_ADB_SHELL_FILE_OP_MILLIS, "svc power stayon true");
133         RunUtil.getDefault().sleep(PAUSE_AFTER_ADB_COMMAND_MILLIS);
134 
135         // Turn screen on.
136         adbShellCommandCheck(WAIT_ADB_SHELL_FILE_OP_MILLIS, "input keyevent KEYCODE_WAKEUP");
137         RunUtil.getDefault().sleep(PAUSE_AFTER_ADB_COMMAND_MILLIS);
138 
139         // Skip lock screen.
140         adbShellCommandCheck(WAIT_ADB_SHELL_FILE_OP_MILLIS, "wm dismiss-keyguard");
141         RunUtil.getDefault().sleep(PAUSE_AFTER_ADB_COMMAND_MILLIS);
142 
143         // Disable notifications like "you're entering full screen mode", which may affect the
144         // results.
145         adbShellCommandCheck(WAIT_ADB_SHELL_FILE_OP_MILLIS,
146                 "settings put secure immersive_mode_confirmations confirmed");
147         adbShellCommandCheck(WAIT_ADB_SHELL_FILE_OP_MILLIS,
148                 "settings put global heads_up_notifications_enabled 0");
149     }
150 
151     /**
152      * Saves a text file as an "artifact", which TradeFed saves for inspection when investigating
153      * the results of tests.
154      */
saveTextFileAsArtifact(final String dataName, final File textFile)155     public void saveTextFileAsArtifact(final String dataName, final File textFile) {
156         try (FileInputStreamSource fiss = new FileInputStreamSource(textFile)) {
157             mLogData.addTestLog(
158                     String.format("%s_%s", mMethodName, dataName), LogDataType.TEXT, fiss);
159         }
160     }
161 
162     /**
163      * Logs the contents of a text file. This goes to the "host log", which TradeFed saves for
164      * inspection when investigating the results of tests.
165      */
logTextFile(final String tag, final File textFile)166     public void logTextFile(final String tag, final File textFile) throws IOException {
167         final String text = Files.asCharSource(textFile, StandardCharsets.UTF_8).read();
168         Log.d(tag, text);
169     }
170 
171     /**
172      * Saves a metric (i.e. key-value pair). Depending on various TradeFed options, these get saved
173      * to a database for querying, ingestion into dashboards, etc. They are also usually output to
174      * the terminal when running a test using `atest`. This function also appends the metric ("key,
175      * value") to {@link Helper#mMetricsLog}, which is saved as an artifact so that all metrics are
176      * made available in a simple CSV format. This is useful for local runs and may be useful in the
177      * future because the newer Android test infrastructure does not ingest metrics.
178      */
logMetricDouble(final String metricName, final String metricValue, final String unit)179     public void logMetricDouble(final String metricName, final String metricValue,
180             final String unit) throws IOException {
181         final double doubleValue = Double.parseDouble(metricValue);
182 
183         final MetricMeasurement.Metric metric =
184                 MetricMeasurement.Metric.newBuilder()
185                         .setMeasurements(
186                                 MetricMeasurement.Measurements.newBuilder().setSingleDouble(
187                                         doubleValue))
188                         .setUnit(unit)
189                         .build();
190 
191         mMetrics.addTestMetric(metricName, metric);
192 
193         mMetricsLog.write(String.format("%s#%s,%s", mMethodName, metricName, metricValue));
194         mMetricsLog.newLine();
195     }
196 
197     /**
198      * Saves a metric (i.e. key-value pair). Depending on various TradeFed options, these get saved
199      * to a database for querying, ingestion into dashboards, etc. They are also usually output to
200      * the terminal when running a test using `atest`. This function also appends the metric ("key,
201      * value") to {@link Helper#mMetricsLog}, which is saved as an artifact so that all metrics are
202      * made available in a simple CSV format. This is useful for local runs and may be useful in the
203      * future because the newer Android test infrastructure does not ingest metrics.
204      */
logMetricString(final String metricName, final String metricValue)205     public void logMetricString(final String metricName, final String metricValue)
206             throws IOException {
207         final MetricMeasurement.Metric metric =
208                 MetricMeasurement.Metric.newBuilder()
209                         .setMeasurements(
210                                 MetricMeasurement.Measurements.newBuilder().setSingleString(
211                                         metricValue))
212                         .build();
213 
214         mMetrics.addTestMetric(metricName, metric);
215 
216         mMetricsLog.write(String.format("%s#%s,%s", mMethodName, metricName, metricValue));
217         mMetricsLog.newLine();
218     }
219 
220     /**
221      * Saves all metrics (i.e. key-value pairs) as an "artifact", which TradeFed saves for
222      * inspection when investigating the results of tests. The metric functions in {@link Helper}
223      * append every metric ("key, value") to {@link Helper#mMetricsLog}, which this function saves
224      * as an artifact so that all metrics are made available in a simple CSV format. This is useful
225      * for local runs and may be useful in the future because the newer Android test infrastructure
226      * does not ingest metrics.
227      */
saveMetricsAsArtifact()228     public void saveMetricsAsArtifact() throws IOException {
229         mMetricsLog.close();
230 
231         saveTextFileAsArtifact(
232                 Files.getNameWithoutExtension(mMetricsTextLogFile.getName()), mMetricsTextLogFile);
233     }
234 
235     /** Install the apkFile onto the device */
installApkFile(final File apkFile)236     public void installApkFile(final File apkFile) throws CommandException {
237         adbCommandCheck(WAIT_INSTALL_APK_MILLIS, "install", "-r", "-d", "-g", apkFile.toString());
238     }
239 
240     /**
241      * Runs an "adb shell am instrument" command.
242      *
243      * <p>The command must start with "am instrument".
244      *
245      * @throws CommandException if the adb command fails
246      * @throws InstrumentationCrashException if the stdout contains shortMsg=Process crashed
247      */
adbShellInstrumentationCommandCheck(final long timeout, final String command)248     public CommandResult adbShellInstrumentationCommandCheck(final long timeout,
249             final String command) throws CommandException, InstrumentationCrashException {
250         if (!command.startsWith("am instrument")) {
251             throw new IllegalArgumentException(String.format(
252                     "Instrumentation command must start with 'am instrument': %s", command));
253         }
254         final String[] commandArray = new String[] {"shell", command};
255         final CommandResult result = adbCommandCheck(timeout, commandArray);
256         if (result.getStdout().contains("shortMsg=Process crashed")) {
257             throw new InstrumentationCrashException(commandArray, result);
258         }
259         return result;
260     }
261 
262     /** Runs adb shell command and returns the result code, also logs the result code */
adbShellCommandCheck(final long timeout, final String command)263     public CommandResult adbShellCommandCheck(final long timeout, final String command)
264             throws CommandException {
265         return adbShellCommandCheck(timeout, true, command);
266     }
267 
adbShellCommandCheck(final long timeout, final boolean logOutput, final String command)268     private CommandResult adbShellCommandCheck(final long timeout, final boolean logOutput,
269             final String command) throws CommandException {
270         return adbCommandCheck(timeout, logOutput, "shell", command);
271     }
272 
273     /** Runs adb command and returns the command result code, also logs the result code */
adbCommandCheck(final long timeout, final String... command)274     public CommandResult adbCommandCheck(final long timeout, final String... command)
275             throws CommandException {
276         return adbCommandCheck(timeout, true, command);
277     }
278 
adbCommandCheck(final long timeout, final boolean logOutput, final String... command)279     private CommandResult adbCommandCheck(final long timeout, final boolean logOutput,
280             final String... command) throws CommandException {
281         final String[] newCommand =
282                 ArrayUtil.buildArray(new String[] {"adb", "-s", mDeviceSerialNumber}, command);
283         return commandCheck(timeout, logOutput, newCommand);
284     }
285 
286     /** Runs command and returns the result */
commandCheck(final long timeout, final boolean logOutput, final String... command)287     private CommandResult commandCheck(final long timeout, final boolean logOutput,
288             final String... command) throws CommandException {
289         final CommandResult result = mRunUtil.runTimedCmd(timeout, command);
290         if (!result.getStatus().equals(CommandStatus.SUCCESS)) {
291             throw new CommandException(command, result);
292         }
293         if (logOutput) {
294             LogUtil.CLog.d(Helper.getCommandResultAsString(result));
295         }
296         return result;
297     }
298 
299     /** compile command result code into a full string */
getCommandResultAsString(final CommandResult commandResult)300     public static String getCommandResultAsString(final CommandResult commandResult) {
301         if (commandResult == null) {
302             return "No details of command result.";
303         }
304         return String.format("Exit code: %d\nStdout: %s\nStderr: %s\n", commandResult.getExitCode(),
305                 commandResult.getStdout(), commandResult.getStderr());
306     }
307 
308     /** runs adb shell command and return the stdout if there is any */
adbShellCommandWithStdout(final long timeout, final File stdOutFile, final String... command)309     public String adbShellCommandWithStdout(final long timeout, final File stdOutFile,
310             final String... command) throws CommandException, FileNotFoundException, IOException {
311         OutputStream stdout = new FileOutputStream(stdOutFile);
312         final String[] newCommand = ArrayUtil.buildArray(
313                 new String[] {"adb", "-s", mDeviceSerialNumber, "shell"}, command);
314         mRunUtil.runTimedCmd(timeout, stdout, null, newCommand);
315         return FileUtil.readStringFromFile(stdOutFile).trim();
316     }
317 
318     /** create a directory on test device */
deviceMkDirP(final String dir)319     public void deviceMkDirP(final String dir) throws CommandException {
320         adbShellCommandCheck(WAIT_ADB_SHELL_FILE_OP_MILLIS, String.format("mkdir -p %s", dir));
321     }
322 
323     /** uninstall app */
uninstallAppIgnoreErrors(final String appPackageName)324     public CommandResult uninstallAppIgnoreErrors(final String appPackageName) {
325         try {
326             return adbCommandCheck(WAIT_APP_UNINSTALL_MILLIS, "uninstall", appPackageName);
327         } catch (final CommandException commandException) {
328             LogUtil.CLog.w(commandException);
329             return commandException.getCommandResult();
330         }
331     }
332 
333     /** construct the file full path from initialPath and pathSegments */
path(final File initialPath, final String... pathSegments)334     public static File path(final File initialPath, final String... pathSegments) {
335         return FileUtil.getFileForPath(initialPath, pathSegments);
336     }
337 }
338