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.FilesKey; 32 import com.android.tradefed.invoker.ExecutionFiles; 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 info.cleanUp(doNotDelete); 224 } else { 225 info.cleanUp(); 226 } 227 FileUtil.recursiveDelete(mArtificialRootDir); 228 } 229 addCompatibilitySuiteInfo(IBuildInfo info)230 private void addCompatibilitySuiteInfo(IBuildInfo info) { 231 long startTimeMs = System.currentTimeMillis(); 232 info.addBuildAttribute(SUITE_BUILD, getSuiteInfoBuildNumber()); 233 info.addBuildAttribute(SUITE_NAME, getSuiteInfoName()); 234 info.addBuildAttribute(SUITE_FULL_NAME, getSuiteInfoFullname()); 235 info.addBuildAttribute(SUITE_VERSION, getSuiteInfoVersion()); 236 info.addBuildAttribute(SUITE_PLAN, mSuitePlan); 237 info.addBuildAttribute(START_TIME_MS, Long.toString(startTimeMs)); 238 info.addBuildAttribute(RESULT_DIR, getDirSuffix(startTimeMs)); 239 String rootDirPath = getRootDirPath(); 240 if (rootDirPath == null || rootDirPath.trim().equals("")) { 241 throw new IllegalArgumentException( 242 String.format("Missing install path property %s_ROOT", getSuiteInfoName())); 243 } 244 File rootDir = new File(rootDirPath); 245 if (!rootDir.exists()) { 246 throw new IllegalArgumentException( 247 String.format("Root directory doesn't exist %s", rootDir.getAbsolutePath())); 248 } 249 info.addBuildAttribute(ROOT_DIR, rootDir.getAbsolutePath()); 250 // For DeviceBuildInfo we populate the testsDir folder of the build info. 251 if (info instanceof IDeviceBuildInfo) { 252 if (mArtificialRootDir == null) { 253 // If the real CTS directory is used, do not copy it again. 254 info.setProperties( 255 BuildInfoProperties.DO_NOT_LINK_TESTS_DIR, 256 BuildInfoProperties.DO_NOT_COPY_ON_SHARDING); 257 } else { 258 info.setProperties(BuildInfoProperties.DO_NOT_COPY_ON_SHARDING); 259 } 260 File testDir = new File(rootDir, String.format("android-%s/testcases/", 261 getSuiteInfoName().toLowerCase())); 262 ((IDeviceBuildInfo) info).setTestsDir(testDir, "0"); 263 if (getInvocationFiles() != null) { 264 getInvocationFiles() 265 .put( 266 FilesKey.TESTS_DIRECTORY, 267 testDir, 268 /** Should not delete */ 269 mArtificialRootDir == null); 270 } 271 } 272 if (mURL != null && !mURL.isEmpty()) { 273 String suiteName = mUrlSuiteNameOverride; 274 if (suiteName == null) { 275 suiteName = getSuiteInfoName(); 276 } 277 info.addBuildAttribute(DYNAMIC_CONFIG_OVERRIDE_URL, 278 mURL.replace("{suite-name}", suiteName)); 279 } 280 } 281 282 /** 283 * Returns the CTS_ROOT variable that the harness was started with. 284 */ 285 @VisibleForTesting getRootDirPath()286 String getRootDirPath() { 287 // Replace - in the suite name with _, as environment variable can't have - in name. 288 String varName = String.format("%s_ROOT", getSuiteInfoName().replace('-', '_')); 289 String rootDirVariable = System.getProperty(varName); 290 if (rootDirVariable != null) { 291 return rootDirVariable; 292 } 293 // Create an artificial root dir, we are most likely running from Tradefed directly. 294 try { 295 mArtificialRootDir = FileUtil.createTempDir( 296 String.format("%s-root-dir", getSuiteInfoName())); 297 new File(mArtificialRootDir, String.format("android-%s/testcases", 298 getSuiteInfoName().toLowerCase())).mkdirs(); 299 return mArtificialRootDir.getAbsolutePath(); 300 } catch (IOException e) { 301 throw new RuntimeException( 302 String.format("%s was not set, and couldn't create an artificial one.", 303 varName)); 304 } 305 } 306 307 /** 308 * Return the SuiteInfo name generated at build time. Exposed for testing. 309 */ getSuiteInfoName()310 protected String getSuiteInfoName() { 311 return TestSuiteInfo.getInstance().getName(); 312 } 313 314 /** 315 * Return the SuiteInfo build number generated at build time. Exposed for testing. 316 */ getSuiteInfoBuildNumber()317 protected String getSuiteInfoBuildNumber() { 318 String buildNumber = TestSuiteInfo.getInstance().getBuildNumber(); 319 String versionFile = VersionParser.fetchVersion(); 320 if (versionFile != null) { 321 buildNumber = versionFile; 322 } 323 return buildNumber; 324 } 325 326 @VisibleForTesting getInvocationFiles()327 ExecutionFiles getInvocationFiles() { 328 return CurrentInvocation.getInvocationFiles(); 329 } 330 331 /** 332 * Return the SuiteInfo fullname generated at build time. Exposed for testing. 333 */ getSuiteInfoFullname()334 protected String getSuiteInfoFullname() { 335 return TestSuiteInfo.getInstance().getFullName(); 336 } 337 338 /** 339 * Return the SuiteInfo version generated at build time. Exposed for testing. 340 */ getSuiteInfoVersion()341 protected String getSuiteInfoVersion() { 342 return TestSuiteInfo.getInstance().getVersion(); 343 } 344 345 /** 346 * @return a {@link String} to use for directory suffixes created from the given time. 347 */ getDirSuffix(long millis)348 private String getDirSuffix(long millis) { 349 return new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss").format(new Date(millis)); 350 } 351 } 352