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