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