1 /*
2  * Copyright (C) 2020 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.yaml;
17 
18 import com.android.tradefed.command.CommandOptions;
19 import com.android.tradefed.config.Configuration;
20 import com.android.tradefed.config.ConfigurationDef;
21 import com.android.tradefed.config.ConfigurationException;
22 import com.android.tradefed.config.OptionSetter;
23 import com.android.tradefed.config.yaml.IDefaultObjectLoader.LoaderConfiguration;
24 
25 import com.google.common.collect.ImmutableList;
26 
27 import org.yaml.snakeyaml.Yaml;
28 import org.yaml.snakeyaml.error.YAMLException;
29 
30 import java.io.InputStream;
31 import java.util.HashSet;
32 import java.util.LinkedHashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.ServiceLoader;
37 import java.util.Set;
38 
39 /** Parser for YAML style Tradefed configurations */
40 public final class ConfigurationYamlParser {
41 
42     private static final String DESCRIPTION_KEY = "description";
43     public static final String DEPENDENCIES_KEY = "dependencies";
44     public static final String TESTS_KEY = "tests";
45 
46     private static final List<String> REQUIRED_KEYS =
47             ImmutableList.of(DESCRIPTION_KEY, DEPENDENCIES_KEY, TESTS_KEY);
48     private Set<String> mSeenKeys = new HashSet<>();
49     private boolean mCreatedAsModule = false;
50 
51     /**
52      * Main entry point of the parser to parse a given YAML file into Trade Federation objects.
53      *
54      * @param configDef
55      * @param source
56      * @param yamlInput
57      * @param createdAsModule
58      */
parse( ConfigurationDef configDef, String source, InputStream yamlInput, boolean createdAsModule)59     public void parse(
60             ConfigurationDef configDef,
61             String source,
62             InputStream yamlInput,
63             boolean createdAsModule)
64             throws ConfigurationException {
65         mCreatedAsModule = createdAsModule;
66         // We don't support multi-device in YAML
67         configDef.setMultiDeviceMode(false);
68         Yaml yaml = new Yaml();
69         try {
70             configDef.addOptionDef(
71                     CommandOptions.TEST_TAG_OPTION,
72                     null,
73                     source,
74                     source,
75                     Configuration.CMD_OPTIONS_TYPE_NAME);
76             Map<String, Object> yamlObjects = (Map<String, Object>) yaml.load(yamlInput);
77             translateYamlInTradefed(configDef, yamlObjects);
78         } catch (YAMLException e) {
79             throw new ConfigurationException(
80                     String.format("Failed to parse yaml file: '%s'.", source), e);
81         }
82     }
83 
translateYamlInTradefed( ConfigurationDef configDef, Map<String, Object> yamlObjects)84     private void translateYamlInTradefed(
85             ConfigurationDef configDef, Map<String, Object> yamlObjects)
86             throws ConfigurationException {
87         if (yamlObjects.containsKey(DESCRIPTION_KEY)) {
88             configDef.setDescription((String) yamlObjects.get(DESCRIPTION_KEY));
89             mSeenKeys.add(DESCRIPTION_KEY);
90         }
91         Set<String> dependencyFiles = new LinkedHashSet<>();
92         if (yamlObjects.containsKey(DEPENDENCIES_KEY)) {
93             YamlTestDependencies testDeps =
94                     new YamlTestDependencies(
95                             (List<Map<String, Object>>) yamlObjects.get(DEPENDENCIES_KEY));
96             dependencyFiles = convertDependenciesToObjects(configDef, testDeps);
97             mSeenKeys.add(DEPENDENCIES_KEY);
98         }
99         if (yamlObjects.containsKey(TESTS_KEY)) {
100             YamlTestRunners runnerInfo =
101                     new YamlTestRunners((List<Map<String, Object>>) yamlObjects.get(TESTS_KEY));
102             mSeenKeys.add(TESTS_KEY);
103             convertTestsToObjects(configDef, runnerInfo);
104         }
105 
106         if (!mSeenKeys.containsAll(REQUIRED_KEYS)) {
107             Set<String> missingKeys = new HashSet<>(REQUIRED_KEYS);
108             missingKeys.removeAll(mSeenKeys);
109             throw new ConfigurationException(
110                     String.format("'%s' keys are required and were not found.", missingKeys));
111         }
112 
113         // Add default configured objects
114         LoaderConfiguration loadConfiguration = new LoaderConfiguration();
115         loadConfiguration
116                 .setConfigurationDef(configDef)
117                 .addDependencies(dependencyFiles)
118                 .setCreatedAsModule(mCreatedAsModule);
119         ServiceLoader<IDefaultObjectLoader> serviceLoader =
120                 ServiceLoader.load(IDefaultObjectLoader.class);
121         for (IDefaultObjectLoader loader : serviceLoader) {
122             loader.addDefaultObjects(loadConfiguration);
123         }
124     }
125 
126     /**
127      * Converts the test dependencies into target_preparer objects.
128      *
129      * <p>TODO: Figure out a more robust way to map to target_preparers options.
130      *
131      * @return returns a list of all the dependency files.
132      */
convertDependenciesToObjects( ConfigurationDef def, YamlTestDependencies testDeps)133     private Set<String> convertDependenciesToObjects(
134             ConfigurationDef def, YamlTestDependencies testDeps) {
135         Set<String> dependencies = new LinkedHashSet<>();
136         List<String> apks = testDeps.apks();
137         if (!apks.isEmpty()) {
138             String className = "com.android.tradefed.targetprep.suite.SuiteApkInstaller";
139             int classCount =
140                     def.addConfigObjectDef(Configuration.TARGET_PREPARER_TYPE_NAME, className);
141             String optionName =
142                     String.format(
143                             "%s%c%d%c%s",
144                             className,
145                             OptionSetter.NAMESPACE_SEPARATOR,
146                             classCount,
147                             OptionSetter.NAMESPACE_SEPARATOR,
148                             "test-file-name");
149             for (String apk : apks) {
150                 def.addOptionDef(
151                         optionName,
152                         null,
153                         apk,
154                         def.getName(),
155                         Configuration.TARGET_PREPARER_TYPE_NAME);
156             }
157             dependencies.addAll(apks);
158         }
159 
160         Map<String, String> deviceFiles = testDeps.deviceFiles();
161         if (!deviceFiles.isEmpty()) {
162             String className = "com.android.tradefed.targetprep.PushFilePreparer";
163             int classCount =
164                     def.addConfigObjectDef(Configuration.TARGET_PREPARER_TYPE_NAME, className);
165             String optionName =
166                     String.format(
167                             "%s%c%d%c%s",
168                             className,
169                             OptionSetter.NAMESPACE_SEPARATOR,
170                             classCount,
171                             OptionSetter.NAMESPACE_SEPARATOR,
172                             "push-file");
173             for (Entry<String, String> toPush : deviceFiles.entrySet()) {
174                 def.addOptionDef(
175                         optionName,
176                         toPush.getKey(),
177                         toPush.getValue(),
178                         def.getName(),
179                         Configuration.TARGET_PREPARER_TYPE_NAME);
180                 dependencies.add(toPush.getKey());
181             }
182         }
183         // Add the non-apk and non-device files
184         dependencies.addAll(testDeps.files());
185         return dependencies;
186     }
187 
convertTestsToObjects(ConfigurationDef def, YamlTestRunners tests)188     private void convertTestsToObjects(ConfigurationDef def, YamlTestRunners tests) {
189         if (tests.getRunner() == null) {
190             return;
191         }
192         String className = tests.getRunner();
193         int classCount = def.addConfigObjectDef(Configuration.TEST_TYPE_NAME, className);
194         for (Entry<String, String> options : tests.getOptions().entries()) {
195             String optionName =
196                     String.format(
197                             "%s%c%d%c%s",
198                             className,
199                             OptionSetter.NAMESPACE_SEPARATOR,
200                             classCount,
201                             OptionSetter.NAMESPACE_SEPARATOR,
202                             options.getKey());
203             def.addOptionDef(
204                     optionName,
205                     null,
206                     options.getValue(),
207                     def.getName(),
208                     Configuration.TEST_TYPE_NAME);
209         }
210     }
211 }
212