1 /* 2 * Copyright (C) 2015 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.android.compatibility.common.tradefed.build; 17 18 import com.android.tradefed.build.IBuildInfo; 19 import com.android.tradefed.build.IDeviceBuildInfo; 20 import com.android.tradefed.build.IFolderBuildInfo; 21 import com.android.tradefed.build.VersionedFile; 22 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 23 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 24 import com.android.tradefed.log.LogUtil.CLog; 25 import com.android.tradefed.testtype.IAbi; 26 import com.android.tradefed.util.FileUtil; 27 import com.android.tradefed.util.SearchArtifactUtil; 28 29 import java.io.File; 30 import java.io.FileNotFoundException; 31 import java.io.IOException; 32 import java.text.SimpleDateFormat; 33 import java.util.Date; 34 import java.util.HashMap; 35 import java.util.Map; 36 37 /** 38 * A simple helper that stores and retrieves information from a {@link IBuildInfo}. 39 */ 40 public class CompatibilityBuildHelper { 41 42 public static final String MODULE_IDS = "MODULE_IDS"; 43 public static final String ROOT_DIR = "ROOT_DIR"; 44 public static final String SUITE_BUILD = "SUITE_BUILD"; 45 public static final String SUITE_NAME = "SUITE_NAME"; 46 public static final String SUITE_FULL_NAME = "SUITE_FULL_NAME"; 47 public static final String SUITE_VERSION = "SUITE_VERSION"; 48 public static final String SUITE_PLAN = "SUITE_PLAN"; 49 public static final String START_TIME_MS = "START_TIME_MS"; 50 public static final String COMMAND_LINE_ARGS = "command_line_args"; 51 52 private static final String DYNAMIC_CONFIG_OVERRIDE_URL = "DYNAMIC_CONFIG_OVERRIDE_URL"; 53 private static final String BUSINESS_LOGIC_HOST_FILE = "BUSINESS_LOGIC_HOST_FILE"; 54 private static final String RETRY_COMMAND_LINE_ARGS = "retry_command_line_args"; 55 56 private static final String CONFIG_PATH_PREFIX = "DYNAMIC_CONFIG_FILE:"; 57 58 private final IBuildInfo mBuildInfo; 59 60 /** 61 * Creates a {@link CompatibilityBuildHelper} wrapping the given {@link IBuildInfo}. 62 */ CompatibilityBuildHelper(IBuildInfo buildInfo)63 public CompatibilityBuildHelper(IBuildInfo buildInfo) { 64 mBuildInfo = buildInfo; 65 } 66 getBuildInfo()67 public IBuildInfo getBuildInfo() { 68 return mBuildInfo; 69 } 70 setRetryCommandLineArgs(String commandLineArgs)71 public void setRetryCommandLineArgs(String commandLineArgs) { 72 mBuildInfo.addBuildAttribute(RETRY_COMMAND_LINE_ARGS, commandLineArgs); 73 } 74 getCommandLineArgs()75 public String getCommandLineArgs() { 76 if (mBuildInfo.getBuildAttributes().containsKey(RETRY_COMMAND_LINE_ARGS)) { 77 return mBuildInfo.getBuildAttributes().get(RETRY_COMMAND_LINE_ARGS); 78 } else { 79 // NOTE: this is a temporary workaround set in TestInvocation#invoke in tradefed. 80 // This will be moved to a separate method in a new invocation metadata class. 81 return mBuildInfo.getBuildAttributes().get(COMMAND_LINE_ARGS); 82 } 83 } 84 getRecentCommandLineArgs()85 public String getRecentCommandLineArgs() { 86 return mBuildInfo.getBuildAttributes().get(COMMAND_LINE_ARGS); 87 } 88 getSuiteBuild()89 public String getSuiteBuild() { 90 return mBuildInfo.getBuildAttributes().get(SUITE_BUILD); 91 } 92 getSuiteName()93 public String getSuiteName() { 94 return mBuildInfo.getBuildAttributes().get(SUITE_NAME); 95 } 96 getSuiteFullName()97 public String getSuiteFullName() { 98 return mBuildInfo.getBuildAttributes().get(SUITE_FULL_NAME); 99 } 100 getSuiteVersion()101 public String getSuiteVersion() { 102 return mBuildInfo.getBuildAttributes().get(SUITE_VERSION); 103 } 104 getSuitePlan()105 public String getSuitePlan() { 106 return mBuildInfo.getBuildAttributes().get(SUITE_PLAN); 107 } 108 getDynamicConfigUrl()109 public String getDynamicConfigUrl() { 110 return mBuildInfo.getBuildAttributes().get(DYNAMIC_CONFIG_OVERRIDE_URL); 111 } 112 getStartTime()113 public long getStartTime() { 114 return Long.parseLong(mBuildInfo.getBuildAttributes().get(START_TIME_MS)); 115 } 116 addDynamicConfigFile(String moduleName, File configFile)117 public void addDynamicConfigFile(String moduleName, File configFile) { 118 // If invocation fails and ResultReporter never moves this file into the result, 119 // using setFile() ensures BuildInfo will delete upon cleanUp(). 120 mBuildInfo.setFile(configFile.getName(), configFile, 121 CONFIG_PATH_PREFIX + moduleName /* version */); 122 } 123 124 /** 125 * Set the business logic file for this invocation. 126 * 127 * @param hostFile The business logic host file. 128 */ setBusinessLogicHostFile(File hostFile)129 public void setBusinessLogicHostFile(File hostFile) { 130 setBusinessLogicHostFile(hostFile, null); 131 } 132 133 /** 134 * Set the business logic file with specific module id for this invocation. 135 * 136 * @param hostFile The business logic host file. 137 * @param moduleId The name of the moduleId. 138 */ setBusinessLogicHostFile(File hostFile, String moduleId)139 public void setBusinessLogicHostFile(File hostFile, String moduleId) { 140 String key = (moduleId == null) ? "" : moduleId; 141 mBuildInfo.setFile(BUSINESS_LOGIC_HOST_FILE + key, hostFile, 142 hostFile.getName()/* version */); 143 } 144 setModuleIds(String[] moduleIds)145 public void setModuleIds(String[] moduleIds) { 146 mBuildInfo.addBuildAttribute(MODULE_IDS, String.join(",", moduleIds)); 147 } 148 149 /** 150 * Returns the map of the dynamic config files downloaded. 151 */ getDynamicConfigFiles()152 public Map<String, File> getDynamicConfigFiles() { 153 Map<String, File> configMap = new HashMap<>(); 154 for (VersionedFile vFile : mBuildInfo.getFiles()) { 155 if (vFile.getVersion().startsWith(CONFIG_PATH_PREFIX)) { 156 configMap.put( 157 vFile.getVersion().substring(CONFIG_PATH_PREFIX.length()), 158 vFile.getFile()); 159 } 160 } 161 return configMap; 162 } 163 164 /** 165 * @return whether the business logic file has been set for this invocation. 166 */ hasBusinessLogicHostFile()167 public boolean hasBusinessLogicHostFile() { 168 return hasBusinessLogicHostFile(null); 169 } 170 171 /** 172 * Check whether the business logic file has been set with specific module id for this 173 * invocation. 174 * 175 * @param moduleId The name of the moduleId. 176 * @return True if the business logic file has been set. False otherwise. 177 */ hasBusinessLogicHostFile(String moduleId)178 public boolean hasBusinessLogicHostFile(String moduleId) { 179 String key = (moduleId == null) ? "" : moduleId; 180 return mBuildInfo.getFile(BUSINESS_LOGIC_HOST_FILE + key) != null; 181 } 182 183 /** 184 * @return a {@link File} representing the file containing business logic data for this 185 * invocation, or null if the business logic file has not been set. 186 */ getBusinessLogicHostFile()187 public File getBusinessLogicHostFile() { 188 return getBusinessLogicHostFile(null); 189 } 190 191 /** 192 * Get the file containing business logic data with specific module id for this invocation. 193 * 194 * @param moduleId The name of the moduleId. 195 * @return a {@link File} representing the file containing business logic data with 196 * specific module id for this invocation , or null if the business logic file has not been set. 197 */ getBusinessLogicHostFile(String moduleId)198 public File getBusinessLogicHostFile(String moduleId) { 199 String key = (moduleId == null) ? "" : moduleId; 200 return mBuildInfo.getFile(BUSINESS_LOGIC_HOST_FILE + key); 201 } 202 203 /** 204 * @return a {@link File} representing the directory holding the Compatibility installation 205 * @throws FileNotFoundException if the directory does not exist 206 */ getRootDir()207 public File getRootDir() throws FileNotFoundException { 208 File dir = null; 209 if (mBuildInfo instanceof IFolderBuildInfo) { 210 dir = ((IFolderBuildInfo) mBuildInfo).getRootDir(); 211 } 212 if (dir == null || !dir.exists()) { 213 String rootDir = mBuildInfo.getBuildAttributes().get(ROOT_DIR); 214 if (rootDir != null) { 215 dir = new File(rootDir); 216 } 217 } 218 if (dir == null || !dir.exists()) { 219 throw new FileNotFoundException( 220 String.format("Compatibility root directory %s does not exist", dir)); 221 } 222 return dir; 223 } 224 225 /** 226 * @return a {@link File} representing the "android-<suite>" folder of the Compatibility 227 * installation 228 * @throws FileNotFoundException if the directory does not exist 229 */ getDir()230 public File getDir() throws FileNotFoundException { 231 File dir = 232 new File(getRootDir(), String.format("android-%s", getSuiteName().toLowerCase())); 233 if (!dir.exists()) { 234 throw new FileNotFoundException(String.format( 235 "Compatibility install folder %s does not exist", 236 dir.getAbsolutePath())); 237 } 238 return dir; 239 } 240 241 /** 242 * @return a {@link File} representing the results directory. 243 * @throws FileNotFoundException if the directory structure is not valid. 244 */ getResultsDir()245 public File getResultsDir() throws FileNotFoundException { 246 return new File(getDir(), "results"); 247 } 248 249 /** 250 * @return a {@link File} representing the result directory of the current invocation. 251 * @throws FileNotFoundException if the directory structure is not valid. 252 */ getResultDir()253 public File getResultDir() throws FileNotFoundException { 254 return new File(getResultsDir(), 255 getDirSuffix(Long.parseLong(mBuildInfo.getBuildAttributes().get(START_TIME_MS)))); 256 } 257 258 /** 259 * @return a {@link File} representing the directory to store result logs. 260 * @throws FileNotFoundException if the directory structure is not valid. 261 */ getLogsDir()262 public File getLogsDir() throws FileNotFoundException { 263 return new File(getDir(), "logs"); 264 } 265 266 /** 267 * @return a {@link File} representing the log directory of the current invocation. 268 * @throws FileNotFoundException if the directory structure is not valid. 269 */ getInvocationLogDir()270 public File getInvocationLogDir() throws FileNotFoundException { 271 return new File( 272 getLogsDir(), 273 getDirSuffix(Long.parseLong(mBuildInfo.getBuildAttributes().get(START_TIME_MS)))); 274 } 275 276 /** 277 * @return a {@link File} representing the directory to store derivedplan files. 278 * @throws FileNotFoundException if the directory structure is not valid. 279 */ getSubPlansDir()280 public File getSubPlansDir() throws FileNotFoundException { 281 File subPlansDir = new File(getDir(), "subplans"); 282 if (!subPlansDir.exists()) { 283 subPlansDir.mkdirs(); 284 } 285 return subPlansDir; 286 } 287 288 /** 289 * @return a {@link File} representing the test modules directory. 290 * @throws FileNotFoundException if the directory structure is not valid. 291 */ getTestsDir()292 public File getTestsDir() throws FileNotFoundException { 293 // We have 2 options that can be the test modules dir (and we're going 294 // look for them in the following order): 295 // 1. ../android-*ts/testcases/ 296 // 2. The build info tests dir 297 // ANDROID_HOST_OUT and ANDROID_TARGET_OUT are already linked 298 // by tradefed to the tests dir when they exists so there is 299 // no need to explicitly search them. 300 301 File testsDir = null; 302 try { 303 testsDir = new File(getDir(), "testcases"); 304 } catch (FileNotFoundException | NullPointerException e) { 305 // Ok, no root dir for us to get, moving on to the next option. 306 testsDir = null; 307 } 308 309 if (testsDir == null) { 310 if (mBuildInfo instanceof IDeviceBuildInfo) { 311 testsDir = ((IDeviceBuildInfo) mBuildInfo).getTestsDir(); 312 } 313 } 314 315 // This just means we have no signs of where to check for the test dir. 316 if (testsDir == null) { 317 throw new FileNotFoundException( 318 String.format("No Compatibility tests folder set, did you run lunch?")); 319 } 320 321 if (!testsDir.exists()) { 322 throw new FileNotFoundException(String.format( 323 "Compatibility tests folder %s does not exist", 324 testsDir.getAbsolutePath())); 325 } 326 327 return testsDir; 328 } 329 330 /** 331 * @return a {@link File} representing the test file in the test modules directory. 332 * @throws FileNotFoundException if the test file cannot be found 333 */ getTestFile(String filename)334 public File getTestFile(String filename) throws FileNotFoundException { 335 return getTestFile(filename, null); 336 } 337 338 /** 339 * @return a {@link File} representing the test file in the test modules directory. 340 * @throws FileNotFoundException if the test file cannot be found 341 */ getTestFile(String filename, IAbi abi)342 public File getTestFile(String filename, IAbi abi) throws FileNotFoundException { 343 File testFile = null; 344 try { 345 testFile = SearchArtifactUtil.searchFile(filename, false, abi); 346 } catch (Exception e) { 347 // TODO: handle error when migration is complete. 348 CLog.e(e); 349 } 350 if (testFile != null && testFile.exists()) { 351 return testFile; 352 } else { 353 // Silently report not found and fall back to old logic. 354 InvocationMetricLogger.addInvocationMetrics( 355 InvocationMetricKey.SEARCH_ARTIFACT_FAILURE_COUNT, 1); 356 } 357 358 File testsDir = getTestsDir(); 359 360 // The file may be in a subdirectory so do a more thorough search 361 // if it did not exist. 362 try { 363 testFile = FileUtil.findFile(filename, abi, testsDir); 364 if (testFile != null) { 365 return testFile; 366 } 367 368 // TODO(b/138416078): Once build dependency can be fixed and test required APKs are all 369 // under the test module directory, we can remove this fallback approach to do 370 // individual download from remote artifact. 371 // Try to stage the files from remote zip files. 372 testFile = mBuildInfo.stageRemoteFile(filename, testsDir); 373 if (testFile != null) { 374 // Search again to match the given abi. 375 testFile = FileUtil.findFile(filename, abi, testsDir); 376 if (testFile != null) { 377 return testFile; 378 } 379 } 380 } catch (IOException e) { 381 // if old logic fails too, do not report search artifact failure 382 InvocationMetricLogger.addInvocationMetrics( 383 InvocationMetricKey.SEARCH_ARTIFACT_FAILURE_COUNT, -1); 384 throw new FileNotFoundException( 385 String.format( 386 "Failure in finding compatibility test file %s due to %s", 387 filename, e)); 388 } 389 // if old logic fails too, do not report search artifact failure 390 InvocationMetricLogger.addInvocationMetrics( 391 InvocationMetricKey.SEARCH_ARTIFACT_FAILURE_COUNT, -1); 392 throw new FileNotFoundException(String.format( 393 "Compatibility test file %s does not exist", filename)); 394 } 395 396 /** 397 * @return a {@link File} in the resultDir for logging invocation failures 398 */ getInvocationFailureFile()399 public File getInvocationFailureFile() throws FileNotFoundException { 400 return new File(getResultDir(), "invocation_failure.txt"); 401 } 402 403 /** 404 * @return a {@link File} in the resultDir for counting expected test runs 405 */ getTestRunsFile()406 public File getTestRunsFile() throws FileNotFoundException { 407 return new File(getResultDir(), "test_runs.txt"); 408 } 409 410 /** 411 * @return a {@link String} to use for directory suffixes created from the given time. 412 */ getDirSuffix(long millis)413 public static String getDirSuffix(long millis) { 414 return new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss").format(new Date(millis)); 415 } 416 } 417