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.annotations.VisibleForTesting; 19 import com.android.tradefed.build.BuildRetrievalError; 20 import com.android.tradefed.build.DeviceBuildInfo; 21 import com.android.tradefed.build.IBuildInfo; 22 import com.android.tradefed.build.IBuildInfo.BuildInfoProperties; 23 import com.android.tradefed.build.IBuildProvider; 24 import com.android.tradefed.build.IDeviceBuildInfo; 25 import com.android.tradefed.build.IDeviceBuildProvider; 26 import com.android.tradefed.config.Option; 27 import com.android.tradefed.config.Option.Importance; 28 import com.android.tradefed.config.OptionClass; 29 import com.android.tradefed.device.DeviceNotAvailableException; 30 import com.android.tradefed.device.ITestDevice; 31 import com.android.tradefed.invoker.ExecutionFiles; 32 import com.android.tradefed.invoker.ExecutionFiles.FilesKey; 33 import com.android.tradefed.invoker.IInvocationContext; 34 import com.android.tradefed.invoker.logger.CurrentInvocation; 35 import com.android.tradefed.testtype.IInvocationContextReceiver; 36 import com.android.tradefed.testtype.suite.TestSuiteInfo; 37 import com.android.tradefed.util.FileUtil; 38 import com.android.tradefed.util.VersionParser; 39 40 import java.io.File; 41 import java.io.IOException; 42 import java.text.SimpleDateFormat; 43 import java.util.ArrayList; 44 import java.util.Date; 45 import java.util.HashMap; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.regex.Pattern; 49 /** 50 * A simple {@link IBuildProvider} that uses a pre-existing Compatibility install. 51 */ 52 @OptionClass(alias="compatibility-build-provider") 53 public class CompatibilityBuildProvider implements IDeviceBuildProvider, IInvocationContextReceiver { 54 55 private static final Pattern RELEASE_BUILD = Pattern.compile("^[A-Z]{3}\\d{2}[A-Z]{0,1}$"); 56 private static final String ROOT_DIR = "ROOT_DIR"; 57 private static final String SUITE_BUILD = "SUITE_BUILD"; 58 private static final String SUITE_NAME = "SUITE_NAME"; 59 private static final String SUITE_FULL_NAME = "SUITE_FULL_NAME"; 60 private static final String SUITE_VERSION = "SUITE_VERSION"; 61 private static final String SUITE_PLAN = "SUITE_PLAN"; 62 private static final String RESULT_DIR = "RESULT_DIR"; 63 private static final String START_TIME_MS = "START_TIME_MS"; 64 public static final String DYNAMIC_CONFIG_OVERRIDE_URL = "DYNAMIC_CONFIG_OVERRIDE_URL"; 65 66 /* API Key for compatibility test project, used for dynamic configuration */ 67 private static final String API_KEY = "AIzaSyAbwX5JRlmsLeygY2WWihpIJPXFLueOQ3U"; 68 69 @Option(name="branch", description="build branch name to supply.") 70 private String mBranch = null; 71 72 @Option(name = "build-id", 73 description = 74 "build version number to supply. Override the default cts version number.") 75 private String mBuildId = null; 76 77 @Option(name="build-flavor", description="build flavor name to supply.") 78 private String mBuildFlavor = null; 79 80 @Option( 81 name = "build-flavor-prefix", 82 description = "allow for a prefix to be inserted into build flavor." 83 ) 84 private String mBuildFlavorPrefix = null; 85 86 @Option(name="build-target", description="build target name to supply.") 87 private String mBuildTarget = null; 88 89 @Option(name="build-attribute", description="build attributes to supply.") 90 private Map<String, String> mBuildAttributes = new HashMap<String,String>(); 91 92 @Option(name="use-device-build-info", description="Bootstrap build info from device") 93 private boolean mUseDeviceBuildInfo = false; 94 95 @Option(name = "dynamic-config-url", 96 description = "Specify the url for override config") 97 private String mURL = "https://androidpartner.googleapis.com/v1/dynamicconfig/" 98 + "suites/{suite-name}/modules/{module}/version/{version}?key=" + API_KEY; 99 100 @Option(name = "url-suite-name-override", 101 description = "Override the name that should used to replace the {suite-name} " 102 + "pattern in the dynamic-config-url.") 103 private String mUrlSuiteNameOverride = null; 104 105 @Option(name = "plan", 106 description = "the test suite plan to run, such as \"everything\" or \"cts\"", 107 importance = Importance.ALWAYS) 108 private String mSuitePlan; 109 110 private String mTestTag; 111 private File mArtificialRootDir; 112 113 /** 114 * Util method to inject build attributes into supplied {@link IBuildInfo} 115 * @param buildInfo 116 */ injectBuildAttributes(IBuildInfo buildInfo)117 private void injectBuildAttributes(IBuildInfo buildInfo) { 118 for (Map.Entry<String, String> entry : mBuildAttributes.entrySet()) { 119 buildInfo.addBuildAttribute(entry.getKey(), entry.getValue()); 120 } 121 if (mTestTag != null) { 122 buildInfo.setTestTag(mTestTag); 123 } 124 } 125 126 /** 127 * {@inheritDoc} 128 */ 129 @Override setInvocationContext(IInvocationContext invocationContext)130 public void setInvocationContext(IInvocationContext invocationContext) { 131 mTestTag = invocationContext.getTestTag(); 132 } 133 134 /** 135 * {@inheritDoc} 136 */ 137 @Override getBuild()138 public IBuildInfo getBuild() throws BuildRetrievalError { 139 // Create a blank BuildInfo which will get populated later. 140 String version = null; 141 if (mBuildId != null) { 142 version = mBuildId; 143 } else { 144 version = getSuiteInfoBuildNumber(); 145 if (version == null) { 146 version = IBuildInfo.UNKNOWN_BUILD_ID; 147 } 148 } 149 IBuildInfo ctsBuild = new DeviceBuildInfo(version, mBuildTarget); 150 if (mBranch != null) { 151 ctsBuild.setBuildBranch(mBranch); 152 } 153 if (mBuildFlavor != null) { 154 String buildFlavor = mBuildFlavor; 155 if (mBuildFlavorPrefix != null) { 156 buildFlavor = mBuildFlavorPrefix + buildFlavor; 157 } 158 ctsBuild.setBuildFlavor(buildFlavor); 159 } 160 injectBuildAttributes(ctsBuild); 161 addCompatibilitySuiteInfo(ctsBuild); 162 return ctsBuild; 163 } 164 165 /** 166 * {@inheritDoc} 167 */ 168 @Override getBuild(ITestDevice device)169 public IBuildInfo getBuild(ITestDevice device) 170 throws BuildRetrievalError, DeviceNotAvailableException { 171 if (!mUseDeviceBuildInfo) { 172 // return a regular build info without extracting device attributes into standard 173 // build info fields 174 return getBuild(); 175 } else { 176 if (mBuildId == null) { 177 mBuildId = device.getBuildId(); 178 } 179 String buildFlavor = mBuildFlavor; 180 if (buildFlavor == null) { 181 buildFlavor = device.getBuildFlavor(); 182 } 183 if (mBuildFlavorPrefix != null) { 184 buildFlavor = mBuildFlavorPrefix + buildFlavor; 185 } 186 if (mBuildTarget == null) { 187 String name = device.getProperty("ro.product.name"); 188 String variant = device.getProperty("ro.build.type"); 189 mBuildTarget = name + "-" + variant; 190 } 191 IBuildInfo info = new DeviceBuildInfo(mBuildId, mBuildTarget); 192 if (mBranch == null) { 193 // if branch is not specified via param, make a pseudo branch name based on platform 194 // version and product info from device 195 mBranch = String.format("%s-%s-%s-%s", 196 device.getProperty("ro.product.brand"), 197 device.getProperty("ro.product.name"), 198 device.getProductVariant(), 199 device.getProperty("ro.build.version.release")); 200 } 201 info.setBuildBranch(mBranch); 202 info.setBuildFlavor(buildFlavor); 203 String buildAlias = device.getBuildAlias(); 204 if (RELEASE_BUILD.matcher(buildAlias).matches()) { 205 info.addBuildAttribute("build_alias", buildAlias); 206 } 207 injectBuildAttributes(info); 208 addCompatibilitySuiteInfo(info); 209 return info; 210 } 211 } 212 213 /** 214 * {@inheritDoc} 215 */ 216 @Override cleanUp(IBuildInfo info)217 public void cleanUp(IBuildInfo info) { 218 // Everything should have been copied properly to result folder, we clean up 219 if (info instanceof IDeviceBuildInfo) { 220 List<File> doNotDelete = new ArrayList<>(); 221 // Clean up everything except the tests dir 222 doNotDelete.add(((IDeviceBuildInfo) info).getTestsDir()); 223 // Skip deleting dynamic config files 224 CompatibilityBuildHelper helper = new CompatibilityBuildHelper(info); 225 doNotDelete.addAll(helper.getDynamicConfigFiles().values()); 226 // Still mark all those files as delete on exit to be deleted eventually 227 helper.getDynamicConfigFiles().values().forEach(f -> f.deleteOnExit()); 228 info.cleanUp(doNotDelete); 229 } else { 230 info.cleanUp(); 231 } 232 FileUtil.recursiveDelete(mArtificialRootDir); 233 } 234 addCompatibilitySuiteInfo(IBuildInfo info)235 private void addCompatibilitySuiteInfo(IBuildInfo info) { 236 long startTimeMs = System.currentTimeMillis(); 237 info.addBuildAttribute(SUITE_BUILD, getSuiteInfoBuildNumber()); 238 info.addBuildAttribute(SUITE_NAME, getSuiteInfoName()); 239 info.addBuildAttribute(SUITE_FULL_NAME, getSuiteInfoFullname()); 240 info.addBuildAttribute(SUITE_VERSION, getSuiteInfoVersion()); 241 info.addBuildAttribute(SUITE_PLAN, mSuitePlan); 242 info.addBuildAttribute(START_TIME_MS, Long.toString(startTimeMs)); 243 info.addBuildAttribute(RESULT_DIR, getDirSuffix(startTimeMs)); 244 String rootDirPath = getRootDirPath(); 245 if (rootDirPath == null || rootDirPath.trim().equals("")) { 246 throw new IllegalArgumentException( 247 String.format("Missing install path property %s_ROOT", getSuiteInfoName())); 248 } 249 File rootDir = new File(rootDirPath); 250 if (!rootDir.exists()) { 251 throw new IllegalArgumentException( 252 String.format("Root directory doesn't exist %s", rootDir.getAbsolutePath())); 253 } 254 info.addBuildAttribute(ROOT_DIR, rootDir.getAbsolutePath()); 255 // For DeviceBuildInfo we populate the testsDir folder of the build info. 256 if (info instanceof IDeviceBuildInfo) { 257 if (mArtificialRootDir == null) { 258 // If the real CTS directory is used, do not copy it again. 259 info.setProperties( 260 BuildInfoProperties.DO_NOT_LINK_TESTS_DIR, 261 BuildInfoProperties.DO_NOT_COPY_ON_SHARDING); 262 } else { 263 info.setProperties(BuildInfoProperties.DO_NOT_COPY_ON_SHARDING); 264 } 265 File testDir = new File(rootDir, String.format("android-%s/testcases/", 266 getSuiteInfoName().toLowerCase())); 267 ((IDeviceBuildInfo) info).setTestsDir(testDir, "0"); 268 if (getInvocationFiles() != null) { 269 getInvocationFiles() 270 .put( 271 FilesKey.TESTS_DIRECTORY, 272 testDir, 273 /** Should not delete */ 274 mArtificialRootDir == null); 275 } 276 } 277 if (mURL != null && !mURL.isEmpty()) { 278 String suiteName = mUrlSuiteNameOverride; 279 if (suiteName == null) { 280 suiteName = getSuiteInfoName(); 281 } 282 info.addBuildAttribute(DYNAMIC_CONFIG_OVERRIDE_URL, 283 mURL.replace("{suite-name}", suiteName)); 284 } 285 } 286 287 /** 288 * Returns the CTS_ROOT variable that the harness was started with. 289 */ 290 @VisibleForTesting getRootDirPath()291 String getRootDirPath() { 292 // Replace - in the suite name with _, as environment variable can't have - in name. 293 String varName = String.format("%s_ROOT", getSuiteInfoName().replace('-', '_')); 294 String rootDirVariable = System.getProperty(varName); 295 if (rootDirVariable != null) { 296 return rootDirVariable; 297 } 298 // Create an artificial root dir, we are most likely running from Tradefed directly. 299 try { 300 mArtificialRootDir = FileUtil.createTempDir( 301 String.format("%s-root-dir", getSuiteInfoName())); 302 new File(mArtificialRootDir, String.format("android-%s/testcases", 303 getSuiteInfoName().toLowerCase())).mkdirs(); 304 return mArtificialRootDir.getAbsolutePath(); 305 } catch (IOException e) { 306 throw new RuntimeException( 307 String.format("%s was not set, and couldn't create an artificial one.", 308 varName)); 309 } 310 } 311 312 /** 313 * Return the SuiteInfo name generated at build time. Exposed for testing. 314 */ getSuiteInfoName()315 protected String getSuiteInfoName() { 316 return TestSuiteInfo.getInstance().getName(); 317 } 318 319 /** 320 * Return the SuiteInfo build number generated at build time. Exposed for testing. 321 */ getSuiteInfoBuildNumber()322 protected String getSuiteInfoBuildNumber() { 323 String buildNumber = TestSuiteInfo.getInstance().getBuildNumber(); 324 String versionFile = VersionParser.fetchVersion(); 325 if (versionFile != null) { 326 buildNumber = versionFile; 327 } 328 return buildNumber; 329 } 330 331 @VisibleForTesting getInvocationFiles()332 ExecutionFiles getInvocationFiles() { 333 return CurrentInvocation.getInvocationFiles(); 334 } 335 336 /** 337 * Return the SuiteInfo fullname generated at build time. Exposed for testing. 338 */ getSuiteInfoFullname()339 protected String getSuiteInfoFullname() { 340 return TestSuiteInfo.getInstance().getFullName(); 341 } 342 343 /** 344 * Return the SuiteInfo version generated at build time. Exposed for testing. 345 */ getSuiteInfoVersion()346 protected String getSuiteInfoVersion() { 347 return TestSuiteInfo.getInstance().getVersion(); 348 } 349 350 /** 351 * @return a {@link String} to use for directory suffixes created from the given time. 352 */ getDirSuffix(long millis)353 private String getDirSuffix(long millis) { 354 return new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss").format(new Date(millis)); 355 } 356 } 357