• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.config.Configuration;
19 import com.android.tradefed.config.ConfigurationException;
20 import com.android.tradefed.config.IConfiguration;
21 import com.android.tradefed.config.IDeviceConfiguration;
22 import com.android.tradefed.config.OptionCopier;
23 import com.android.tradefed.invoker.TestInformation;
24 import com.android.tradefed.log.LogUtil.CLog;
25 import com.android.tradefed.targetprep.ITargetPreparer;
26 import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
27 import com.android.tradefed.testtype.IAbiReceiver;
28 import com.android.tradefed.testtype.IRemoteTest;
29 import com.android.tradefed.testtype.IShardableTest;
30 
31 import java.lang.reflect.InvocationTargetException;
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.LinkedHashMap;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Map.Entry;
38 
39 /**
40  * Helper to split a list of modules represented by {@link IConfiguration} into a list of execution
41  * units represented by {@link ModuleDefinition}.
42  *
43  * <p>Each configuration may generate 1 or more {@link ModuleDefinition} depending on its options
44  * and test types:
45  *
46  * <ul>
47  *   <li>A non-shardable {@link IConfiguration} will generate a single {@link ModuleDefinition}.
48  *   <li>A shardable {@link IConfiguration} will generate a number of ModuleDefinition linked to the
49  *       {@link IRemoteTest} properties:
50  *       <ul>
51  *         <li>A non - {@link IShardableTest} will generate a single ModuleDefinition.
52  *         <li>A {@link IShardableTest} generates one ModuleDefinition per tests returned by {@link
53  *             IShardableTest#split()}.
54  *       </ul>
55  *
56  * </ul>
57  */
58 public class ModuleSplitter {
59 
60     /**
61      * Set an upper limit to how much a given module can be sharded. This can avoid over-granular
62      * intra-module sharding.
63      */
64     private static final int MAX_MODULE_LOCAL_SHARDING = 8;
65 
66     /**
67      * Create a List of executable unit {@link ModuleDefinition}s based on the map of configuration
68      * that was loaded.
69      *
70      * @param testInfo the current {@link TestInformation} to proceed with sharding.
71      * @param runConfig {@link LinkedHashMap} loaded from {@link ITestSuite#loadTests()}.
72      * @param suitePreparersPerDevice map of suite level preparers per test device.
73      * @param shardCount a shard count hint to help with sharding.
74      * @param dynamicModule Whether or not module can be shared in pool or must be independent
75      *     (strict sharding).
76      * @param intraModuleSharding Whether or not to shard within the modules.
77      * @return List of {@link ModuleDefinition}
78      */
splitConfiguration( TestInformation testInfo, LinkedHashMap<String, IConfiguration> runConfig, Map<String, List<ITargetPreparer>> suitePreparersPerDevice, int shardCount, boolean dynamicModule, boolean intraModuleSharding)79     public static List<ModuleDefinition> splitConfiguration(
80             TestInformation testInfo,
81             LinkedHashMap<String, IConfiguration> runConfig,
82             Map<String, List<ITargetPreparer>> suitePreparersPerDevice,
83             int shardCount,
84             boolean dynamicModule,
85             boolean intraModuleSharding) {
86         if (dynamicModule) {
87             // We maximize the sharding for dynamic to reduce time difference between first and
88             // last shard as much as possible. Overhead is low due to our test pooling.
89             shardCount *= 2;
90         }
91         List<ModuleDefinition> runModules = new ArrayList<>();
92         for (Entry<String, IConfiguration> configMap : runConfig.entrySet()) {
93             // Check that it's a valid configuration for suites, throw otherwise.
94             ValidateSuiteConfigHelper.validateConfig(configMap.getValue());
95 
96             try {
97                 createAndAddModule(
98                         testInfo,
99                         runModules,
100                         configMap.getKey(),
101                         configMap.getValue(),
102                         shardCount,
103                         dynamicModule,
104                         intraModuleSharding,
105                         suitePreparersPerDevice);
106             } catch (RuntimeException e) {
107                 CLog.e("Exception while creating module for '%s'", configMap.getKey());
108                 throw e;
109             }
110         }
111         return runModules;
112     }
113 
createAndAddModule( TestInformation testInfo, List<ModuleDefinition> currentList, String moduleName, IConfiguration config, int shardCount, boolean dynamicModule, boolean intraModuleSharding, Map<String, List<ITargetPreparer>> suitePreparersPerDevice)114     private static void createAndAddModule(
115             TestInformation testInfo,
116             List<ModuleDefinition> currentList,
117             String moduleName,
118             IConfiguration config,
119             int shardCount,
120             boolean dynamicModule,
121             boolean intraModuleSharding,
122             Map<String, List<ITargetPreparer>> suitePreparersPerDevice) {
123         List<IRemoteTest> tests = config.getTests();
124         // Get rid of the IRemoteTest reference on the shared configuration. It will not be used
125         // to run.
126         config.setTests(new ArrayList<>());
127 
128         if (config.getConfigurationDescription().isNotIRemoteTestShardable()) {
129             // If it cannot be sharded at all, then put all the tests together.
130             ModuleDefinition module =
131                     new ModuleDefinition(
132                             moduleName,
133                             tests,
134                             clonePreparersMap(config),
135                             clonePreparersMap(suitePreparersPerDevice),
136                             clonePreparers(config.getMultiTargetPreparers()),
137                             config);
138             currentList.add(module);
139             clearPreparersFromConfig(config);
140             return;
141         }
142 
143         // If this particular configuration module is declared as 'not shardable' we take it whole
144         // but still split the individual IRemoteTest in a pool.
145         if (!intraModuleSharding
146                 || config.getConfigurationDescription().isNotShardable()
147                 || (!dynamicModule
148                         && config.getConfigurationDescription().isNotStrictShardable())) {
149             for (int i = 0; i < tests.size(); i++) {
150                 if (dynamicModule) {
151                     ModuleDefinition module =
152                             new ModuleDefinition(
153                                     moduleName,
154                                     tests,
155                                     clonePreparersMap(config),
156                                     clonePreparersMap(suitePreparersPerDevice),
157                                     clonePreparers(config.getMultiTargetPreparers()),
158                                     config);
159                     currentList.add(module);
160                 } else {
161                     addModuleToListFromSingleTest(
162                             currentList, tests.get(i), moduleName, config, suitePreparersPerDevice);
163                 }
164             }
165             clearPreparersFromConfig(config);
166             return;
167         }
168 
169         // If configuration is possibly shardable we attempt to shard it.
170         for (IRemoteTest test : tests) {
171             if (test instanceof IShardableTest) {
172                 int shard = Math.min(MAX_MODULE_LOCAL_SHARDING, shardCount);
173                 Collection<IRemoteTest> shardedTests =
174                         ((IShardableTest) test).split(shard, testInfo);
175                 if (shardedTests != null) {
176                     // Test did shard we put the shard pool in ModuleDefinition which has a polling
177                     // behavior on the pool.
178                     if (dynamicModule) {
179                         for (int i = 0; i < shardedTests.size(); i++) {
180                             ModuleDefinition module =
181                                     new ModuleDefinition(
182                                             moduleName,
183                                             shardedTests,
184                                             clonePreparersMap(config),
185                                             clonePreparersMap(suitePreparersPerDevice),
186                                             clonePreparers(config.getMultiTargetPreparers()),
187                                             config);
188                             module.setIntraModuleInformation(shardedTests.size(), i);
189                             currentList.add(module);
190                         }
191                     } else {
192                         // We create independent modules with each sharded test.
193                         int i = 0;
194                         for (IRemoteTest moduleTest : shardedTests) {
195                             ModuleDefinition module =
196                                     addModuleToListFromSingleTest(
197                                             currentList,
198                                             moduleTest,
199                                             moduleName,
200                                             config,
201                                             suitePreparersPerDevice);
202                             module.setIntraModuleInformation(shardedTests.size(), i);
203                             i++;
204                         }
205                     }
206                     continue;
207                 }
208             }
209             // test is not shardable or did not shard
210             addModuleToListFromSingleTest(
211                     currentList, test, moduleName, config, suitePreparersPerDevice);
212         }
213         clearPreparersFromConfig(config);
214     }
215 
216     /**
217      * Helper to add a new {@link ModuleDefinition} to our list of Modules from a single {@link
218      * IRemoteTest}.
219      */
addModuleToListFromSingleTest( List<ModuleDefinition> currentList, IRemoteTest test, String moduleName, IConfiguration config, Map<String, List<ITargetPreparer>> suitePreparersPerDevice)220     private static ModuleDefinition addModuleToListFromSingleTest(
221             List<ModuleDefinition> currentList,
222             IRemoteTest test,
223             String moduleName,
224             IConfiguration config,
225             Map<String, List<ITargetPreparer>> suitePreparersPerDevice) {
226         List<IRemoteTest> testList = new ArrayList<>();
227         testList.add(test);
228         ModuleDefinition module =
229                 new ModuleDefinition(
230                         moduleName,
231                         testList,
232                         clonePreparersMap(config),
233                         clonePreparersMap(suitePreparersPerDevice),
234                         clonePreparers(config.getMultiTargetPreparers()),
235                         config);
236         currentList.add(module);
237         return module;
238     }
239 
240     /**
241      * Deep clone a list of {@link ITargetPreparer} or {@link IMultiTargetPreparer}. We are ensured
242      * to find a default constructor with no arguments since that's the expectation from Tradefed
243      * when loading configuration. Cloning preparers is required since they may be stateful and we
244      * cannot share instance across devices.
245      */
clonePreparers(List<T> preparerList)246     private static <T> List<T> clonePreparers(List<T> preparerList) {
247         List<T> clones = new ArrayList<>();
248         for (T prep : preparerList) {
249             try {
250                 @SuppressWarnings("unchecked")
251                 T clone = (T) prep.getClass().getDeclaredConstructor().newInstance();
252                 OptionCopier.copyOptions(prep, clone);
253                 // Ensure we copy the Abi too.
254                 if (clone instanceof IAbiReceiver) {
255                     ((IAbiReceiver) clone).setAbi(((IAbiReceiver) prep).getAbi());
256                 }
257                 clones.add(clone);
258             } catch (InstantiationException
259                     | IllegalAccessException
260                     | ConfigurationException
261                     | InvocationTargetException
262                     | NoSuchMethodException e) {
263                 throw new RuntimeException(e);
264             }
265         }
266         return clones;
267     }
268 
269     /** Deep cloning of potentially multi-device preparers. */
clonePreparersMap(IConfiguration config)270     private static Map<String, List<ITargetPreparer>> clonePreparersMap(IConfiguration config) {
271         Map<String, List<ITargetPreparer>> res = new LinkedHashMap<>();
272         for (IDeviceConfiguration holder : config.getDeviceConfig()) {
273             List<ITargetPreparer> preparers = new ArrayList<>();
274             res.put(holder.getDeviceName(), preparers);
275             preparers.addAll(clonePreparers(holder.getTargetPreparers()));
276         }
277         return res;
278     }
279 
280     /** Deep cloning of potentially multi-device preparers. */
clonePreparersMap( Map<String, List<ITargetPreparer>> suitePreparersPerDevice)281     private static Map<String, List<ITargetPreparer>> clonePreparersMap(
282             Map<String, List<ITargetPreparer>> suitePreparersPerDevice) {
283         Map<String, List<ITargetPreparer>> res = new LinkedHashMap<>();
284         for (String device : suitePreparersPerDevice.keySet()) {
285             List<ITargetPreparer> preparers = new ArrayList<>();
286             res.put(device, preparers);
287             preparers.addAll(clonePreparers(suitePreparersPerDevice.get(device)));
288         }
289         return res;
290     }
291 
clearPreparersFromConfig(IConfiguration config)292     private static void clearPreparersFromConfig(IConfiguration config) {
293         try {
294             for (IDeviceConfiguration holder : config.getDeviceConfig()) {
295                 holder.removeObjectType(Configuration.TARGET_PREPARER_TYPE_NAME);
296             }
297             config.setMultiTargetPreparers(new ArrayList<>());
298         } catch (ConfigurationException e) {
299             throw new RuntimeException(e);
300         }
301     }
302 }
303