• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.tradefed.testtype.suite;
17 
18 import com.android.tradefed.build.IDeviceBuildInfo;
19 import com.android.tradefed.config.ConfigurationDescriptor;
20 import com.android.tradefed.config.ConfigurationException;
21 import com.android.tradefed.config.ConfigurationFactory;
22 import com.android.tradefed.config.ConfigurationUtil;
23 import com.android.tradefed.config.IConfiguration;
24 import com.android.tradefed.config.IConfigurationFactory;
25 import com.android.tradefed.config.Option;
26 import com.android.tradefed.device.DeviceNotAvailableException;
27 import com.android.tradefed.log.LogUtil.CLog;
28 import com.android.tradefed.targetprep.ITargetPreparer;
29 import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
30 import com.android.tradefed.testtype.IAbi;
31 import com.android.tradefed.testtype.IAbiReceiver;
32 import com.android.tradefed.testtype.IRemoteTest;
33 import com.android.tradefed.util.DirectedGraph;
34 import com.android.tradefed.util.StreamUtil;
35 import com.android.tradefed.util.ZipUtil2;
36 
37 import org.apache.commons.compress.archivers.zip.ZipFile;
38 
39 import java.io.File;
40 import java.io.IOException;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collections;
44 import java.util.LinkedHashMap;
45 import java.util.List;
46 import java.util.Set;
47 
48 /**
49  * Implementation of {@link ITestSuite} which will load tests from TF jars res/config/suite/
50  * folder.
51  */
52 public class TfSuiteRunner extends ITestSuite {
53 
54     private static final String CONFIG_EXT = ".config";
55 
56     @Option(name = "run-suite-tag", description = "The tag that must be run.",
57             mandatory = true)
58     private String mSuiteTag = null;
59 
60     @Option(
61         name = "suite-config-prefix",
62         description = "Search only configs with given prefix for suite tags."
63     )
64     private String mSuitePrefix = null;
65 
66     @Option(
67         name = "additional-tests-zip",
68         description = "Path to a zip file containing additional tests to be loaded."
69     )
70     private String mAdditionalTestsZip = null;
71 
72     private DirectedGraph<String> mLoadedConfigGraph = null;
73 
74     /** {@inheritDoc} */
75     @Override
loadTests()76     public LinkedHashMap<String, IConfiguration> loadTests() {
77         mLoadedConfigGraph = new DirectedGraph<>();
78         return loadTests(null, mLoadedConfigGraph);
79     }
80 
81     /**
82      * Internal load configuration. Load configuration, expand sub suite runners and track cycle
83      * inclusion to prevent infinite recursion.
84      *
85      * @param parentConfig the name of the config being loaded.
86      * @param graph the directed graph tracking inclusion.
87      * @return a map of module name and the configuration associated.
88      */
loadTests( String parentConfig, DirectedGraph<String> graph)89     private LinkedHashMap<String, IConfiguration> loadTests(
90             String parentConfig, DirectedGraph<String> graph) {
91         LinkedHashMap <String, IConfiguration> configMap =
92                 new LinkedHashMap<String, IConfiguration>();
93         IConfigurationFactory configFactory = ConfigurationFactory.getInstance();
94         // TODO: Do a better job searching for configs.
95         // We do not load config from environment, they should be inside the testsDir of the build
96         // info.
97         List<String> configs = configFactory.getConfigList(mSuitePrefix, false);
98         Set<IAbi> abis = null;
99         if (getBuildInfo() instanceof IDeviceBuildInfo) {
100             IDeviceBuildInfo deviceBuildInfo = (IDeviceBuildInfo) getBuildInfo();
101             File testsDir = deviceBuildInfo.getTestsDir();
102             if (testsDir != null) {
103                 if (mAdditionalTestsZip != null) {
104                     CLog.d(
105                             "Extract general-tests.zip (%s) to tests directory.",
106                             mAdditionalTestsZip);
107                     ZipFile zip = null;
108                     try {
109                         zip = new ZipFile(mAdditionalTestsZip);
110                         ZipUtil2.extractZip(zip, testsDir);
111                     } catch (IOException e) {
112                         RuntimeException runtimeException =
113                                 new RuntimeException(
114                                         String.format(
115                                                 "IO error (%s) when unzipping general-tests.zip",
116                                                 e.toString()),
117                                         e);
118                         throw runtimeException;
119                     } finally {
120                         StreamUtil.close(zip);
121                     }
122                 }
123 
124                 CLog.d(
125                         "Loading extra test configs from the tests directory: %s",
126                         testsDir.getAbsolutePath());
127                 List<File> extraTestCasesDirs = Arrays.asList(testsDir);
128                 configs.addAll(
129                         ConfigurationUtil.getConfigNamesFromDirs(mSuitePrefix, extraTestCasesDirs));
130             }
131         }
132         // Sort configs to ensure they are always evaluated and added in the same order.
133         Collections.sort(configs);
134         for (String configName : configs) {
135             try {
136                 IConfiguration testConfig =
137                         configFactory.createConfigurationFromArgs(new String[]{configName});
138                 // Store the module dir path
139                 File moduleDir = new File(configName).getParentFile();
140                 if (moduleDir != null && moduleDir.exists()) {
141                     testConfig
142                             .getConfigurationDescription()
143                             .addMetadata(
144                                     ConfigurationDescriptor.MODULE_DIR_PATH_KEY,
145                                     moduleDir.getAbsolutePath());
146                 }
147                 if (testConfig.getConfigurationDescription().getSuiteTags().contains(mSuiteTag)) {
148                     // If this config supports running against different ABIs we need to queue up
149                     // multiple instances of this config.
150                     if (canRunMultipleAbis(testConfig)) {
151                         if (abis == null) {
152                             try {
153                                 abis = getAbis(getDevice());
154                             } catch (DeviceNotAvailableException e) {
155                                 throw new RuntimeException(e);
156                             }
157                         }
158                         for (IAbi abi : abis) {
159                             testConfig =
160                                     configFactory.createConfigurationFromArgs(
161                                             new String[] {configName});
162                             String configNameAbi = abi.getName() + " " + configName;
163                             for (IRemoteTest test : testConfig.getTests()) {
164                                 if (test instanceof IAbiReceiver) {
165                                     ((IAbiReceiver) test).setAbi(abi);
166                                 }
167                             }
168                             for (ITargetPreparer preparer : testConfig.getTargetPreparers()) {
169                                 if (preparer instanceof IAbiReceiver) {
170                                     ((IAbiReceiver) preparer).setAbi(abi);
171                                 }
172                             }
173                             for (IMultiTargetPreparer preparer :
174                                     testConfig.getMultiTargetPreparers()) {
175                                 if (preparer instanceof IAbiReceiver) {
176                                     ((IAbiReceiver) preparer).setAbi(abi);
177                                 }
178                             }
179                             LinkedHashMap<String, IConfiguration> expandedConfig =
180                                     expandTestSuites(
181                                             configNameAbi, testConfig, parentConfig, graph);
182                             configMap.putAll(expandedConfig);
183                         }
184                     } else {
185                         LinkedHashMap<String, IConfiguration> expandedConfig =
186                                 expandTestSuites(configName, testConfig, parentConfig, graph);
187                         configMap.putAll(expandedConfig);
188                     }
189                 }
190             } catch (ConfigurationException | NoClassDefFoundError e) {
191                 // Do not print the stack it's too verbose.
192                 CLog.e("Configuration '%s' cannot be loaded, ignoring.", configName);
193             }
194         }
195         return configMap;
196     }
197 
198     /** Helper to determine if a test configuration can be ran against multiple ABIs. */
canRunMultipleAbis(IConfiguration testConfig)199     private boolean canRunMultipleAbis(IConfiguration testConfig) {
200         for (IRemoteTest test : testConfig.getTests()) {
201             if (test instanceof IAbiReceiver) {
202                 return true;
203             }
204         }
205         for (ITargetPreparer preparer : testConfig.getTargetPreparers()) {
206             if (preparer instanceof IAbiReceiver) {
207                 return true;
208             }
209         }
210         for (IMultiTargetPreparer preparer : testConfig.getMultiTargetPreparers()) {
211             if (preparer instanceof IAbiReceiver) {
212                 return true;
213             }
214         }
215         return false;
216     }
217 
218     /**
219      * Helper to expand all TfSuiteRunner included in sub-configuration. Avoid having module inside
220      * module if a suite is ran as part of another suite. Verifies against circular suite
221      * dependencies.
222      */
expandTestSuites( String configName, IConfiguration config, String parentConfig, DirectedGraph<String> graph)223     private LinkedHashMap<String, IConfiguration> expandTestSuites(
224             String configName,
225             IConfiguration config,
226             String parentConfig,
227             DirectedGraph<String> graph) {
228         LinkedHashMap<String, IConfiguration> configMap =
229                 new LinkedHashMap<String, IConfiguration>();
230         List<IRemoteTest> tests = new ArrayList<>(config.getTests());
231         // In case some sub-config are suite too, we expand them to avoid weirdness
232         // of modules inside modules.
233         if (parentConfig != null) {
234             graph.addEdge(parentConfig, configName);
235             if (!graph.isDag()) {
236                 CLog.e("%s", graph);
237                 throw new RuntimeException(
238                         String.format(
239                                 "Circular configuration detected: %s has been included "
240                                         + "several times.",
241                                 configName));
242             }
243         }
244         for (IRemoteTest test : tests) {
245             if (test instanceof TfSuiteRunner) {
246                 TfSuiteRunner runner = (TfSuiteRunner) test;
247                 // Suite runner can only load and run stuff, if it has a suite tag set.
248                 if (runner.getSuiteTag() != null) {
249                     LinkedHashMap<String, IConfiguration> subConfigs =
250                             runner.loadTests(configName, graph);
251                     configMap.putAll(subConfigs);
252                 } else {
253                     CLog.w(
254                             "Config %s does not have a suite-tag it cannot run anything.",
255                             configName);
256                 }
257                 config.getTests().remove(test);
258             }
259         }
260         // If we have any IRemoteTests remaining in the base configuration, it will run.
261         if (!config.getTests().isEmpty()) {
262             configMap.put(sanitizeConfigName(configName), config);
263         }
264         return configMap;
265     }
266 
getSuiteTag()267     private String getSuiteTag() {
268         return mSuiteTag;
269     }
270 
271     /**
272      * Sanitize the config name by sanitizing the module name and reattaching the abi if it is part
273      * of the config name.
274      */
sanitizeConfigName(String originalName)275     private String sanitizeConfigName(String originalName) {
276         String[] segments;
277         if (originalName.contains(" ")) {
278             segments = originalName.split(" ");
279             return segments[0] + " " + sanitizeModuleName(segments[1]);
280         }
281         return sanitizeModuleName(originalName);
282     }
283 
284     /**
285      * Some module names are currently the absolute path name of some config files. We want to
286      * sanitize that look more like a short included config name.
287      */
sanitizeModuleName(String originalName)288     private String sanitizeModuleName(String originalName) {
289         if (originalName.endsWith(CONFIG_EXT)) {
290             originalName = originalName.substring(0, originalName.length() - CONFIG_EXT.length());
291         }
292         if (!originalName.startsWith("/")) {
293             return originalName;
294         }
295         // if it's an absolute path
296         String[] segments = originalName.split("/");
297         if (segments.length < 3) {
298             return originalName;
299         }
300         // return last two segments only
301         return String.join("/", segments[segments.length - 2], segments[segments.length - 1]);
302     }
303 }
304