1 /* 2 * Copyright (C) 2022 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.catbox.result; 18 19 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 20 import com.android.compatibility.common.tradefed.util.CollectorUtil; 21 22 import com.android.ddmlib.Log.LogLevel; 23 24 import com.android.tradefed.build.IBuildInfo; 25 import com.android.tradefed.config.Option; 26 import com.android.tradefed.config.OptionClass; 27 import com.android.tradefed.device.DeviceNotAvailableException; 28 import com.android.tradefed.device.ITestDevice; 29 30 import com.android.tradefed.log.LogUtil.CLog; 31 32 import com.android.tradefed.targetprep.BuildError; 33 import com.android.tradefed.targetprep.ITargetPreparer; 34 import com.android.tradefed.targetprep.TargetSetupError; 35 36 import com.android.tradefed.util.CommandResult; 37 import com.android.tradefed.util.CommandStatus; 38 39 import com.google.common.base.Strings; 40 41 import java.io.File; 42 import java.io.FileOutputStream; 43 import java.io.IOException; 44 import java.io.OutputStream; 45 46 import java.util.HashMap; 47 import java.util.Map; 48 49 /** 50 * ResultReportCollector is an {@link ITargetPreparer} that pulls the test result reports from the 51 * device and adds it to the Results. 52 */ 53 @OptionClass(alias = "result-report-collector") 54 public class ResultReportCollector implements ITargetPreparer { 55 @Option( 56 name = "pull-file-content-uri", 57 description = "Copy the src files from device to destination using content uri") 58 private Map<String, String> mFileContentUriMap = new HashMap<String, String>(); 59 60 @Option( 61 name = "pull-file-path", 62 description = "Copy the src files from device to destination using file path") 63 private Map<String, String> mFilePathMap = new HashMap<String, String>(); 64 65 @Option( 66 name = "pull-dir-path", 67 description = "Copy the src directory from device to destination using directory path") 68 private Map<String, String> mDirPathMap = new HashMap<String, String>(); 69 70 private static final String NO_RESULTS_STRING = "No result found."; 71 private static final String ERROR_MESSAGE_TAG = "[ERROR]"; 72 73 @Override setUp(ITestDevice device, IBuildInfo buildInfo)74 public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, 75 BuildError, DeviceNotAvailableException { 76 // Nothing To Do 77 } 78 79 @Override tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)80 public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) 81 throws DeviceNotAvailableException { 82 // Pull files from device using Content URI 83 pullFilesUsingContentUri(device, buildInfo, mFileContentUriMap); 84 85 // Pull files from device using File Paths 86 pullFilesUsingFilePaths(device, buildInfo, mFilePathMap); 87 88 // Pull directories from device using Directory Paths 89 pullDirUsingFilePaths(device, buildInfo, mDirPathMap); 90 } 91 92 /** Pull files from the device using Content URI */ pullFilesUsingContentUri(ITestDevice device, IBuildInfo buildInfo, Map<String, String> contentUriMap)93 private void pullFilesUsingContentUri(ITestDevice device, IBuildInfo buildInfo, 94 Map<String, String> contentUriMap) throws DeviceNotAvailableException { 95 if (contentUriMap.isEmpty()) { 96 // No Content URI provided 97 return; 98 } 99 CLog.logAndDisplay( 100 LogLevel.INFO, 101 "Started pulling file using Content URI."); 102 103 // Iterate over given Content URIs 104 for (Map.Entry<String, String> entry: contentUriMap.entrySet()) { 105 // Pull file using Content URI 106 pullFileUsingContentUri(device, buildInfo, entry.getKey(), entry.getValue()); 107 } 108 109 CLog.logAndDisplay( 110 LogLevel.INFO, 111 "Completed pulling file using Content URI."); 112 } 113 pullFileUsingContentUri(ITestDevice device, IBuildInfo buildInfo, String fileContentUri, String destFilePath)114 private void pullFileUsingContentUri(ITestDevice device, IBuildInfo buildInfo, 115 String fileContentUri, String destFilePath) throws DeviceNotAvailableException { 116 // Validate Source and Destination File Path 117 if (Strings.isNullOrEmpty(fileContentUri) || Strings.isNullOrEmpty(destFilePath) || 118 !fileContentUri.startsWith("content://")) { 119 CLog.logAndDisplay( 120 LogLevel.ERROR, 121 String.format("Either Src or Dest Path is invalid. Source: %s, Destination: %s", 122 fileContentUri, destFilePath)); 123 return; 124 } 125 126 CLog.logAndDisplay( 127 LogLevel.INFO, 128 String.format("Started pulling file. Source File: %s, Destination File: %s", 129 fileContentUri, destFilePath)); 130 131 // Check if file exist on the device 132 if (!doesFileExist(device, fileContentUri)) { 133 CLog.logAndDisplay( 134 LogLevel.ERROR, 135 String.format("File %s does not exist on the device.", 136 fileContentUri)); 137 return; 138 } 139 140 // Get Destination File using filepath 141 File destinationFile = getDestinationFile(buildInfo, destFilePath); 142 143 if (destinationFile == null) { 144 CLog.logAndDisplay( 145 LogLevel.ERROR, 146 String.format("Unable to get destination file path for %s.", 147 destFilePath)); 148 return; 149 } 150 151 // Create the result directory if it does not exist 152 // isDestFile=true : Since destination is a file 153 if (!createResultDir(buildInfo, destinationFile, true /* isDestFile */)) { 154 CLog.logAndDisplay( 155 LogLevel.ERROR, 156 String.format("Unable to create results directory %s.", 157 destFilePath)); 158 return; 159 } 160 161 // Create Pull Command 162 String pullCommand = 163 String.format("content read --user %d --uri %s", 164 device.getCurrentUser(), 165 fileContentUri); 166 167 // Open the output stream to the local file. 168 // try-with-resource should close the stream once the try block completes 169 // so we don't need a finally block to close the stream 170 try (OutputStream localFileStream = new FileOutputStream(destinationFile)) { 171 CommandResult pullResult = device.executeShellV2Command(pullCommand, 172 localFileStream); 173 if (!isSuccessful(pullResult)) { 174 String stderr = pullResult.getStderr(); 175 CLog.logAndDisplay( 176 LogLevel.ERROR, 177 String.format( 178 "Failed to pull a file at '%s' to %s. Error: '%s'", 179 fileContentUri, destFilePath, stderr)); 180 } 181 } catch(IOException ex) { 182 CLog.logAndDisplay( 183 LogLevel.ERROR, 184 String.format("Failed to open OutputStream to the local file %s. Error: %s", 185 destFilePath, ex.getMessage())); 186 return; 187 } 188 189 CLog.logAndDisplay( 190 LogLevel.INFO, 191 String.format("Completed pulling file. Source File: %s, Destination File: %s", 192 fileContentUri, destFilePath)); 193 } 194 195 /** Verify if file exists */ doesFileExist(ITestDevice device, String contentUri)196 private boolean doesFileExist(ITestDevice device, String contentUri) 197 throws DeviceNotAvailableException { 198 String queryContentCommand = 199 String.format( 200 "content query --user %d --uri %s", device.getCurrentUser(), contentUri); 201 202 String listCommandResult = device.executeShellCommand(queryContentCommand); 203 204 return (!NO_RESULTS_STRING.equals(listCommandResult.trim())); 205 } 206 207 /** Verify Command Result for success */ isSuccessful(CommandResult result)208 private boolean isSuccessful(CommandResult result) { 209 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 210 return false; 211 } 212 String stdout = result.getStdout(); 213 if (stdout.contains(ERROR_MESSAGE_TAG)) { 214 return false; 215 } 216 return Strings.isNullOrEmpty(result.getStderr()); 217 } 218 219 /** Get Destination File using file path */ getDestinationFile(IBuildInfo buildInfo, String destPath)220 private File getDestinationFile(IBuildInfo buildInfo, String destPath) { 221 File destinationFile = null; 222 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo); 223 try { 224 // get results directory 225 destinationFile = buildHelper.getResultDir(); 226 // get destination file using path 227 destinationFile = new File(destinationFile, destPath); 228 } catch (IOException ex) { 229 CLog.logAndDisplay( 230 LogLevel.ERROR, 231 String.format("Unable to get destination file %s: Error: %s.", 232 destPath, ex.getMessage())); 233 return null; 234 } 235 return destinationFile; 236 } 237 238 /** Create directory for results */ createResultDir(IBuildInfo buildInfo, File destination, boolean isDestFile)239 private boolean createResultDir(IBuildInfo buildInfo, File destination, boolean isDestFile) { 240 File resultDir = destination; 241 if (isDestFile) { 242 // if filepath, get the parent for creating the directory 243 resultDir = destination.getParentFile(); 244 } 245 if (!resultDir.exists() && !resultDir.mkdirs()) { 246 CLog.logAndDisplay( 247 LogLevel.ERROR, 248 String.format("Unable to create %s directory", resultDir.getAbsolutePath())); 249 return false; 250 } 251 if (!resultDir.isDirectory()) { 252 CLog.logAndDisplay( 253 LogLevel.ERROR, 254 String.format("%s is not a directory", resultDir.getAbsolutePath())); 255 return false; 256 } 257 return true; 258 } 259 260 /** Pull files from the device using File Path */ pullFilesUsingFilePaths(ITestDevice device, IBuildInfo buildInfo, Map<String, String> filePathMap)261 private void pullFilesUsingFilePaths(ITestDevice device, IBuildInfo buildInfo, 262 Map<String, String> filePathMap) throws DeviceNotAvailableException { 263 if (filePathMap.isEmpty()) { 264 // No File Paths provided 265 return; 266 } 267 268 CLog.logAndDisplay( 269 LogLevel.INFO, 270 "Started pulling file using file path."); 271 272 // Iterate over given Paths 273 for (Map.Entry<String, String> entry: filePathMap.entrySet()) { 274 // Pull file using File Path 275 pullFileUsingFilePath(device, buildInfo, entry.getKey(), entry.getValue()); 276 } 277 278 CLog.logAndDisplay( 279 LogLevel.INFO, 280 "Completed pulling file using file path."); 281 } 282 283 /** Pull file using File Path */ pullFileUsingFilePath(ITestDevice device, IBuildInfo buildInfo, String filePath, String destPath)284 private void pullFileUsingFilePath(ITestDevice device, IBuildInfo buildInfo, String filePath, 285 String destPath) throws DeviceNotAvailableException { 286 // Validate Source and Destination File Path 287 if (Strings.isNullOrEmpty(filePath) || Strings.isNullOrEmpty(destPath)) { 288 CLog.logAndDisplay( 289 LogLevel.ERROR, 290 String.format("Either Src or Dest Path is invalid. Source: %s, Destination: %s", 291 filePath, destPath)); 292 return; 293 } 294 295 CLog.logAndDisplay( 296 LogLevel.INFO, 297 String.format("Started pulling file. Source File: %s, Destination File: %s", 298 filePath, destPath)); 299 300 // Check if file exist on the device 301 if (!device.doesFileExist(filePath)) { 302 CLog.logAndDisplay( 303 LogLevel.ERROR, 304 String.format("File %s does not exist on the device.", 305 filePath)); 306 return; 307 } 308 309 // Get Destination File using filepath 310 File destinationFile = getDestinationFile(buildInfo, destPath); 311 312 if (destinationFile == null) { 313 CLog.logAndDisplay( 314 LogLevel.ERROR, 315 String.format("Unable to get destination file path for %s.", 316 destPath)); 317 return; 318 } 319 320 // Create the result directory if it does not exist 321 // isDestFile=true : Since destination is a file 322 if (!createResultDir(buildInfo, destinationFile, true /* isDestFile */)) { 323 CLog.logAndDisplay( 324 LogLevel.ERROR, 325 String.format("Unable to create results directory %s.", 326 destPath)); 327 return; 328 } 329 330 // Pull File 331 boolean isSuccess = device.pullFile(filePath, destinationFile); 332 333 if (!isSuccess) { 334 CLog.logAndDisplay( 335 LogLevel.ERROR, 336 String.format("Failed to pull the file. Source File: %s, Destination File: %s", 337 filePath, destPath)); 338 return; 339 } 340 341 CLog.logAndDisplay( 342 LogLevel.INFO, 343 String.format("Completed pulling file. Source File: %s, Destination File: %s", 344 filePath, destPath)); 345 } 346 347 /** Pull directories from the device using File Path */ pullDirUsingFilePaths(ITestDevice device, IBuildInfo buildInfo, Map<String, String> dirPathMap)348 private void pullDirUsingFilePaths(ITestDevice device, IBuildInfo buildInfo, 349 Map<String, String> dirPathMap) throws DeviceNotAvailableException { 350 if (dirPathMap.isEmpty()) { 351 // No Dir Paths provided 352 return; 353 } 354 355 CLog.logAndDisplay( 356 LogLevel.INFO, 357 "Started pulling directory using file path."); 358 359 // Iterate over given Paths 360 for (Map.Entry<String, String> entry: dirPathMap.entrySet()) { 361 // Pull directory using Dir Path 362 pullDirectoryUsingDirPath(device, buildInfo, entry.getKey(), entry.getValue()); 363 } 364 365 CLog.logAndDisplay( 366 LogLevel.INFO, 367 "Completed pulling directory using file path."); 368 } 369 370 /** Pull Dir using Dir Path */ pullDirectoryUsingDirPath(ITestDevice device, IBuildInfo buildInfo, String dirPath, String destPath)371 private void pullDirectoryUsingDirPath(ITestDevice device, IBuildInfo buildInfo, String dirPath, 372 String destPath) throws DeviceNotAvailableException { 373 // Validate Source and Destination File Path 374 if (Strings.isNullOrEmpty(dirPath) || Strings.isNullOrEmpty(destPath)) { 375 CLog.logAndDisplay( 376 LogLevel.ERROR, 377 String.format("Either Src or Dest Path is invalid. Source: %s, Destination: %s", 378 dirPath, destPath)); 379 return; 380 } 381 382 CLog.logAndDisplay( 383 LogLevel.INFO, 384 String.format("Started pulling directory. Source File: %s, Destination File: %s", 385 dirPath, destPath)); 386 387 // Check if directory exist on the device 388 if (!device.doesFileExist(dirPath)) { 389 CLog.logAndDisplay( 390 LogLevel.ERROR, 391 String.format("File %s does not exist on the device.", 392 dirPath)); 393 return; 394 } 395 396 // Get Destination Dir using filepath 397 File destinationDir = getDestinationFile(buildInfo, destPath); 398 399 if (destinationDir == null) { 400 CLog.logAndDisplay( 401 LogLevel.ERROR, 402 String.format("Unable to get destination file path for %s.", 403 destPath)); 404 return; 405 } 406 407 // Create the result directory if it does not exist 408 // isDestFile=false : Since destination is a directory 409 if (!createResultDir(buildInfo, destinationDir, false /* isDestFile */)) { 410 CLog.logAndDisplay( 411 LogLevel.ERROR, 412 String.format("Unable to create results directory %s.", 413 destPath)); 414 return; 415 } 416 417 // Pull Dir 418 boolean isSuccess = device.pullDir(dirPath, destinationDir); 419 420 if (!isSuccess) { 421 CLog.logAndDisplay( 422 LogLevel.ERROR, 423 String.format("Failed to pull directory. Source Dir: %s, Destination File: %s", 424 dirPath, destPath)); 425 return; 426 } 427 428 CLog.logAndDisplay( 429 LogLevel.INFO, 430 String.format("Completed pulling directory. Source File: %s, Destination File: %s", 431 dirPath, destPath)); 432 } 433 } 434