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