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