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