1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.tradefed.testtype.suite;
17 
18 import com.android.tradefed.build.StubBuildProvider;
19 import com.android.tradefed.config.Configuration;
20 import com.android.tradefed.config.IConfiguration;
21 import com.android.tradefed.config.IDeviceConfiguration;
22 import com.android.tradefed.device.metric.FilePullerLogCollector;
23 import com.android.tradefed.device.metric.IMetricCollector;
24 import com.android.tradefed.result.TextResultReporter;
25 import com.android.tradefed.targetprep.ITargetPreparer;
26 import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
27 import com.android.tradefed.testtype.suite.module.BaseModuleController;
28 import com.android.tradefed.util.FileUtil;
29 import com.android.tradefed.util.ModuleTestTypeUtil;
30 
31 import java.io.File;
32 import java.io.IOException;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37 
38 /**
39  * This class will help validating that the {@link IConfiguration} loaded for the suite are meeting
40  * the expected requirements: - No Build providers - No Result reporters
41  */
42 public class ValidateSuiteConfigHelper {
43 
44     /**
45      * Special exemption list for some collectors. They would be overly cumbersome to be defined at
46      * the suite level.
47      */
48     private static final List<String> ALLOWED_COLLECTOR_IN_MODULE = new ArrayList<>();
49 
50     private static final String XML_COMMENT_REGEX = "<!--(\n|.)*?-->";
51     // Matches both 'include' and 'template-include' tags.
52     private static final String INCLUDE_TAG_REGEX = "<\\s*(template-)?include\\s";
53     private static Pattern mIncludeTagPattern = null;
54 
55     static {
56         // This collector simply pull and log file from the device. it is useful at the module level
57         // so they can specify which 'key' is going to be watched to be pulled.
FilePullerLogCollector.class.getCanonicalName()58         ALLOWED_COLLECTOR_IN_MODULE.add(FilePullerLogCollector.class.getCanonicalName());
59     }
60 
ValidateSuiteConfigHelper()61     private ValidateSuiteConfigHelper() {}
62 
63     /**
64      * Check that a configuration is properly built to run in a suite.
65      *
66      * @param config a {@link IConfiguration} to be checked if valide for suite.
67      */
validateConfig(IConfiguration config)68     public static void validateConfig(IConfiguration config) {
69         if (config.getDeviceConfig().size() < 2) {
70             // Special handling for single device objects.
71             if (!config.getBuildProvider().getClass().isAssignableFrom(StubBuildProvider.class)) {
72                 throwRuntime(
73                         config,
74                         String.format(
75                                 "%s objects are not allowed in module.",
76                                 Configuration.BUILD_PROVIDER_TYPE_NAME));
77             }
78             checkTargetPrep(config, config.getTargetPreparers());
79         }
80         // if a multi device config is presented, ensure none of the devices define a build_provider
81         for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) {
82             if (!deviceConfig
83                     .getBuildProvider()
84                     .getClass()
85                     .isAssignableFrom(StubBuildProvider.class)) {
86                 throwRuntime(
87                         config,
88                         String.format(
89                                 "%s objects are not allowed in module.",
90                                 Configuration.BUILD_PROVIDER_TYPE_NAME));
91             }
92             checkTargetPrep(config, deviceConfig.getTargetPreparers());
93         }
94         if (config.getTestInvocationListeners().size() != 1) {
95             throwRuntime(
96                     config,
97                     String.format(
98                             "%s objects are not allowed in module.",
99                             Configuration.RESULT_REPORTER_TYPE_NAME));
100         }
101         if (!config.getTestInvocationListeners()
102                 .get(0)
103                 .getClass()
104                 .isAssignableFrom(TextResultReporter.class)) {
105             throwRuntime(
106                     config,
107                     String.format(
108                             "%s objects are not allowed in module.",
109                             Configuration.RESULT_REPORTER_TYPE_NAME));
110         }
111         // For now we do not allow pre-multi target preparers in modules.
112         if (!config.getMultiPreTargetPreparers().isEmpty()) {
113             throwRuntime(
114                     config,
115                     String.format(
116                             "%s objects are not allowed in module.",
117                             Configuration.MULTI_PRE_TARGET_PREPARER_TYPE_NAME));
118         }
119 
120         // Check multi target preparers
121         checkTargetPrep(config, config.getMultiTargetPreparers());
122 
123         // Check metric collectors if not a performance module
124         if (!ModuleTestTypeUtil.isPerformanceModule(config)) {
125             for (IMetricCollector collector : config.getMetricCollectors()) {
126                 // Only some collectors are allowed in the module
127                 if (!ALLOWED_COLLECTOR_IN_MODULE.contains(
128                         collector.getClass().getCanonicalName())) {
129                     throwRuntime(
130                             config,
131                             String.format(
132                                     "%s objects are not allowed in module. Except for: %s",
133                                     Configuration.DEVICE_METRICS_COLLECTOR_TYPE_NAME,
134                                     ALLOWED_COLLECTOR_IN_MODULE));
135                 }
136             }
137 
138             if (!config.getPostProcessors().isEmpty()) {
139                 throwRuntime(
140                         config,
141                         String.format(
142                                 "%s objects are not allowed in module.",
143                                 Configuration.METRIC_POST_PROCESSOR_TYPE_NAME));
144             }
145         }
146 
147         // Check that we validate the module_controller.
148         List<?> controllers = config.getConfigurationObjectList(ModuleDefinition.MODULE_CONTROLLER);
149         if (controllers != null) {
150             for (Object controller : controllers) {
151                 if (!(controller instanceof BaseModuleController)) {
152                     throwRuntime(
153                             config,
154                             String.format(
155                                     "%s object should be of type %s",
156                                     ModuleDefinition.MODULE_CONTROLLER,
157                                     BaseModuleController.class));
158                 }
159             }
160         }
161     }
162 
163     /**
164      * Check that a module config file is valid - no templates or includes
165      *
166      * @param configFile a config {@link File} to be validated for a suite.
167      */
validateConfigFile(File configFile)168     public static void validateConfigFile(File configFile) {
169         try {
170             if (mIncludeTagPattern == null) {
171                 mIncludeTagPattern = Pattern.compile(INCLUDE_TAG_REGEX);
172             }
173 
174             // Read config and remove all xml comments
175             String source = FileUtil.readStringFromFile(configFile);
176             source = source.replaceAll(XML_COMMENT_REGEX, "");
177 
178             // Find matches for 'template-include' or 'include' tags.
179             Matcher matcher = mIncludeTagPattern.matcher(source);
180             if (matcher.find()) {
181                 throw new RuntimeException(
182                         String.format(
183                                 "Found template-include or include tag in config file: %s",
184                                 configFile.getAbsolutePath()));
185             }
186         } catch (IOException e) {
187             throw new RuntimeException(
188                     String.format(
189                             "Failed to read file %s with exception %s",
190                             configFile.getAbsolutePath(), e));
191         }
192     }
193 
194     /**
195      * Check target_preparer and multi_target_preparer to ensure they do not extends each other as
196      * it could lead to some issues.
197      */
checkTargetPrep(IConfiguration config, List<?> targetPrepList)198     private static boolean checkTargetPrep(IConfiguration config, List<?> targetPrepList) {
199         for (Object o : targetPrepList) {
200             if (o instanceof ITargetPreparer && o instanceof IMultiTargetPreparer) {
201                 throwRuntime(
202                         config,
203                         String.format(
204                                 "%s is extending both %s and " + "%s",
205                                 o.getClass().getCanonicalName(),
206                                 Configuration.TARGET_PREPARER_TYPE_NAME,
207                                 Configuration.MULTI_PREPARER_TYPE_NAME));
208                 return false;
209             }
210         }
211         return true;
212     }
213 
throwRuntime(IConfiguration config, String msg)214     private static void throwRuntime(IConfiguration config, String msg) {
215         throw new RuntimeException(
216                 String.format(
217                         "Configuration %s cannot be run in a suite: %s", config.getName(), msg));
218     }
219 }
220