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