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