• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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.config;
17 
18 import static org.junit.Assert.assertEquals;
19 import static org.junit.Assert.assertFalse;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.assertNotSame;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 
26 import static java.util.Map.entry;
27 
28 import com.android.tradefed.build.IBuildInfo;
29 import com.android.tradefed.build.IBuildProvider;
30 import com.android.tradefed.build.IDeviceBuildProvider;
31 import com.android.tradefed.build.LocalDeviceBuildProvider;
32 import com.android.tradefed.config.ConfigurationDef.ConfigObjectDef;
33 import com.android.tradefed.config.ConfigurationFactory.ConfigId;
34 import com.android.tradefed.config.remote.IRemoteFileResolver.ResolvedFile;
35 import com.android.tradefed.log.ILeveledLogOutput;
36 import com.android.tradefed.log.Log.LogLevel;
37 import com.android.tradefed.log.LogUtil.CLog;
38 import com.android.tradefed.targetprep.DeviceWiper;
39 import com.android.tradefed.targetprep.ILabPreparer;
40 import com.android.tradefed.targetprep.StubTargetPreparer;
41 import com.android.tradefed.targetprep.multi.StubMultiTargetPreparer;
42 import com.android.tradefed.util.FileUtil;
43 import com.android.tradefed.util.StreamUtil;
44 
45 import com.google.common.collect.ImmutableSet;
46 
47 import org.junit.Before;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.junit.runners.JUnit4;
51 import org.mockito.Mockito;
52 
53 import java.io.ByteArrayOutputStream;
54 import java.io.File;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.io.PrintStream;
58 import java.net.URI;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.Enumeration;
62 import java.util.HashMap;
63 import java.util.HashSet;
64 import java.util.LinkedHashMap;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Map.Entry;
68 import java.util.Set;
69 import java.util.jar.JarEntry;
70 import java.util.jar.JarFile;
71 
72 /** Unit tests for {@link ConfigurationFactory} */
73 @RunWith(JUnit4.class)
74 public class ConfigurationFactoryTest {
75 
76     private ConfigurationFactory mFactory;
77     // Create a real instance for tests that checks the content of our configs. making it static to
78     // reduce the runtime of reloading the config thanks to the caching of configurations.
79     private static ConfigurationFactory mRealFactory = new ConfigurationFactory();
80 
81     /** the test config name that is built into this jar */
82     private static final String TEST_CONFIG = "test-config";
83     private static final String GLOBAL_TEST_CONFIG = "global-config";
84     private static final String INCLUDE_CONFIG = "include-config";
85 
86     private static final List<String> JAR_TO_CHECK = new ArrayList<>();
87 
88     static {
89         JAR_TO_CHECK.add("tradefed-contrib.jar");
90         JAR_TO_CHECK.add("google-tradefed-contrib.jar");
91     }
92 
93     @Before
setUp()94     public void setUp() throws Exception {
95 
96         mFactory =
97                 new ConfigurationFactory() {
98                     @Override
99                     protected String getConfigPrefix() {
100                         return "testconfigs/";
101                     }
102                 };
103     }
104 
105     /** Initial test to ensure all config names on classpath are loadable */
106     @Test
testLoadAllConfigs()107     public void testLoadAllConfigs() throws Exception {
108         ConfigurationFactory spyFactory = Mockito.spy(mRealFactory);
109         Mockito.doReturn(new HashSet<String>()).when(spyFactory).getConfigNamesFromTestCases(null);
110 
111         // we dry-run the templates otherwise it will always fail.
112         spyFactory.loadAllConfigs(false);
113         assertTrue(spyFactory.getMapConfig().size() > 0);
114         Mockito.verify(spyFactory, Mockito.times(1)).getConfigNamesFromTestCases(null);
115 
116         // Verify that there is no cross-referencing between core and contrib:
117         // core configs should not use any contrib object.
118         Map<String, String> configAndJar = spyFactory.getConfigSetFromClasspathFromJar(null);
119         Map<String, List<String>> classInContribJars = getClassInContribJar();
120 
121         List<String> exceptionConfigJar = new ArrayList<>();
122         for (Entry<ConfigId, ConfigurationDef> entry : spyFactory.getMapConfig().entrySet()) {
123             String configName = entry.getKey().name;
124             String jarName = configAndJar.get(configName);
125             ConfigurationDef cDef = entry.getValue();
126             if (JAR_TO_CHECK.contains(jarName)) {
127                 continue;
128             }
129 
130             for (List<ConfigObjectDef> cObjects : cDef.getObjectClassMap().values()) {
131                 for (ConfigObjectDef o : cObjects) {
132                     for (String contribJar : classInContribJars.keySet()) {
133                         if (classInContribJars.get(contribJar).contains(o.mClassName)) {
134                             exceptionConfigJar.add(
135                                     String.format(
136                                             "%s is a core-configuration (%s). It shouldn't contain"
137                                                     + " a contrib objects '%s' from %s.",
138                                             configName, jarName, o.mClassName, contribJar));
139                         }
140                     }
141                 }
142             }
143         }
144         if (!exceptionConfigJar.isEmpty()) {
145             fail(
146                     String.format(
147                             "Found some dependencies of core on contrib:\n%s",
148                             String.join("\n", exceptionConfigJar)));
149         }
150     }
151 
152     /** Initial test to ensure all configs on classpath can be fully loaded and parsed */
153     @Test
testLoadAndPrintAllConfigs()154     public void testLoadAndPrintAllConfigs() throws ConfigurationException {
155         ConfigurationFactory spyFactory = Mockito.spy(mRealFactory);
156         Mockito.doReturn(new HashSet<String>()).when(spyFactory).getConfigNamesFromTestCases(null);
157 
158         // Printing the help involves more checks since it tries to resolve the config objects.
159         spyFactory.loadAndPrintAllConfigs();
160         assertTrue(spyFactory.getMapConfig().size() > 0);
161         Mockito.verify(spyFactory, Mockito.times(1)).getConfigNamesFromTestCases(null);
162     }
163 
164     /** Test that a config xml defined in this test jar can be read as a built-in */
165     @Test
testGetConfiguration_extension()166     public void testGetConfiguration_extension() throws ConfigurationException {
167         assertConfigValid(TEST_CONFIG);
168     }
169 
buildMap(String... args)170     private Map<String, String> buildMap(String... args) {
171         if ((args.length % 2) != 0) {
172             throw new IllegalArgumentException(String.format(
173                 "Expected an even number of args; got %d", args.length));
174         }
175 
176         final Map<String, String> map = new HashMap<String, String>(args.length / 2);
177         for (int i = 0; i < args.length; i += 2) {
178             map.put(args[i], args[i + 1]);
179         }
180 
181         return map;
182     }
183 
184     /** Make sure that ConfigId behaves in the right way to serve as a hash key */
185     @Test
testConfigId_equals()186     public void testConfigId_equals() {
187         final ConfigId config1a = new ConfigId("one");
188         final ConfigId config1b = new ConfigId("one");
189         final ConfigId config2 = new ConfigId("two");
190         final ConfigId config3a = new ConfigId("one", buildMap("target", "foo"));
191         final ConfigId config3b = new ConfigId("one", buildMap("target", "foo"));
192         final ConfigId config4 = new ConfigId("two", buildMap("target", "bar"));
193 
194         assertEquals(config1a, config1b);
195         assertEquals(config3a, config3b);
196 
197         // Check for false equivalences, and don't depend on #equals being commutative
198         assertFalse(config1a.equals(config2));
199         assertFalse(config1a.equals(config3a));
200         assertFalse(config1a.equals(config4));
201 
202         assertFalse(config2.equals(config1a));
203         assertFalse(config2.equals(config3a));
204         assertFalse(config2.equals(config4));
205 
206         assertFalse(config3a.equals(config1a));
207         assertFalse(config3a.equals(config2));
208         assertFalse(config3a.equals(config4));
209 
210         assertFalse(config4.equals(config1a));
211         assertFalse(config4.equals(config2));
212         assertFalse(config4.equals(config3a));
213     }
214 
215     /** Make sure that ConfigId behaves in the right way to serve as a hash key */
216     @Test
testConfigId_hashKey()217     public void testConfigId_hashKey() {
218         final Map<ConfigId, String> map = new HashMap<>();
219         final ConfigId config1a = new ConfigId("one");
220         final ConfigId config1b = new ConfigId("one");
221         final ConfigId config2 = new ConfigId("two");
222         final ConfigId config3a = new ConfigId("one", buildMap("target", "foo"));
223         final ConfigId config3b = new ConfigId("one", buildMap("target", "foo"));
224         final ConfigId config4 = new ConfigId("two", buildMap("target", "bar"));
225 
226         // Make sure that keys config1a and config1b behave identically
227         map.put(config1a, "1a");
228         assertEquals("1a", map.get(config1a));
229         assertEquals("1a", map.get(config1b));
230 
231         map.put(config1b, "1b");
232         assertEquals("1b", map.get(config1a));
233         assertEquals("1b", map.get(config1b));
234 
235         assertFalse(map.containsKey(config2));
236         assertFalse(map.containsKey(config3a));
237         assertFalse(map.containsKey(config4));
238 
239         // Make sure that keys config3a and config3b behave identically
240         map.put(config3a, "3a");
241         assertEquals("3a", map.get(config3a));
242         assertEquals("3a", map.get(config3b));
243 
244         map.put(config3b, "3b");
245         assertEquals("3b", map.get(config3a));
246         assertEquals("3b", map.get(config3b));
247 
248         assertEquals(2, map.size());
249         assertFalse(map.containsKey(config2));
250         assertFalse(map.containsKey(config4));
251 
252         // It's unlikely for these to fail if the above tests all passed, but just fill everything
253         // out for completeness
254         map.put(config2, "2");
255         map.put(config4, "4");
256 
257         assertEquals(4, map.size());
258         assertEquals("1b", map.get(config1a));
259         assertEquals("1b", map.get(config1b));
260         assertEquals("2", map.get(config2));
261         assertEquals("3b", map.get(config3a));
262         assertEquals("3b", map.get(config3b));
263         assertEquals("4", map.get(config4));
264     }
265 
266     /** Test specifying a config xml by file path */
267     @Test
testGetConfiguration_xmlpath()268     public void testGetConfiguration_xmlpath() throws ConfigurationException, IOException {
269         // extract the test-config.xml into a tmp file
270         InputStream configStream = getClass().getResourceAsStream(
271                 String.format("/testconfigs/%s.xml", TEST_CONFIG));
272         File tmpFile = FileUtil.createTempFile(TEST_CONFIG, ".xml");
273         try {
274             FileUtil.writeToFile(configStream, tmpFile);
275             assertConfigValid(tmpFile.getAbsolutePath());
276             // check reading it again - should grab the cached version
277             assertConfigValid(tmpFile.getAbsolutePath());
278         } finally {
279             FileUtil.deleteFile(tmpFile);
280         }
281     }
282 
283     /** Test that a config xml defined in this test jar can be read as a built-in */
284     @Test
testGetGlobalConfiguration_extension()285     public void testGetGlobalConfiguration_extension() throws ConfigurationException {
286         assertGlobalConfigValid(GLOBAL_TEST_CONFIG);
287     }
288 
289     /** Test specifying a config xml by file path */
290     @Test
testGetGlobalConfiguration_xmlpath()291     public void testGetGlobalConfiguration_xmlpath() throws ConfigurationException, IOException {
292         // extract the test-config.xml into a tmp file
293         InputStream configStream = getClass().getResourceAsStream(
294                 String.format("/testconfigs/%s.xml", GLOBAL_TEST_CONFIG));
295         File tmpFile = FileUtil.createTempFile(GLOBAL_TEST_CONFIG, ".xml");
296         try {
297             FileUtil.writeToFile(configStream, tmpFile);
298             assertGlobalConfigValid(tmpFile.getAbsolutePath());
299             // check reading it again - should grab the cached version
300             assertGlobalConfigValid(tmpFile.getAbsolutePath());
301         } finally {
302             FileUtil.deleteFile(tmpFile);
303         }
304     }
305 
306     /**
307      * Checks all config attributes are non-null
308      */
assertConfigValid(String name)309     private void assertConfigValid(String name) throws ConfigurationException {
310         IConfiguration config = mFactory.createConfigurationFromArgs(new String[] {name});
311         assertNotNull(config);
312     }
313 
314     /**
315      * Checks all config attributes are non-null
316      */
assertGlobalConfigValid(String name)317     private void assertGlobalConfigValid(String name) throws ConfigurationException {
318         List<String> unprocessed = new ArrayList<String>();
319         IGlobalConfiguration config =
320                 mFactory.createGlobalConfigurationFromArgs(new String[] {name}, unprocessed);
321         assertNotNull(config);
322         assertNotNull(config.getDeviceMonitors());
323         assertNotNull(config.getWtfHandler());
324         assertTrue(unprocessed.isEmpty());
325     }
326 
327     /**
328      * Test calling {@link ConfigurationFactory#createConfigurationFromArgs(String[])} with a name
329      * that does not exist.
330      */
331     @Test
testCreateConfigurationFromArgs_missing()332     public void testCreateConfigurationFromArgs_missing() {
333         try {
334             mFactory.createConfigurationFromArgs(new String[] {"non existent"});
335             fail("did not throw ConfigurationException");
336         } catch (ConfigurationException e) {
337             // expected
338         }
339     }
340 
341     /**
342      * Test calling {@link ConfigurationFactory#createConfigurationFromArgs(String[])} with config
343      * that has unset mandatory options.
344      *
345      * <p>Expect this to succeed, since mandatory option validation no longer happens at
346      * configuration instantiation time.
347      */
348     @Test
testCreateConfigurationFromArgs_mandatory()349     public void testCreateConfigurationFromArgs_mandatory() throws ConfigurationException {
350         assertNotNull(mFactory.createConfigurationFromArgs(new String[] {"mandatory-config"}));
351     }
352 
353     /**
354      * Test passing empty arg list to {@link
355      * ConfigurationFactory#createConfigurationFromArgs(String[])}.
356      */
357     @Test
testCreateConfigurationFromArgs_empty()358     public void testCreateConfigurationFromArgs_empty() {
359         try {
360             mFactory.createConfigurationFromArgs(new String[] {});
361             fail("did not throw ConfigurationException");
362         } catch (ConfigurationException e) {
363             // expected
364         }
365     }
366 
367     /** Test {@link ConfigurationFactory#createConfigurationFromArgs(String[])} using TEST_CONFIG */
368     @Test
testCreateConfigurationFromArgs()369     public void testCreateConfigurationFromArgs() throws ConfigurationException {
370         // pick an arbitrary option to test to ensure it gets populated
371         IConfiguration config = mFactory.createConfigurationFromArgs(new String[] {TEST_CONFIG,
372                 "--log-level", LogLevel.VERBOSE.getStringValue()});
373         ILeveledLogOutput logger = config.getLogOutput();
374         assertEquals(LogLevel.VERBOSE, logger.getLogLevel());
375     }
376 
377     /**
378      * Test {@link ConfigurationFactory#createConfigurationFromArgs(String[])} when extra positional
379      * arguments are supplied
380      */
381     @Test
testCreateConfigurationFromArgs_unprocessedArgs()382     public void testCreateConfigurationFromArgs_unprocessedArgs() {
383         try {
384             mFactory.createConfigurationFromArgs(new String[] {TEST_CONFIG, "--log-level",
385                     LogLevel.VERBOSE.getStringValue(), "blah"});
386             fail("ConfigurationException not thrown");
387         } catch (ConfigurationException e) {
388             // expected
389         }
390     }
391 
392     /** Test {@link ConfigurationFactory#printHelp(PrintStream)} */
393     @Test
testPrintHelp()394     public void testPrintHelp() {
395         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
396         PrintStream mockPrintStream = new PrintStream(outputStream);
397         mRealFactory.printHelp(mockPrintStream);
398         // verify all the instrument config names are present
399         final String usageString = outputStream.toString();
400         assertTrue(usageString.contains("instrument"));
401     }
402 
403     /**
404      * Test {@link ConfigurationFactory#printHelpForConfig(String[], boolean, PrintStream)} when
405      * config referenced by args exists
406      */
407     @Test
testPrintHelpForConfig_configExists()408     public void testPrintHelpForConfig_configExists() {
409         String[] args = new String[] {TEST_CONFIG};
410         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
411         PrintStream mockPrintStream = new PrintStream(outputStream);
412         mFactory.printHelpForConfig(args, true, mockPrintStream);
413 
414         // verify the default configs name used is present
415         final String usageString = outputStream.toString();
416         assertTrue(usageString.contains(TEST_CONFIG));
417         // TODO: add stricter verification
418     }
419 
420     /** Test {@link ConfigurationFactory#getConfigList()} */
421     @Test
testListAllConfigs()422     public void testListAllConfigs() {
423         List<String> listConfigs = mRealFactory.getConfigList();
424         assertTrue(listConfigs.size() != 0);
425         // Check that our basic configs are always here
426         assertTrue(listConfigs.contains("empty"));
427         assertTrue(listConfigs.contains("host"));
428         assertTrue(listConfigs.contains("instrument"));
429     }
430 
431     /**
432      * Test {@link ConfigurationFactory#getConfigList(String, boolean)} where we list the config in
433      * a sub path only
434      */
435     @Test
testListSubConfig()436     public void testListSubConfig() {
437         final String subDir = "suite/";
438         List<String> listConfigs = mRealFactory.getConfigList(subDir, false);
439         assertTrue(listConfigs.size() != 0);
440         // Check that our basic configs are always here
441         assertTrue(listConfigs.contains("suite/stub1"));
442         assertTrue(listConfigs.contains("suite/stub2"));
443         // Validate that all listed config are indeed from the subdir.
444         for (String config : listConfigs) {
445             assertTrue(config.startsWith(subDir));
446         }
447     }
448 
449     /** Test loading a config that includes another config. */
450     @Test
testCreateConfigurationFromArgs_includeConfig()451     public void testCreateConfigurationFromArgs_includeConfig() throws Exception {
452         IConfiguration config = mFactory.createConfigurationFromArgs(
453                 new String[]{INCLUDE_CONFIG});
454         assertTrue(config.getTests().get(0) instanceof StubOptionTest);
455         assertTrue(config.getTests().get(1) instanceof StubOptionTest);
456         StubOptionTest fromTestConfig = (StubOptionTest) config.getTests().get(0);
457         StubOptionTest fromIncludeConfig = (StubOptionTest) config.getTests().get(1);
458         assertEquals("valueFromTestConfig", fromTestConfig.mOption);
459         assertEquals("valueFromIncludeConfig", fromIncludeConfig.mOption);
460     }
461 
462     /**
463      * Test loading a config that uses the "default" attribute of a template-include tag to include
464      * another config.
465      */
466     @Test
testCreateConfigurationFromArgs_defaultTemplateInclude_default()467     public void testCreateConfigurationFromArgs_defaultTemplateInclude_default() throws Exception {
468         // The default behavior is to include test-config directly.  Nesting is such that innermost
469         // elements come first.
470         IConfiguration config = mFactory.createConfigurationFromArgs(
471                 new String[]{"template-include-config-with-default"});
472         assertEquals(2, config.getTests().size());
473         assertTrue(config.getTests().get(0) instanceof StubOptionTest);
474         assertTrue(config.getTests().get(1) instanceof StubOptionTest);
475         StubOptionTest innerConfig = (StubOptionTest) config.getTests().get(0);
476         StubOptionTest outerConfig = (StubOptionTest) config.getTests().get(1);
477         assertEquals("valueFromTestConfig", innerConfig.mOption);
478         assertEquals("valueFromTemplateIncludeWithDefaultConfig", outerConfig.mOption);
479     }
480 
481     /**
482      * Test using {@code <include>} to load a config that uses the "default" attribute of a
483      * template-include tag to include a third config.
484      */
485     @Test
testCreateConfigurationFromArgs_includeTemplateIncludeWithDefault()486     public void testCreateConfigurationFromArgs_includeTemplateIncludeWithDefault()
487             throws Exception {
488         // The default behavior is to include test-config directly.  Nesting is such that innermost
489         // elements come first.
490         IConfiguration config = mFactory.createConfigurationFromArgs(
491                 new String[]{"include-template-config-with-default"});
492         assertEquals(3, config.getTests().size());
493         assertTrue(config.getTests().get(0) instanceof StubOptionTest);
494         assertTrue(config.getTests().get(1) instanceof StubOptionTest);
495         assertTrue(config.getTests().get(2) instanceof StubOptionTest);
496         StubOptionTest innerConfig = (StubOptionTest) config.getTests().get(0);
497         StubOptionTest middleConfig = (StubOptionTest) config.getTests().get(1);
498         StubOptionTest outerConfig = (StubOptionTest) config.getTests().get(2);
499         assertEquals("valueFromTestConfig", innerConfig.mOption);
500         assertEquals("valueFromTemplateIncludeWithDefaultConfig", middleConfig.mOption);
501         assertEquals("valueFromIncludeTemplateConfigWithDefault", outerConfig.mOption);
502     }
503 
504     /**
505      * Test loading a config that uses the "default" attribute of a template-include tag to include
506      * another config. In this case, we override the default attribute on the commandline.
507      */
508     @Test
testCreateConfigurationFromArgs_defaultTemplateInclude_alternate()509     public void testCreateConfigurationFromArgs_defaultTemplateInclude_alternate()
510             throws Exception {
511         IConfiguration config = mFactory.createConfigurationFromArgs(
512                 new String[]{"template-include-config-with-default", "--template:map", "target",
513                 INCLUDE_CONFIG});
514         assertEquals(3, config.getTests().size());
515         assertTrue(config.getTests().get(0) instanceof StubOptionTest);
516         assertTrue(config.getTests().get(1) instanceof StubOptionTest);
517         assertTrue(config.getTests().get(2) instanceof StubOptionTest);
518 
519         StubOptionTest innerConfig = (StubOptionTest) config.getTests().get(0);
520         StubOptionTest middleConfig = (StubOptionTest) config.getTests().get(1);
521         StubOptionTest outerConfig = (StubOptionTest) config.getTests().get(2);
522 
523         assertEquals("valueFromTestConfig", innerConfig.mOption);
524         assertEquals("valueFromIncludeConfig", middleConfig.mOption);
525         assertEquals("valueFromTemplateIncludeWithDefaultConfig", outerConfig.mOption);
526     }
527 
528     /** Test loading a config that uses template-include to include another config. */
529     @Test
testCreateConfigurationFromArgs_templateInclude()530     public void testCreateConfigurationFromArgs_templateInclude() throws Exception {
531         IConfiguration config = mFactory.createConfigurationFromArgs(
532                 new String[]{"template-include-config", "--template:map", "target",
533                 "test-config"});
534         assertTrue(config.getTests().get(0) instanceof StubOptionTest);
535         assertTrue(config.getTests().get(1) instanceof StubOptionTest);
536         StubOptionTest fromTestConfig = (StubOptionTest) config.getTests().get(0);
537         StubOptionTest fromTemplateIncludeConfig = (StubOptionTest) config.getTests().get(1);
538         assertEquals("valueFromTestConfig", fromTestConfig.mOption);
539         assertEquals("valueFromTemplateIncludeConfig", fromTemplateIncludeConfig.mOption);
540     }
541 
542     @Test
testCreateConfigurationFromArgs_repeatedTemplate()543     public void testCreateConfigurationFromArgs_repeatedTemplate() throws Exception {
544         try {
545             mFactory.createConfigurationFromArgs(
546                     new String[] {"repeated-template", "--template:map", "target", "empty"});
547             fail("Should have thrown an exception.");
548         } catch (ConfigurationException expected) {
549             assertEquals(
550                     "Failed to parse config xml 'repeated-template'. Reason: Template named "
551                             + "'target' appeared more than once.",
552                     expected.getMessage());
553         }
554     }
555 
556     /** Test loading a config that uses template-include to include another config. */
557     @Test
testCreateConfigurationFromArgs_templateInclude_multiKey()558     public void testCreateConfigurationFromArgs_templateInclude_multiKey() throws Exception {
559         try {
560             mFactory.createConfigurationFromArgs(
561                     new String[] {
562                         "template-include-config",
563                         "--template:map",
564                         "target",
565                         "test-config",
566                         "--template:map",
567                         "target",
568                         "test-config"
569                     });
570             fail("Should have thrown an exception.");
571         } catch (ConfigurationException expected) {
572             // Expected
573             assertEquals(
574                     "More than one template specified for key 'target'", expected.getMessage());
575         }
576     }
577 
578     /** Make sure that we throw a useful error when template-include usage is underspecified. */
579     @Test
testCreateConfigurationFromArgs_templateInclude_unspecified()580     public void testCreateConfigurationFromArgs_templateInclude_unspecified() throws Exception {
581         final String configName = "template-include-config";
582         try {
583             mFactory.createConfigurationFromArgs(new String[]{configName});
584             fail ("ConfigurationException not thrown");
585         } catch (ConfigurationException e) {
586             // Make sure that we get the expected error message
587             final String msg = e.getMessage();
588             assertNotNull(msg);
589 
590             assertTrue(String.format("Error message does not mention the name of the broken " +
591                     "config.  msg was: %s", msg), msg.contains(configName));
592 
593             // Error message should help people to resolve the problem
594             assertTrue(String.format("Error message should help user to resolve the " +
595                     "template-include.  msg was: %s", msg),
596                     msg.contains(String.format("--template:map %s", "target")));
597             CLog.e(msg);
598             assertTrue(
599                     String.format(
600                             "Error message should mention the ability to specify a "
601                                     + "default resolution.  msg was: %s",
602                             msg),
603                     msg.contains("'default'"));
604         }
605     }
606 
607     /**
608      * Make sure that we throw a useful error when template-include mentions a target configuration
609      * that doesn't exist.
610      */
611     @Test
testCreateConfigurationFromArgs_templateInclude_missing()612     public void testCreateConfigurationFromArgs_templateInclude_missing() throws Exception {
613         final String configName = "template-include-config";
614         final String includeName = "no-exist";
615 
616         try {
617             mFactory.createConfigurationFromArgs(
618                     new String[]{configName, "--template:map", "target", includeName});
619             fail ("ConfigurationException not thrown");
620         } catch (ConfigurationException e) {
621             // Make sure that we get the expected error message
622             final String msg = e.getMessage();
623             assertNotNull(msg);
624 
625             assertTrue(String.format("Error message does not mention the name of the broken " +
626                     "config.  msg was: %s", msg), msg.contains(configName));
627             assertTrue(String.format("Error message does not mention the name of the missing " +
628                     "include target.  msg was: %s", msg), msg.contains(includeName));
629         }
630     }
631 
632     /** Test loading a config that includes a local config. */
633     @Test
testCreateConfigurationFromArgs_templateInclude_local()634     public void testCreateConfigurationFromArgs_templateInclude_local() throws Exception {
635         final String configName = "template-include-config";
636         InputStream configStream = getClass().getResourceAsStream(
637                 String.format("/testconfigs/%s.xml", INCLUDE_CONFIG));
638         File tmpConfig = FileUtil.createTempFile(INCLUDE_CONFIG, ".xml");
639         try {
640             FileUtil.writeToFile(configStream, tmpConfig);
641             final String includeName = tmpConfig.getAbsolutePath();
642             IConfiguration config = mFactory.createConfigurationFromArgs(
643                     new String[]{configName, "--template:map", "target", includeName});
644             assertTrue(config.getTests().get(0) instanceof StubOptionTest);
645             assertTrue(config.getTests().get(1) instanceof StubOptionTest);
646             StubOptionTest fromTestConfig = (StubOptionTest) config.getTests().get(0);
647             StubOptionTest fromIncludeConfig = (StubOptionTest) config.getTests().get(1);
648             assertEquals("valueFromTestConfig", fromTestConfig.mOption);
649             assertEquals("valueFromIncludeConfig", fromIncludeConfig.mOption);
650         } finally {
651             FileUtil.deleteFile(tmpConfig);
652         }
653     }
654 
655     /**
656      * This unit test codifies the expectation that an inner {@code <template-include>} tag is
657      * properly found and replaced by a config containing another template that is resolved. MAIN
658      * CONFIG -> Template 1 -> Template 2 = Works!
659      */
660     @Test
testCreateConfigurationFromArgs_templateInclude_dependent()661     public void testCreateConfigurationFromArgs_templateInclude_dependent() throws Exception {
662         final String configName = "depend-template-include-config";
663         final String depTargetName = "template-include-config";
664         final String targetName = "test-config";
665 
666         IConfiguration config =
667                 mFactory.createConfigurationFromArgs(
668                         new String[] {
669                             configName,
670                             "--template:map",
671                             "dep-target",
672                             depTargetName,
673                             "--template:map",
674                             "target",
675                             targetName
676                         });
677         assertTrue(config.getTests().get(0) instanceof StubOptionTest);
678         assertTrue(config.getTests().get(1) instanceof StubOptionTest);
679     }
680 
681     /**
682      * This unit test codifies the expectation that an inner {@code <template-include>} tag replaced
683      * by a missing config raise a failure. MAIN CONFIG -> Template 1 -> Template 2(missing) = error
684      */
685     @Test
testCreateConfigurationFromArgs_templateInclude_dependent_missing()686     public void testCreateConfigurationFromArgs_templateInclude_dependent_missing()
687             throws Exception {
688         final String configName = "depend-template-include-config";
689         final String depTargetName = "template-include-config";
690         final String targetName = "test-config-missing";
691         final String expError = String.format(
692                 "Bundled config '%s' is including a config '%s' that's neither local nor bundled.",
693                 depTargetName, targetName);
694 
695         try {
696             mFactory.createConfigurationFromArgs(new String[]{configName,
697                     "--template:map", "dep-target", depTargetName,
698                     "--template:map", "target", targetName});
699             fail ("ConfigurationException not thrown");
700         } catch (ConfigurationException e) {
701             // Make sure that we get the expected error message
702             assertEquals(expError, e.getMessage());
703         }
704     }
705 
706     /**
707      * This unit test codifies the expectation that an inner {@code <template-include>} tag that
708      * doesn't have a default attribute will fail because cannot be resolved. MAIN CONFIG ->
709      * Template 1 -> Template 2(no Default, no args override) = error
710      */
711     @Test
testCreateConfigurationFromArgs_templateInclude_dependent_nodefault()712     public void testCreateConfigurationFromArgs_templateInclude_dependent_nodefault()
713             throws Exception {
714         final String configName = "depend-template-include-config";
715         final String depTargetName = "template-include-config";
716         final String expError = String.format(
717                 "Failed to parse config xml '%s'. Reason: " +
718                 ConfigurationXmlParser.ConfigHandler.INNER_TEMPLATE_INCLUDE_ERROR,
719                 configName, configName, depTargetName);
720         try {
721             mFactory.createConfigurationFromArgs(new String[]{configName,
722                     "--template:map", "dep-target", depTargetName});
723             fail ("ConfigurationException not thrown");
724         } catch (ConfigurationException e) {
725             assertEquals(expError, e.getMessage());
726         }
727     }
728 
729     /**
730      * This unit test codifies the expectation that an inner {@code <template-include>} tag that is
731      * inside an include tag will be correctly replaced by arguments. Main Config -> Include 1 ->
732      * Template 1 -> test-config.xml
733      */
734     @Test
testCreateConfigurationFromArgs_include_dependent()735     public void testCreateConfigurationFromArgs_include_dependent() throws Exception {
736         final String configName = "include-template-config";
737         final String targetName = "test-config";
738         try {
739             IConfiguration config = mFactory.createConfigurationFromArgs(new String[]{configName,
740                     "--template:map", "target", targetName});
741             assertTrue(config.getTests().get(0) instanceof StubOptionTest);
742             assertTrue(config.getTests().get(1) instanceof StubOptionTest);
743         } catch (ConfigurationException e) {
744             fail(e.getMessage());
745         }
746     }
747 
748     /**
749      * This unit test ensure that when a configuration use included configuration the top
750      * configuration description is kept.
751      */
752     @Test
testTopDescriptionIsPreserved()753     public void testTopDescriptionIsPreserved() throws ConfigurationException {
754         final String configName = "top-config";
755         Map<String, String> fakeTemplate = new HashMap<>();
756         fakeTemplate.put("target", "included-config");
757         ConfigurationDef test = mFactory.new ConfigLoader(false)
758                 .getConfigurationDef(configName, fakeTemplate);
759         assertEquals("top config description", test.getDescription());
760     }
761 
762     /**
763      * This unit test codifies the expectation that an inner {@code <template-include>} tag that is
764      * inside an include tag will be correctly rejected if no arguments can match it and no default
765      * value is present. Main Config -> Include 1 -> Template 1 (No default, no args override) =
766      * error
767      */
768     @Test
testCreateConfigurationFromArgs_include_dependent_nodefault()769     public void testCreateConfigurationFromArgs_include_dependent_nodefault() throws Exception {
770         final String configName = "include-template-config";
771         final String includeTargetName = "template-include-config";
772         final String expError = String.format(
773                 "Failed to parse config xml '%s'. Reason: " +
774                 ConfigurationXmlParser.ConfigHandler.INNER_TEMPLATE_INCLUDE_ERROR,
775                 configName, configName, includeTargetName);
776         try {
777             mFactory.createConfigurationFromArgs(new String[]{configName});
778             fail ("ConfigurationException not thrown");
779         } catch (ConfigurationException e) {
780             assertEquals(expError, e.getMessage());
781         }
782     }
783 
784     /** Test loading a config that tries to include itself */
785     @Test
testCreateConfigurationFromArgs_recursiveInclude()786     public void testCreateConfigurationFromArgs_recursiveInclude() throws Exception {
787         try {
788             mFactory.createConfigurationFromArgs(new String[] {"recursive-config"});
789             fail("ConfigurationException not thrown");
790         } catch (ConfigurationException e) {
791             // expected
792         }
793     }
794 
795     /**
796      * Test loading a config that tries to replace a template with itself will fail because it
797      * creates a cycle of configuration.
798      */
799     @Test
testCreateConfigurationFromArgs_recursiveTemplate()800     public void testCreateConfigurationFromArgs_recursiveTemplate() throws Exception {
801         final String configName = "depend-template-include-config";
802         final String depTargetName = "depend-template-include-config";
803         final String expError = String.format(
804                 "Circular configuration include: config '%s' is already included", depTargetName);
805         try {
806             mFactory.createConfigurationFromArgs(new String[]{configName,
807                     "--template:map", "dep-target", depTargetName});
808             fail ("ConfigurationException not thrown");
809         } catch (ConfigurationException e) {
810             assertEquals(expError, e.getMessage());
811         }
812     }
813 
814     /**
815      * Re-apply a template on a lower config level. Should result in a fail to parse because each
816      * template:map can only be applied once. So missing the default will throw exception.
817      */
818     @Test
testCreateConfigurationFromArgs_template_multilevel()819     public void testCreateConfigurationFromArgs_template_multilevel() throws Exception {
820         final String configName = "depend-template-include-config";
821         final String depTargetName = "template-include-config";
822         final String depTargetName2 = "template-collision-include-config";
823         final String expError = String.format(
824                 "Failed to parse config xml '%s'. Reason: " +
825                 ConfigurationXmlParser.ConfigHandler.INNER_TEMPLATE_INCLUDE_ERROR,
826                 configName, configName, depTargetName2);
827         try {
828             mFactory.createConfigurationFromArgs(new String[]{configName,
829                     "--template:map", "dep-target", depTargetName,
830                     "--template:map", "target", depTargetName2});
831             fail ("ConfigurationException not thrown");
832         } catch (ConfigurationException e) {
833             assertEquals(expError, e.getMessage());
834         }
835     }
836 
837     /**
838      * Re-apply a template twice. Should result in an error only because the configs included have
839      * several build_provider
840      */
841     @Test
testCreateConfigurationFromArgs_templateCollision()842     public void testCreateConfigurationFromArgs_templateCollision() throws Exception {
843         final String configName = "template-collision-include-config";
844         final String depTargetName = "template-include-config-with-default";
845         final String expError =
846                 "Failed to parse config xml 'template-collision-include-config'. Reason: "
847                         + "Template named 'target' appeared more than once.";
848         try {
849             mFactory.createConfigurationFromArgs(new String[]{configName,
850                     "--template:map", "target-col", depTargetName,
851                     "--template:map", "target-col2", depTargetName});
852             fail ("ConfigurationException not thrown");
853         } catch (ConfigurationException e) {
854             assertEquals(expError, e.getMessage());
855         }
856     }
857 
858     /** Test loading a config that tries to include a non-bundled config. */
859     @Test
testCreateConfigurationFromArgs_nonBundledInclude()860     public void testCreateConfigurationFromArgs_nonBundledInclude() throws Exception {
861        try {
862            mFactory.createConfigurationFromArgs(new String[] {"non-bundled-config"});
863            fail("ConfigurationException not thrown");
864        } catch (ConfigurationException e) {
865            // expected
866        }
867     }
868 
869     /** Test reloading a config after it has been updated. */
870     @Test
testCreateConfigurationFromArgs_localConfigReload()871     public void testCreateConfigurationFromArgs_localConfigReload()
872             throws ConfigurationException, IOException {
873         File localConfigFile = FileUtil.createTempFile("local-config", ".xml");
874         try {
875             // Copy the config to the local filesystem
876             InputStream source = getClass().getResourceAsStream("/testconfigs/local-config.xml");
877             FileUtil.writeToFile(source, localConfigFile);
878 
879             // Depending on the system, file modification times might not have greater than 1 second
880             // resolution. Backdate the original contents so that when we write to the file later,
881             // it shows up as a new change.
882             localConfigFile.setLastModified(System.currentTimeMillis() - 5000);
883 
884             // Load the configuration from the local file
885             IConfiguration config = mFactory.createConfigurationFromArgs(
886                     new String[] { localConfigFile.getAbsolutePath() });
887             if (!(config.getTests().get(0) instanceof StubOptionTest)) {
888                 fail(String.format("Expected a StubOptionTest, but got %s",
889                         config.getTests().get(0).getClass().getName()));
890                 return;
891             }
892             StubOptionTest test = (StubOptionTest)config.getTests().get(0);
893             assertEquals("valueFromOriginalConfig", test.mOption);
894 
895             // Change the contents of the local file
896             source = getClass().getResourceAsStream("/testconfigs/local-config-update.xml");
897             FileUtil.writeToFile(source, localConfigFile);
898 
899             // Get the configuration again and verify that it picked up the update
900             config = mFactory.createConfigurationFromArgs(
901                     new String[] { localConfigFile.getAbsolutePath() });
902             if (!(config.getTests().get(0) instanceof StubOptionTest)) {
903                 fail(String.format("Expected a StubOptionTest, but got %s",
904                         config.getTests().get(0).getClass().getName()));
905                 return;
906             }
907             test = (StubOptionTest)config.getTests().get(0);
908             assertEquals("valueFromUpdatedConfig", test.mOption);
909         } finally {
910             FileUtil.deleteFile(localConfigFile);
911         }
912     }
913 
914     /** Test loading a config that has a circular include */
915     @Test
testCreateConfigurationFromArgs_circularInclude()916     public void testCreateConfigurationFromArgs_circularInclude() throws Exception {
917         try {
918             mFactory.createConfigurationFromArgs(new String[] {"circular-config"});
919             fail("ConfigurationException not thrown");
920         } catch (ConfigurationException e) {
921             // expected
922         }
923     }
924 
925     /**
926      * If a template:map argument is passed but doesn't match any {@code <template-include>} tag a
927      * configuration exception will be thrown for unmatched arguments.
928      */
929     @Test
testCreateConfigurationFromArgs_templateName_notExist()930     public void testCreateConfigurationFromArgs_templateName_notExist() throws Exception {
931         final String configName = "include-template-config-with-default";
932         final String targetName = "test-config";
933         final String missingNameTemplate = "NOTEXISTINGNAME";
934         Map<String, String> expected = new HashMap<String,String>();
935         expected.put(missingNameTemplate, targetName);
936         final String expError = String.format(
937                 "Unused template:map parameters: %s", expected);
938 
939         try {
940             mFactory.createConfigurationFromArgs(new String[]{configName,
941                     "--template:map", missingNameTemplate, targetName});
942             fail ("ConfigurationException not thrown");
943         } catch (ConfigurationException e) {
944             // Make sure that we get the expected error message
945             assertEquals(expError, e.getMessage());
946         }
947     }
948 
949     /**
950      * If a configuration is called a second time, ensure that the cached config is also properly
951      * returned, and that template:map did not cause issues.
952      */
953     @Test
testCreateConfigurationFromArgs_templateName_notExistTest()954     public void testCreateConfigurationFromArgs_templateName_notExistTest() throws Exception {
955         final String configName = "template-include-config-with-default";
956         final String targetName = "local-config";
957         final String nameTemplate = "target";
958         IConfiguration tmp = null;
959         try {
960             tmp = mFactory.createConfigurationFromArgs(new String[]{configName,
961                     "--template:map", nameTemplate, targetName});
962         } catch (ConfigurationException e) {
963             fail("ConfigurationException thrown: " + e.getMessage());
964         }
965         assertTrue(tmp.getTests().size() == 2);
966 
967         // Call the same config a second time to make sure the cached version works.
968         try {
969             tmp = mFactory.createConfigurationFromArgs(new String[]{configName,
970                     "--template:map", nameTemplate, targetName});
971         } catch (ConfigurationException e) {
972             fail("ConfigurationException thrown: " + e.getMessage());
973         }
974         assertTrue(tmp.getTests().size() == 2);
975     }
976 
977     /**
978      * If a configuration is called a second time with bad template name, it should still throw the
979      * unused config template:map
980      */
981     @Test
testCreateConfigurationFromArgs_templateName_stillThrow()982     public void testCreateConfigurationFromArgs_templateName_stillThrow() throws Exception {
983         final String configName = "template-include-config-with-default";
984         final String targetName = "local-config";
985         final String nameTemplate = "target_not_exist";
986         try {
987             mFactory.createConfigurationFromArgs(new String[]{configName,
988                     "--template:map", nameTemplate, targetName});
989             fail("ConfigurationException should have been thrown");
990         } catch (ConfigurationException e) {
991             // expected
992         }
993 
994         // Call the same config a second time to make sure it is also rejected.
995         try {
996             mFactory.createConfigurationFromArgs(new String[]{configName,
997                     "--template:map", nameTemplate, targetName});
998             fail("ConfigurationException should have been thrown");
999         } catch (ConfigurationException e) {
1000             // expected
1001         }
1002     }
1003 
1004     /** Parse a config with 3 different device configuration specified. */
1005     @Test
testCreateConfigurationFromArgs_multidevice()1006     public void testCreateConfigurationFromArgs_multidevice() throws Exception {
1007         IConfiguration config = mFactory.createConfigurationFromArgs(
1008                 new String[]{"multi-device"});
1009         assertEquals(1, config.getTests().size());
1010         assertTrue(config.getTests().get(0) instanceof StubOptionTest);
1011         // Verify that all attributes are in the right place:
1012         assertNotNull(config.getDeviceConfigByName("device1"));
1013         assertEquals("10", config.getDeviceConfigByName("device1")
1014                 .getBuildProvider().getBuild().getBuildId());
1015         assertEquals("stub", config.getDeviceConfigByName("device1")
1016                 .getBuildProvider().getBuild().getTestTag());
1017         assertEquals(0, config.getDeviceConfigByName("device1")
1018                 .getTargetPreparers().size());
1019 
1020         assertNotNull(config.getDeviceConfigByName("device2"));
1021         assertEquals("0", config.getDeviceConfigByName("device2")
1022                 .getBuildProvider().getBuild().getBuildId());
1023         assertEquals("stub", config.getDeviceConfigByName("device2")
1024                 .getBuildProvider().getBuild().getTestTag());
1025         assertEquals(1, config.getDeviceConfigByName("device2")
1026                 .getTargetPreparers().size());
1027         assertTrue(config.getDeviceConfigByName("device2")
1028                 .getTargetPreparers().get(0) instanceof StubTargetPreparer);
1029 
1030         assertNotNull(config.getDeviceConfigByName("device3"));
1031         assertEquals("0", config.getDeviceConfigByName("device3")
1032                 .getBuildProvider().getBuild().getBuildId());
1033         assertEquals("build-flavor3", config.getDeviceConfigByName("device3")
1034                 .getBuildProvider().getBuild().getBuildFlavor());
1035         assertEquals(2, config.getDeviceConfigByName("device3")
1036                 .getTargetPreparers().size());
1037         assertTrue(config.getDeviceConfigByName("device3")
1038                 .getTargetPreparers().get(0) instanceof StubTargetPreparer);
1039     }
1040 
1041     /**
1042      * Test that if an object inside a <device> tag is implementing {@link IConfigurationReceiver}
1043      * it will receives the config properly like non-device object.
1044      */
1045     @Test
testCreateConfigurationFromArgs_injectConfiguration()1046     public void testCreateConfigurationFromArgs_injectConfiguration() throws Exception {
1047         IConfiguration config = mFactory.createConfigurationFromArgs(new String[] {"multi-device"});
1048         assertEquals(1, config.getTests().size());
1049 
1050         assertNotNull(config.getDeviceConfigByName("device2"));
1051         assertEquals(1, config.getDeviceConfigByName("device2").getTargetPreparers().size());
1052         assertTrue(
1053                 config.getDeviceConfigByName("device2").getTargetPreparers().get(0)
1054                         instanceof StubTargetPreparer);
1055         StubTargetPreparer stubDevice2 =
1056                 (StubTargetPreparer)
1057                         config.getDeviceConfigByName("device2").getTargetPreparers().get(0);
1058         assertEquals(config, stubDevice2.getConfiguration());
1059 
1060         assertNotNull(config.getDeviceConfigByName("device3"));
1061         assertEquals(2, config.getDeviceConfigByName("device3").getTargetPreparers().size());
1062         assertTrue(
1063                 config.getDeviceConfigByName("device3").getTargetPreparers().get(0)
1064                         instanceof StubTargetPreparer);
1065         StubTargetPreparer stubDevice3 =
1066                 (StubTargetPreparer)
1067                         config.getDeviceConfigByName("device3").getTargetPreparers().get(0);
1068         assertEquals(config, stubDevice3.getConfiguration());
1069     }
1070 
1071     /**
1072      * Parse a config with 3 different device configuration specified. And apply a command line to
1073      * override some attributes.
1074      */
1075     @Test
testCreateConfigurationFromArgs_multidevice_applyCommandLine()1076     public void testCreateConfigurationFromArgs_multidevice_applyCommandLine() throws Exception {
1077         IConfiguration config = mFactory.createConfigurationFromArgs(
1078                 new String[]{"multi-device", "--{device2}build-id","20", "--{device1}null-device",
1079                         "--{device3}com.android.tradefed.build.StubBuildProvider:build-id","30"});
1080         assertEquals(1, config.getTests().size());
1081         assertTrue(config.getTests().get(0) instanceof StubOptionTest);
1082         // Verify that all attributes are in the right place:
1083         assertNotNull(config.getDeviceConfigByName("device1"));
1084         assertEquals("10", config.getDeviceConfigByName("device1")
1085                 .getBuildProvider().getBuild().getBuildId());
1086         assertEquals("stub", config.getDeviceConfigByName("device1")
1087                 .getBuildProvider().getBuild().getTestTag());
1088         assertEquals(0, config.getDeviceConfigByName("device1")
1089                 .getTargetPreparers().size());
1090         assertTrue(config.getDeviceConfigByName("device1")
1091                 .getDeviceRequirements().nullDeviceRequested());
1092 
1093         assertNotNull(config.getDeviceConfigByName("device2"));
1094         // Device2 build provider is modified independently
1095         assertEquals("20", config.getDeviceConfigByName("device2")
1096                 .getBuildProvider().getBuild().getBuildId());
1097         assertEquals("stub", config.getDeviceConfigByName("device2")
1098                 .getBuildProvider().getBuild().getTestTag());
1099         assertEquals(1, config.getDeviceConfigByName("device2")
1100                 .getTargetPreparers().size());
1101         assertTrue(config.getDeviceConfigByName("device2")
1102                 .getTargetPreparers().get(0) instanceof StubTargetPreparer);
1103         assertFalse(config.getDeviceConfigByName("device2")
1104                 .getDeviceRequirements().nullDeviceRequested());
1105 
1106         // Device3 build provider is modified independently
1107         assertNotNull(config.getDeviceConfigByName("device3"));
1108         assertEquals("30", config.getDeviceConfigByName("device3")
1109                 .getBuildProvider().getBuild().getBuildId());
1110         assertEquals("build-flavor3", config.getDeviceConfigByName("device3")
1111                 .getBuildProvider().getBuild().getBuildFlavor());
1112         assertEquals(2, config.getDeviceConfigByName("device3")
1113                 .getTargetPreparers().size());
1114         assertTrue(config.getDeviceConfigByName("device3")
1115                 .getTargetPreparers().get(0) instanceof StubTargetPreparer);
1116         assertFalse(config.getDeviceConfigByName("device3")
1117                 .getDeviceRequirements().nullDeviceRequested());
1118     }
1119 
1120     /**
1121      * Parse a config with 3 different device configuration specified. And apply a command line to
1122      * override some attributes.
1123      */
1124     @Test
testCreateConfigurationFromArgs_multidevice_singletag()1125     public void testCreateConfigurationFromArgs_multidevice_singletag() throws Exception {
1126         IConfiguration config =
1127                 mFactory.createConfigurationFromArgs(
1128                         new String[] {
1129                             "multi-device-empty",
1130                             "--{device2}build-id",
1131                             "20",
1132                             "--{device1}null-device",
1133                             "--{device3}com.android.tradefed.build.StubBuildProvider:build-id",
1134                             "30"
1135                         });
1136         assertEquals(1, config.getTests().size());
1137         assertTrue(config.getTests().get(0) instanceof StubOptionTest);
1138         // Verify that all attributes are in the right place:
1139         assertNotNull(config.getDeviceConfigByName("device1"));
1140         assertEquals(
1141                 "0",
1142                 config.getDeviceConfigByName("device1").getBuildProvider().getBuild().getBuildId());
1143         assertEquals(
1144                 "stub",
1145                 config.getDeviceConfigByName("device1").getBuildProvider().getBuild().getTestTag());
1146         assertEquals(0, config.getDeviceConfigByName("device1").getTargetPreparers().size());
1147         assertTrue(
1148                 config.getDeviceConfigByName("device1")
1149                         .getDeviceRequirements()
1150                         .nullDeviceRequested());
1151 
1152         assertNotNull(config.getDeviceConfigByName("device2"));
1153         // Device2 build provider is modified independently
1154         assertEquals(
1155                 "20",
1156                 config.getDeviceConfigByName("device2").getBuildProvider().getBuild().getBuildId());
1157         assertEquals(
1158                 "stub",
1159                 config.getDeviceConfigByName("device2").getBuildProvider().getBuild().getTestTag());
1160         assertEquals(0, config.getDeviceConfigByName("device2").getTargetPreparers().size());
1161         assertFalse(
1162                 config.getDeviceConfigByName("device2")
1163                         .getDeviceRequirements()
1164                         .nullDeviceRequested());
1165 
1166         // Device3 build provider is modified independently
1167         assertNotNull(config.getDeviceConfigByName("device3"));
1168         assertEquals(
1169                 "30",
1170                 config.getDeviceConfigByName("device3").getBuildProvider().getBuild().getBuildId());
1171         assertEquals(0, config.getDeviceConfigByName("device3").getTargetPreparers().size());
1172         assertFalse(
1173                 config.getDeviceConfigByName("device3")
1174                         .getDeviceRequirements()
1175                         .nullDeviceRequested());
1176     }
1177 
1178     /**
1179      * Parse a config with 3 different device configuration specified. And apply a command line to
1180      * override all attributes.
1181      */
1182     @Test
testCreateConfigurationFromArgs_multidevice_applyCommandLineGlobal()1183     public void testCreateConfigurationFromArgs_multidevice_applyCommandLineGlobal()
1184             throws Exception {
1185         IConfiguration config = mFactory.createConfigurationFromArgs(
1186                 new String[]{"multi-device", "--build-id","20"});
1187         assertEquals(1, config.getTests().size());
1188         assertTrue(config.getTests().get(0) instanceof StubOptionTest);
1189         // Verify that all attributes are in the right place:
1190         // All build id are now modified since option had a global scope
1191         assertNotNull(config.getDeviceConfigByName("device1"));
1192         assertEquals("20", config.getDeviceConfigByName("device1")
1193                 .getBuildProvider().getBuild().getBuildId());
1194         assertEquals("stub", config.getDeviceConfigByName("device1")
1195                 .getBuildProvider().getBuild().getTestTag());
1196         assertEquals(0, config.getDeviceConfigByName("device1")
1197                 .getTargetPreparers().size());
1198 
1199         assertNotNull(config.getDeviceConfigByName("device2"));
1200         assertEquals("20", config.getDeviceConfigByName("device2")
1201                 .getBuildProvider().getBuild().getBuildId());
1202         assertEquals("stub", config.getDeviceConfigByName("device2")
1203                 .getBuildProvider().getBuild().getTestTag());
1204         assertEquals(1, config.getDeviceConfigByName("device2")
1205                 .getTargetPreparers().size());
1206         assertTrue(config.getDeviceConfigByName("device2")
1207                 .getTargetPreparers().get(0) instanceof StubTargetPreparer);
1208 
1209         assertNotNull(config.getDeviceConfigByName("device3"));
1210         assertEquals("20", config.getDeviceConfigByName("device3")
1211                 .getBuildProvider().getBuild().getBuildId());
1212         assertEquals("build-flavor3", config.getDeviceConfigByName("device3")
1213                 .getBuildProvider().getBuild().getBuildFlavor());
1214         assertEquals(2, config.getDeviceConfigByName("device3")
1215                 .getTargetPreparers().size());
1216         assertTrue(config.getDeviceConfigByName("device3")
1217                 .getTargetPreparers().get(0) instanceof StubTargetPreparer);
1218     }
1219 
1220     /**
1221      * Test that when <device> tags are out of order (device 1 - device 2 - device 1) and an option
1222      * is specified in the last device 1 with an increased frequency (a same class object from the
1223      * first device 1 or 2), the option is properly found and assigned.
1224      */
1225     @Test
testCreateConfigurationFromArgs_frequency()1226     public void testCreateConfigurationFromArgs_frequency() throws Exception {
1227         IConfiguration config =
1228                 mFactory.createConfigurationFromArgs(new String[] {"multi-device-mix"});
1229         assertNotNull(config.getDeviceConfigByName("device1"));
1230         assertEquals(3, config.getDeviceConfigByName("device1").getTargetPreparers().size());
1231         assertTrue(
1232                 config.getDeviceConfigByName("device1").getTargetPreparers().get(0)
1233                         instanceof DeviceWiper);
1234         DeviceWiper prep1 =
1235                 (DeviceWiper) config.getDeviceConfigByName("device1").getTargetPreparers().get(0);
1236         assertTrue(prep1.isDisabled());
1237         assertTrue(
1238                 config.getDeviceConfigByName("device1").getTargetPreparers().get(2)
1239                         instanceof DeviceWiper);
1240         DeviceWiper prep3 =
1241                 (DeviceWiper) config.getDeviceConfigByName("device1").getTargetPreparers().get(2);
1242         assertFalse(prep3.isDisabled());
1243 
1244         assertNotNull(config.getDeviceConfigByName("device2"));
1245         assertEquals(1, config.getDeviceConfigByName("device2").getTargetPreparers().size());
1246         assertTrue(
1247                 config.getDeviceConfigByName("device2").getTargetPreparers().get(0)
1248                         instanceof DeviceWiper);
1249         DeviceWiper prep2 =
1250                 (DeviceWiper) config.getDeviceConfigByName("device2").getTargetPreparers().get(0);
1251         // Only device 1 preparer has been targeted.
1252         assertTrue(prep2.isDisabled());
1253     }
1254 
1255     /**
1256      * Tests a different usage of options for a multi device interleaved config where options are
1257      * specified.
1258      */
1259     @Test
testCreateConfigurationFromArgs_frequency_withOptionOpen()1260     public void testCreateConfigurationFromArgs_frequency_withOptionOpen() throws Exception {
1261         IConfiguration config =
1262                 mFactory.createConfigurationFromArgs(new String[] {"multi-device-mix-options"});
1263         assertNotNull(config.getDeviceConfigByName("device1"));
1264         assertEquals(3, config.getDeviceConfigByName("device1").getTargetPreparers().size());
1265         assertTrue(
1266                 config.getDeviceConfigByName("device1").getTargetPreparers().get(0)
1267                         instanceof DeviceWiper);
1268         DeviceWiper prep1 =
1269                 (DeviceWiper) config.getDeviceConfigByName("device1").getTargetPreparers().get(0);
1270         assertTrue(prep1.isDisabled());
1271         assertTrue(
1272                 config.getDeviceConfigByName("device1").getTargetPreparers().get(2)
1273                         instanceof DeviceWiper);
1274         DeviceWiper prep3 =
1275                 (DeviceWiper) config.getDeviceConfigByName("device1").getTargetPreparers().get(2);
1276         assertFalse(prep3.isDisabled());
1277 
1278         assertNotNull(config.getDeviceConfigByName("device2"));
1279         assertEquals(1, config.getDeviceConfigByName("device2").getTargetPreparers().size());
1280         assertTrue(
1281                 config.getDeviceConfigByName("device2").getTargetPreparers().get(0)
1282                         instanceof DeviceWiper);
1283         DeviceWiper prep2 =
1284                 (DeviceWiper) config.getDeviceConfigByName("device2").getTargetPreparers().get(0);
1285         // Only device 1 preparer has been targeted.
1286         assertTrue(prep2.isDisabled());
1287     }
1288 
1289     /**
1290      * Configuration for multi device is wrong since it contains a build_provider tag outside the
1291      * devices tags.
1292      */
1293     @Test
testCreateConfigurationFromArgs_multidevice_exception()1294     public void testCreateConfigurationFromArgs_multidevice_exception() throws Exception {
1295         String expectedException =
1296                 "You seem to want a multi-devices configuration but you have "
1297                         + "[build_provider] tags outside the <device> tags";
1298         try {
1299             mFactory.createConfigurationFromArgs(new String[]{"multi-device-outside-tag"});
1300             fail("Should have thrown a Configuration Exception");
1301         } catch(ConfigurationException e) {
1302             assertEquals(expectedException, e.getMessage());
1303         }
1304     }
1305 
1306     /**
1307      * Parse a config with no multi device config, and expect the new device holder to still be
1308      * there and adding a default device.
1309      */
1310     @Test
testCreateConfigurationFromArgs_old_config_with_deviceHolder()1311     public void testCreateConfigurationFromArgs_old_config_with_deviceHolder() throws Exception {
1312         IConfiguration config = mFactory.createConfigurationFromArgs(
1313                 new String[]{"test-config", "--build-id","20", "--serial", "test"});
1314         assertEquals(1, config.getTests().size());
1315         assertTrue(config.getTests().get(0) instanceof StubOptionTest);
1316         // Verify that all attributes are in the right place:
1317         // All build id are now modified since option had a global scope
1318         assertNotNull(config.getDeviceConfigByName(ConfigurationDef.DEFAULT_DEVICE_NAME));
1319         assertEquals("20", config.getDeviceConfigByName(ConfigurationDef.DEFAULT_DEVICE_NAME)
1320                 .getBuildProvider().getBuild().getBuildId());
1321         assertEquals("stub", config.getDeviceConfigByName(ConfigurationDef.DEFAULT_DEVICE_NAME)
1322                 .getBuildProvider().getBuild().getTestTag());
1323         assertEquals(1, config.getDeviceConfigByName(ConfigurationDef.DEFAULT_DEVICE_NAME)
1324                 .getTargetPreparers().size());
1325         List<String> serials = new ArrayList<String>();
1326         serials.add("test");
1327         assertEquals(serials, config.getDeviceRequirements().getSerials(null));
1328         assertEquals(
1329                 serials,
1330                 config.getDeviceConfigByName(ConfigurationDef.DEFAULT_DEVICE_NAME)
1331                         .getDeviceRequirements()
1332                         .getSerials(null));
1333     }
1334 
1335     /**
1336      * Test that when parsing command line options, boolean options with Device tag and namespace
1337      * are correctly assigned.
1338      */
1339     @Test
testCreateConfiguration_injectDeviceBooleanOption()1340     public void testCreateConfiguration_injectDeviceBooleanOption() throws Exception {
1341         IConfiguration config =
1342                 mFactory.createConfigurationFromArgs(
1343                         new String[] {
1344                             "test-config-multi",
1345                             "--{device1}no-test-boolean-option",
1346                             "--{device1}test-boolean-option-false",
1347                             // testing with namespace too
1348                             "--{device2}stub-preparer:no-test-boolean-option",
1349                             "--{device2}stub-preparer:test-boolean-option-false"
1350                         });
1351         assertEquals(2, config.getDeviceConfig().size());
1352         IDeviceConfiguration device1 = config.getDeviceConfigByName("device1");
1353         StubTargetPreparer deviceSetup1 = (StubTargetPreparer) device1.getTargetPreparers().get(0);
1354         // default value of test-boolean-option is true, we set it to false
1355         assertFalse(deviceSetup1.getTestBooleanOption());
1356         // default value of test-boolean-option-false is false, we set it to true.
1357         assertTrue(deviceSetup1.getTestBooleanOptionFalse());
1358 
1359         IDeviceConfiguration device2 = config.getDeviceConfigByName("device2");
1360         StubTargetPreparer deviceSetup2 = (StubTargetPreparer) device2.getTargetPreparers().get(0);
1361         assertFalse(deviceSetup2.getTestBooleanOption());
1362         assertTrue(deviceSetup2.getTestBooleanOptionFalse());
1363     }
1364 
1365     /** Test that when an <include> tag is used inside a <device> tag we correctly resolve it. */
1366     @Test
testCreateConfiguration_includeInDevice()1367     public void testCreateConfiguration_includeInDevice() throws Exception {
1368         IConfiguration config =
1369                 mFactory.createConfigurationFromArgs(
1370                         new String[] {"test-config-multi-include", "--test-dir", "faketestdir"});
1371         assertEquals(2, config.getDeviceConfig().size());
1372         IDeviceConfiguration device1 = config.getDeviceConfigByName("device1");
1373         assertTrue(device1.getTargetPreparers().get(0) instanceof StubTargetPreparer);
1374         // The included config in device2 loads a different build_provider
1375         IDeviceConfiguration device2 = config.getDeviceConfigByName("device2");
1376         assertTrue(device2.getBuildProvider() instanceof LocalDeviceBuildProvider);
1377         LocalDeviceBuildProvider provider = (LocalDeviceBuildProvider) device2.getBuildProvider();
1378         // command line options are properly propagated to the included object in device tag.
1379         assertEquals("faketestdir", provider.getTestDir().getName());
1380     }
1381 
1382     @Test
testPartialCreateMultiDevices()1383     public void testPartialCreateMultiDevices() throws Exception {
1384         IConfiguration config =
1385                 mFactory.createPartialConfigurationFromArgs(
1386                         new String[] {"test-config-multi-include", "--test-dir", "faketestdir"},
1387                         null,
1388                         ImmutableSet.of(Configuration.BUILD_PROVIDER_TYPE_NAME),
1389                         null);
1390         assertEquals(2, config.getDeviceConfig().size());
1391         IDeviceConfiguration device2 = config.getDeviceConfigByName("device2");
1392         assertTrue(device2.getBuildProvider() instanceof LocalDeviceBuildProvider);
1393     }
1394 
1395     /**
1396      * Test when an <include> tag tries to load a <device> tag inside another <device> tag. This
1397      * should throw an exception.
1398      */
1399     @Test
testCreateConfiguration_includeInDevice_inDevice()1400     public void testCreateConfiguration_includeInDevice_inDevice() throws Exception {
1401         try {
1402             mFactory.createConfigurationFromArgs(
1403                     new String[] {
1404                         "multi-device-incorrect-include",
1405                     });
1406             fail("Should have thrown an exception.");
1407         } catch (ConfigurationException expected) {
1408             assertEquals(
1409                     "<device> tag cannot be included inside another device", expected.getMessage());
1410         }
1411     }
1412 
1413     /** Test that {@link ConfigurationFactory#reorderArgs(String[])} is properly reordering args. */
1414     @Test
testReorderArgs_check_ordering()1415     public void testReorderArgs_check_ordering() throws Throwable {
1416         String[] args =
1417                 new String[] {
1418                     "config",
1419                     "--option1",
1420                     "o1",
1421                     "--template:map",
1422                     "tm=tm1",
1423                     "--option2",
1424                     "--option3",
1425                     "o3",
1426                     "--template:map",
1427                     "tm",
1428                     "tm2"
1429                 };
1430         String[] wantArgs =
1431                 new String[] {
1432                     "config",
1433                     "--template:map",
1434                     "tm=tm1",
1435                     "--template:map",
1436                     "tm",
1437                     "tm2",
1438                     "--option1",
1439                     "o1",
1440                     "--option2",
1441                     "--option3",
1442                     "o3"
1443                 };
1444 
1445         assertEquals(Arrays.toString(wantArgs), Arrays.toString(mFactory.reorderArgs(args)));
1446     }
1447 
1448     /**
1449      * Test that {@link ConfigurationFactory#reorderArgs(String[])} properly handles a short arg
1450      * after a template arg.
1451      */
1452     @Test
testReorderArgs_template_with_short_arg()1453     public void testReorderArgs_template_with_short_arg() throws Throwable {
1454         String[] args =
1455                 new String[] {
1456                     "config",
1457                     "--option1",
1458                     "o1",
1459                     "--template:map",
1460                     "tm=tm1",
1461                     "-option2",
1462                     "--option3",
1463                     "o3",
1464                     "--template:map",
1465                     "tm",
1466                     "tm2"
1467                 };
1468         String[] wantArgs =
1469                 new String[] {
1470                     "config",
1471                     "--template:map",
1472                     "tm=tm1",
1473                     "--template:map",
1474                     "tm",
1475                     "tm2",
1476                     "--option1",
1477                     "o1",
1478                     "-option2",
1479                     "--option3",
1480                     "o3"
1481                 };
1482 
1483         assertEquals(Arrays.toString(wantArgs), Arrays.toString(mFactory.reorderArgs(args)));
1484     }
1485 
1486     /**
1487      * Test that {@link ConfigurationFactory#reorderArgs(String[])} properly handles a incomplete
1488      * template arg.
1489      */
1490     @Test
testReorderArgs_incomplete_template_arg()1491     public void testReorderArgs_incomplete_template_arg() throws Throwable {
1492         String[] args =
1493                 new String[] {
1494                     "config",
1495                     "--option1",
1496                     "o1",
1497                     "--template:map",
1498                     "tm=tm1",
1499                     "-option2",
1500                     "--option3",
1501                     "o3",
1502                     "--template:map",
1503                 };
1504         String[] wantArgs =
1505                 new String[] {
1506                     "config",
1507                     "--template:map",
1508                     "tm=tm1",
1509                     "--template:map",
1510                     "--option1",
1511                     "o1",
1512                     "-option2",
1513                     "--option3",
1514                     "o3"
1515                 };
1516 
1517         assertEquals(Arrays.toString(wantArgs), Arrays.toString(mFactory.reorderArgs(args)));
1518     }
1519 
1520     /**
1521      * Test that when doing a dry-run with keystore arguments, we skip the keystore validation. We
1522      * accept the argument, as long as the key exists.
1523      */
1524     @Test
testCreateConfigurationFromArgs_dryRun_keystore()1525     public void testCreateConfigurationFromArgs_dryRun_keystore() throws Exception {
1526         IConfiguration res =
1527                 mFactory.createConfigurationFromArgs(
1528                         new String[] {
1529                             "test-config",
1530                             "--build-id",
1531                             "USE_KEYSTORE@test_string",
1532                             "--dry-run",
1533                             "--online-wait-time=USE_KEYSTORE@test_long",
1534                             "--min-battery-after-recovery",
1535                             "USE_KEYSTORE@test_int",
1536                             "--disable-unresponsive-reboot=USE_KEYSTORE@test_boolean",
1537                         });
1538         res.validateOptions();
1539         // we still throw exception if the option itself doesn't exists.
1540         try {
1541             mFactory.createConfigurationFromArgs(
1542                     new String[] {
1543                         "test-config", "--does-not-exists", "USE_KEYSTORE@test_string", "--dry-run"
1544                     });
1545             fail("Should have thrown an exception.");
1546         } catch (ConfigurationException expected) {
1547             // expected
1548         }
1549     }
1550 
1551     /**
1552      * Test that when mandatory option are set with a keystore during a dry-run, they can still be
1553      * validated.
1554      */
1555     @Test
testCreateConfigurationFromArgs_dryRun_keystore_required_arg()1556     public void testCreateConfigurationFromArgs_dryRun_keystore_required_arg() throws Exception {
1557         IConfiguration res =
1558                 mFactory.createConfigurationFromArgs(
1559                         new String[] {
1560                             "mandatory-config",
1561                             "--build-dir",
1562                             "USE_KEYSTORE@test_string",
1563                             "--dry-run",
1564                         });
1565         // Check that mandatory option was properly set, otherwise it will throw.
1566         res.validateOptions();
1567     }
1568 
1569     /**
1570      * This unit test ensures that the code will search for missing test configs in directories
1571      * specified in certain environment variables.
1572      */
1573     @Test
testSearchConfigFromEnvVar()1574     public void testSearchConfigFromEnvVar() throws IOException {
1575         File externalConfig = FileUtil.createTempFile("external-config", ".config");
1576         String configName = FileUtil.getBaseName(externalConfig.getName());
1577         File tmpDir = externalConfig.getParentFile();
1578 
1579         ConfigurationFactory spyFactory = Mockito.spy(mFactory);
1580         Mockito.doReturn(Arrays.asList(tmpDir)).when(spyFactory).getExternalTestCasesDirs();
1581 
1582         try {
1583             File config = spyFactory.getTestCaseConfigPath(configName);
1584             assertEquals(config.getAbsolutePath(), externalConfig.getAbsolutePath());
1585         } finally {
1586             FileUtil.deleteFile(externalConfig);
1587         }
1588     }
1589 
1590     /**
1591      * This unit test ensures that the code will search for missing test configs in directories
1592      * specified in certain environment variables, and fail as the test config still can't be found.
1593      */
1594     @Test
testSearchConfigFromEnvVarFailed()1595     public void testSearchConfigFromEnvVarFailed() throws Exception {
1596         File tmpDir = FileUtil.createTempDir("config-check-var");
1597         try {
1598             ConfigurationFactory spyFactory = Mockito.spy(mFactory);
1599             Mockito.doReturn(Arrays.asList(tmpDir)).when(spyFactory).getExternalTestCasesDirs();
1600             File config = spyFactory.getTestCaseConfigPath("non-exist");
1601             assertNull(config);
1602             Mockito.verify(spyFactory, Mockito.times(1)).getExternalTestCasesDirs();
1603         } finally {
1604             FileUtil.recursiveDelete(tmpDir);
1605         }
1606     }
1607 
1608     /**
1609      * Tests that {@link ConfigurationFactory#getConfigNamesFromTestCases(String)} returns the
1610      * proper files of the subpath only.
1611      */
1612     @Test
testGetConfigNamesFromTestCases_subpath()1613     public void testGetConfigNamesFromTestCases_subpath() throws Exception {
1614         File tmpDir = FileUtil.createTempDir("test-config-dir");
1615         try {
1616             File config1 = FileUtil.createTempFile("testconfig1", ".config", tmpDir);
1617             FileUtil.writeToFile("<configuration></configuration>", config1);
1618             File subDir = FileUtil.createTempDir("subdir", tmpDir);
1619             File config2 = FileUtil.createTempFile("testconfig2", ".xml", subDir);
1620             FileUtil.writeToFile("<configuration></configuration>", config2);
1621             ConfigurationFactory spyFactory = Mockito.spy(mFactory);
1622             Mockito.doReturn(Arrays.asList(tmpDir)).when(spyFactory).getExternalTestCasesDirs();
1623             // looking at full path we get both configs
1624             Set<String> res = spyFactory.getConfigNamesFromTestCases(null);
1625             assertEquals(2, res.size());
1626             res = spyFactory.getConfigNamesFromTestCases(subDir.getName());
1627             assertEquals(1, res.size());
1628             assertTrue(res.iterator().next().contains("testconfig2"));
1629         } finally {
1630             FileUtil.recursiveDelete(tmpDir);
1631         }
1632     }
1633 
1634     /**
1635      * Test that if the base configuration is single device and a template attempt to make it
1636      * multi-devices, it will fail and provide a clear message about which tags are not in the right
1637      * place.
1638      */
1639     @Test
testCreateConfigurationFromArgs_singleDeviceTemplate_includeMulti()1640     public void testCreateConfigurationFromArgs_singleDeviceTemplate_includeMulti()
1641             throws Exception {
1642         try {
1643             mFactory.createConfigurationFromArgs(
1644                     new String[] {
1645                         "local-preparer-base", "--template:map", "config", "test-config-multi"
1646                     });
1647             fail("Should have thrown an exception");
1648         } catch (ConfigurationException expected) {
1649             assertEquals(
1650                     "You seem to want a multi-devices configuration but you have "
1651                             + "[target_preparer] tags outside the <device> tags",
1652                     expected.getMessage());
1653         }
1654     }
1655 
1656     /**
1657      * Opposite scenario of {@link
1658      * #testCreateConfigurationFromArgs_singleDeviceTemplate_includeMulti()} where the base template
1659      * is multi-devices and the template included has a single device tag.
1660      */
1661     @Test
testCreateConfigurationFromArgs_multiDeviceTemplate_includeSingle()1662     public void testCreateConfigurationFromArgs_multiDeviceTemplate_includeSingle()
1663             throws Exception {
1664         try {
1665             mFactory.createConfigurationFromArgs(
1666                     new String[] {
1667                         "multi-device", "--template:map", "preparers", "local-preparer-base"
1668                     });
1669             fail("Should have thrown an exception");
1670         } catch (ConfigurationException expected) {
1671             assertEquals(
1672                     "You seem to want a multi-devices configuration but you have "
1673                             + "[target_preparer] tags outside the <device> tags",
1674                     expected.getMessage());
1675         }
1676     }
1677 
1678     /**
1679      * Test that if we load a configuration with some unfound class, we throw an exception and
1680      * report the objects that did not load.
1681      */
1682     @Test
testCreateConfigurationFromArgs_failedToLoadClass()1683     public void testCreateConfigurationFromArgs_failedToLoadClass() throws Exception {
1684         try {
1685             mFactory.createConfigurationFromArgs(new String[] {"multi-device-incorrect"});
1686             fail("Should have thrown an exception");
1687         } catch (ClassNotFoundConfigurationException expected) {
1688             assertTrue(
1689                     expected.getMessage()
1690                             .contains(
1691                                     "Failed to load some objects in the configuration "
1692                                             + "'multi-device-incorrect':"));
1693             assertEquals(
1694                     "device1:build_provider",
1695                     expected.getRejectedObjects().get("com.android.tradefed.build.doesnotexists"));
1696             assertEquals(
1697                     "device3:target_preparer",
1698                     expected.getRejectedObjects()
1699                             .get("com.android.tradefed.targetprep.doesnotexistseither"));
1700         }
1701     }
1702 
1703     /**
1704      * Test that a configuration with one real device and one fake device (isFake=true) will be
1705      * loaded like a single device config: The objects outside the <device> tags will be part of the
1706      * real device.
1707      */
1708     @Test
testCreateConfiguration_multiDevice_fake()1709     public void testCreateConfiguration_multiDevice_fake() throws Exception {
1710         IConfiguration config =
1711                 mFactory.createConfigurationFromArgs(
1712                         new String[] {
1713                             "test-config-multi-fake",
1714                             "--{device1}no-test-boolean-option",
1715                             "--{device1}test-boolean-option-false",
1716                             // testing with namespace too
1717                             "--{device1}stub-preparer:no-test-boolean-option",
1718                             "--{device1}stub-preparer:test-boolean-option-false"
1719                         });
1720         assertEquals(2, config.getDeviceConfig().size());
1721         IDeviceConfiguration device1 = config.getDeviceConfigByName("device1");
1722         // One target preparer from inside the device tag, one from outside.
1723         assertEquals(2, device1.getTargetPreparers().size());
1724         StubTargetPreparer deviceSetup1 = (StubTargetPreparer) device1.getTargetPreparers().get(0);
1725         // default value of test-boolean-option is true, we set it to false
1726         assertFalse(deviceSetup1.getTestBooleanOption());
1727         // default value of test-boolean-option-false is false, we set it to true.
1728         assertTrue(deviceSetup1.getTestBooleanOptionFalse());
1729 
1730         // Check that the second preparer, outside device1 can still receive option as {device1}.
1731         StubTargetPreparer deviceSetup2 = (StubTargetPreparer) device1.getTargetPreparers().get(1);
1732         // default value of test-boolean-option is true, we set it to false
1733         assertFalse(deviceSetup2.getTestBooleanOption());
1734         // default value of test-boolean-option-false is false, we set it to true.
1735         assertTrue(deviceSetup2.getTestBooleanOptionFalse());
1736 
1737         assertFalse(config.isDeviceConfiguredFake("device1"));
1738         assertTrue(config.isDeviceConfiguredFake("device2"));
1739     }
1740 
1741     /**
1742      * Test when a single device configuration (standard flat) add a fake device. Configuration
1743      * objects should be added to the default device.
1744      */
1745     @Test
testCreateConfiguration_singleDeviceConfig_withFake()1746     public void testCreateConfiguration_singleDeviceConfig_withFake() throws Exception {
1747         IConfiguration config =
1748                 mFactory.createConfigurationFromArgs(
1749                         new String[] {
1750                             "single-config-and-fake",
1751                             "--no-test-boolean-option",
1752                             "--test-boolean-option-false",
1753                             // testing with namespace too
1754                             "--stub-preparer:no-test-boolean-option",
1755                             "--stub-preparer:test-boolean-option-false"
1756                         });
1757         assertEquals(2, config.getDeviceConfig().size());
1758         // Ensure the first device is the default one.
1759         assertEquals(
1760                 ConfigurationDef.DEFAULT_DEVICE_NAME,
1761                 config.getDeviceConfig().get(0).getDeviceName());
1762 
1763         IDeviceConfiguration device1 =
1764                 config.getDeviceConfigByName(ConfigurationDef.DEFAULT_DEVICE_NAME);
1765         // One target preparer from inside the device tag, one from outside.
1766         assertEquals(2, device1.getTargetPreparers().size());
1767         StubTargetPreparer deviceSetup1 = (StubTargetPreparer) device1.getTargetPreparers().get(0);
1768         // default value of test-boolean-option is true, we set it to false
1769         assertFalse(deviceSetup1.getTestBooleanOption());
1770         // default value of test-boolean-option-false is false, we set it to true.
1771         assertTrue(deviceSetup1.getTestBooleanOptionFalse());
1772         assertTrue(device1.getDeviceRequirements().gceDeviceRequested());
1773         assertFalse(device1.getDeviceRequirements().nullDeviceRequested());
1774 
1775         // Check that the second preparer, outside device1 can still receive option as {device1}.
1776         StubTargetPreparer deviceSetup2 = (StubTargetPreparer) device1.getTargetPreparers().get(1);
1777         // default value of test-boolean-option is true, we set it to false
1778         assertFalse(deviceSetup2.getTestBooleanOption());
1779         // default value of test-boolean-option-false is false, we set it to true.
1780         assertTrue(deviceSetup2.getTestBooleanOptionFalse());
1781 
1782         assertFalse(config.isDeviceConfiguredFake(ConfigurationDef.DEFAULT_DEVICE_NAME));
1783         assertTrue(config.isDeviceConfiguredFake("device2"));
1784         IDeviceConfiguration device2 = config.getDeviceConfigByName("device2");
1785         assertFalse(device2.getDeviceRequirements().gceDeviceRequested());
1786         assertTrue(device2.getDeviceRequirements().nullDeviceRequested());
1787     }
1788 
1789     /** Test that a configuration with all the device marked as isReal=false will be rejected. */
1790     @Test
testCreateConfiguration_multiDevice_real_notReal()1791     public void testCreateConfiguration_multiDevice_real_notReal() throws Exception {
1792         try {
1793             mFactory.createConfigurationFromArgs(new String[] {"test-config-real-not-real"});
1794             fail("Should have thrown an exception");
1795         } catch (ConfigurationException expected) {
1796             assertEquals(
1797                     "Failed to parse config xml 'test-config-real-not-real'. Reason: Mismatch for "
1798                             + "device 'device1'. It was defined once as isFake=false, once as "
1799                             + "isFake=true",
1800                     expected.getMessage());
1801         }
1802     }
1803 
1804     /**
1805      * Test that a configuration with two real devices and one fake one (isFake=false) will reject
1806      * object that are at the root: We cannot decide where to put these objects.
1807      */
1808     @Test
testCreateConfiguration_multiDevice_twoReal_oneFake()1809     public void testCreateConfiguration_multiDevice_twoReal_oneFake() throws Exception {
1810         try {
1811             mFactory.createConfigurationFromArgs(new String[] {"test-config-multi-3-fake"});
1812             fail("Should have thrown an exception");
1813         } catch (ConfigurationException expected) {
1814             assertEquals(
1815                     "You seem to want a multi-devices configuration but you have [target_preparer] "
1816                             + "tags outside the <device> tags",
1817                     expected.getMessage());
1818         }
1819     }
1820 
1821     /** Class to test out lab preparer parsing */
1822     public static final class TestLabPreparer extends StubTargetPreparer implements ILabPreparer {}
1823 
1824     @Test
testParse_labPreparer()1825     public void testParse_labPreparer() throws Exception {
1826         String normalConfig =
1827                 "<configuration description=\"desc\" >\n"
1828                         + "  <lab_preparer class=\""
1829                         + TestLabPreparer.class.getName()
1830                         + "\">\n"
1831                         + "     <option name=\"test-boolean-option\" value=\"false\"/>"
1832                         + "  </lab_preparer>\n"
1833                         + "</configuration>";
1834         File tmpConfig = FileUtil.createTempFile("tmp-config-tests", ".xml");
1835         try {
1836             FileUtil.writeToFile(normalConfig, tmpConfig);
1837             IConfiguration config =
1838                     mFactory.createConfigurationFromArgs(
1839                             new String[] {tmpConfig.getAbsolutePath()});
1840             assertEquals(1, config.getLabPreparers().size());
1841             assertFalse(
1842                     ((StubTargetPreparer) config.getLabPreparers().get(0)).getTestBooleanOption());
1843         } finally {
1844             FileUtil.deleteFile(tmpConfig);
1845         }
1846     }
1847 
1848     /**
1849      * Test that even if multi_pre_target_prep and multi_target_prep share the same type, we do not
1850      * mix the objects internally.
1851      */
1852     @Test
testParse_multiTargetPrep()1853     public void testParse_multiTargetPrep() throws Exception {
1854         String normalConfig =
1855                 "<configuration description=\"desc\" >\n"
1856                         + "  <multi_pre_target_preparer class=\""
1857                         + StubMultiTargetPreparer.class.getName()
1858                         + "\" />\n"
1859                         + "  <multi_target_preparer class=\""
1860                         + StubMultiTargetPreparer.class.getName()
1861                         + "\" />\n"
1862                         + "</configuration>";
1863         File tmpConfig = FileUtil.createTempFile("tmp-config-tests", ".xml");
1864         try {
1865             FileUtil.writeToFile(normalConfig, tmpConfig);
1866             IConfiguration config =
1867                     mFactory.createConfigurationFromArgs(
1868                             new String[] {tmpConfig.getAbsolutePath()});
1869             assertEquals(1, config.getMultiPreTargetPreparers().size());
1870             assertEquals(1, config.getMultiTargetPreparers().size());
1871             // Different objects have been created for each.
1872             assertNotSame(
1873                     config.getMultiPreTargetPreparers().get(0),
1874                     config.getMultiTargetPreparers().get(0));
1875         } finally {
1876             FileUtil.deleteFile(tmpConfig);
1877         }
1878     }
1879 
1880     /** Test that an unexpected extension for a config file doesn't parse. */
1881     @Test
testParseUnexpectedFormat()1882     public void testParseUnexpectedFormat() throws Exception {
1883         File testConfigFile = FileUtil.createTempFile("test-config-file", ".txt");
1884         try {
1885             mFactory.createConfigurationFromArgs(new String[] {testConfigFile.getAbsolutePath()});
1886             fail("Should have thrown an exception");
1887         } catch (ConfigurationException expected) {
1888             assertTrue(expected.getMessage().contains("not supported."));
1889         } finally {
1890             FileUtil.deleteFile(testConfigFile);
1891         }
1892     }
1893 
1894     /** Test that a YAML config command line parse correctly. */
1895     @Test
testCreateConfigurationFromArgs_yaml()1896     public void testCreateConfigurationFromArgs_yaml() throws Exception {
1897         IConfiguration config =
1898                 mFactory.createConfigurationFromArgs(
1899                         new String[] {
1900                             "yaml/test-config.tf_yaml",
1901                             "--build-id",
1902                             "5",
1903                             "--build-flavor",
1904                             "test",
1905                             "--branch",
1906                             "main"
1907                         });
1908         assertNotNull(config);
1909         IBuildProvider provider = config.getBuildProvider();
1910         assertTrue(provider instanceof IDeviceBuildProvider);
1911         IBuildInfo info = ((IDeviceBuildProvider) provider).getBuild(null);
1912         try {
1913             assertEquals("5", info.getBuildId());
1914             assertEquals("test", info.getBuildFlavor());
1915             assertEquals("main", info.getBuildBranch());
1916         } finally {
1917             info.cleanUp();
1918         }
1919     }
1920 
1921     /**
1922      * Test that the direct config regex is working as expected
1923      *
1924      * <p>This test contains examples that should fail the regex.
1925      */
1926     @Test
testIsDirectConfigurationFalses()1927     public void testIsDirectConfigurationFalses() throws Exception {
1928         Map<String, Boolean> testMatrixMap =
1929                 Map.ofEntries(
1930                         entry("yaml/test-config.tf_yaml", false),
1931                         entry("suite/test-mapping", false),
1932                         entry(
1933                                 "./out/host/linux-x86/testcases/HelloWorldHostTest/HelloWorldHostTest.config",
1934                                 false),
1935                         entry("gs/proxy-config", false));
1936 
1937         for (Map.Entry<String, Boolean> entry : testMatrixMap.entrySet()) {
1938             assertEquals(entry.getValue(), mFactory.isDirectConfiguration(entry.getKey()));
1939         }
1940     }
1941 
1942     /**
1943      * Test that the direct config regex is working as expected
1944      *
1945      * <p>This test contains examples that should match the regex.
1946      */
1947     @Test
testIsDirectConfigurationTrues()1948     public void testIsDirectConfigurationTrues() throws Exception {
1949         Map<String, Boolean> testMatrixMap =
1950                 Map.ofEntries(
1951                         entry("gs://tradefed_test_resources/configs/HelloWorldHostTest.xml", true),
1952                         entry("gs://other_teams_bucket/SomeOtherTest.xml", true),
1953                         entry(
1954                                 "https://android-build.googleplex.com/objects/configs/SomeABConfig.xml",
1955                                 true),
1956                         entry("http://source.android.com/code/AConfig.config", true),
1957                         entry("file://localhost/home/tradefed/config.xml", true));
1958 
1959         for (Map.Entry<String, Boolean> entry : testMatrixMap.entrySet()) {
1960             assertEquals(entry.getValue(), mFactory.isDirectConfiguration(entry.getKey()));
1961         }
1962     }
1963 
1964     /** Test that the direct configuration method is minimally working */
1965     @Test
testLoadDirectConfiguration()1966     public void testLoadDirectConfiguration() throws Exception {
1967         ConfigurationFactory spyFactory = Mockito.spy(mRealFactory);
1968         // extract the test-config.xml into a tmp file
1969         InputStream configStream =
1970                 getClass().getResourceAsStream(String.format("/testconfigs/%s.xml", TEST_CONFIG));
1971         File tmpFile = FileUtil.createTempFile(TEST_CONFIG, ".xml");
1972         ResolvedFile resolvedFile = new ResolvedFile(tmpFile);
1973         String cfgPath = "gs://tradefed_test_resources/configs/test-config.xml";
1974         try {
1975             FileUtil.writeToFile(configStream, tmpFile);
1976 
1977             // Inject it into the direct config resolver, then try to load a direct config
1978             URI cfgUri = new URI(cfgPath);
1979             Mockito.doReturn(resolvedFile)
1980                     .when(spyFactory)
1981                     .resolveRemoteFile(Mockito.eq(cfgUri), Mockito.<URI>any());
1982 
1983             String args[] = {cfgPath, "--null-device"};
1984             IConfiguration config = spyFactory.createConfigurationFromArgs(args);
1985 
1986             assertNotNull(config);
1987 
1988             // ensure it looks like what we'd expect
1989             assertTrue(config.getDeviceRequirements().nullDeviceRequested());
1990             assertEquals(1, config.getTests().size());
1991             assertTrue(config.getTests().get(0) instanceof StubOptionTest);
1992         } finally {
1993             FileUtil.deleteFile(tmpFile);
1994         }
1995     }
1996 
getClassName(String name)1997     private static String getClassName(String name) {
1998         // -6 because of .class
1999         return name.substring(0, name.length() - 6).replace('/', '.');
2000     }
2001 
getClassInContribJar()2002     private Map<String, List<String>> getClassInContribJar() throws IOException {
2003         Map<String, List<String>> jarToObject = new LinkedHashMap<String, List<String>>();
2004         for (File jar : getListOfBuiltJars()) {
2005             List<String> objects = new ArrayList<>();
2006             JarFile jarFile = null;
2007             try {
2008                 jarFile = new JarFile(jar);
2009                 Enumeration<JarEntry> e = jarFile.entries();
2010 
2011                 while (e.hasMoreElements()) {
2012                     JarEntry je = e.nextElement();
2013                     if (je.isDirectory()
2014                             || !je.getName().endsWith(".class")
2015                             || je.getName().contains("$")) {
2016                         continue;
2017                     }
2018                     String className = getClassName(je.getName());
2019                     objects.add(className);
2020                 }
2021             } finally {
2022                 StreamUtil.close(jarFile);
2023             }
2024             jarToObject.put(jar.getName(), objects);
2025         }
2026         return jarToObject;
2027     }
2028 
getListOfBuiltJars()2029     private List<File> getListOfBuiltJars() {
2030         // testJarPath is the path of the jar file that contains this test
2031         // class.  We assume the other jars live in the same dir as this test
2032         // class' jar.
2033         String testJarPath =
2034                 ConfigurationFactoryTest.class
2035                         .getProtectionDomain()
2036                         .getCodeSource()
2037                         .getLocation()
2038                         .getPath();
2039         File jarFilePath = new File(testJarPath);
2040         String jarFileParentPath = jarFilePath.getParent();
2041         List<File> listOfJars = new ArrayList<File>();
2042         File jarToCheck;
2043         for (String jar : JAR_TO_CHECK) {
2044             jarToCheck = new File(jarFileParentPath, jar);
2045             if (jarToCheck.exists()) {
2046                 listOfJars.add(jarToCheck);
2047             }
2048         }
2049         return listOfJars;
2050     }
2051 }
2052