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