Home | History | Annotate | Download | only in suite
      1 /*
      2  * Copyright (C) 2016 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.IDeviceBuildInfo;
     19 import com.android.tradefed.config.ConfigurationException;
     20 import com.android.tradefed.config.ConfigurationFactory;
     21 import com.android.tradefed.config.ConfigurationUtil;
     22 import com.android.tradefed.config.IConfiguration;
     23 import com.android.tradefed.config.IConfigurationFactory;
     24 import com.android.tradefed.config.Option;
     25 import com.android.tradefed.device.DeviceNotAvailableException;
     26 import com.android.tradefed.log.LogUtil.CLog;
     27 import com.android.tradefed.targetprep.ITargetPreparer;
     28 import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
     29 import com.android.tradefed.testtype.IAbi;
     30 import com.android.tradefed.testtype.IAbiReceiver;
     31 import com.android.tradefed.testtype.IRemoteTest;
     32 import com.android.tradefed.util.DirectedGraph;
     33 import com.android.tradefed.util.StreamUtil;
     34 import com.android.tradefed.util.ZipUtil2;
     35 
     36 import org.apache.commons.compress.archivers.zip.ZipFile;
     37 
     38 import java.io.File;
     39 import java.io.IOException;
     40 import java.util.ArrayList;
     41 import java.util.Arrays;
     42 import java.util.Collections;
     43 import java.util.LinkedHashMap;
     44 import java.util.List;
     45 import java.util.Set;
     46 
     47 /**
     48  * Implementation of {@link ITestSuite} which will load tests from TF jars res/config/suite/
     49  * folder.
     50  */
     51 public class TfSuiteRunner extends ITestSuite {
     52 
     53     private static final String CONFIG_EXT = ".config";
     54 
     55     @Option(name = "run-suite-tag", description = "The tag that must be run.",
     56             mandatory = true)
     57     private String mSuiteTag = null;
     58 
     59     @Option(
     60         name = "suite-config-prefix",
     61         description = "Search only configs with given prefix for suite tags."
     62     )
     63     private String mSuitePrefix = null;
     64 
     65     @Option(
     66         name = "additional-tests-zip",
     67         description = "Path to a zip file containing additional tests to be loaded."
     68     )
     69     private String mAdditionalTestsZip = null;
     70 
     71     private DirectedGraph<String> mLoadedConfigGraph = null;
     72 
     73     /** {@inheritDoc} */
     74     @Override
     75     public LinkedHashMap<String, IConfiguration> loadTests() {
     76         mLoadedConfigGraph = new DirectedGraph<>();
     77         return loadTests(null, mLoadedConfigGraph);
     78     }
     79 
     80     /**
     81      * Internal load configuration. Load configuration, expand sub suite runners and track cycle
     82      * inclusion to prevent infinite recursion.
     83      *
     84      * @param parentConfig the name of the config being loaded.
     85      * @param graph the directed graph tracking inclusion.
     86      * @return a map of module name and the configuration associated.
     87      */
     88     private LinkedHashMap<String, IConfiguration> loadTests(
     89             String parentConfig, DirectedGraph<String> graph) {
     90         LinkedHashMap <String, IConfiguration> configMap =
     91                 new LinkedHashMap<String, IConfiguration>();
     92         IConfigurationFactory configFactory = ConfigurationFactory.getInstance();
     93         // TODO: Do a better job searching for configs.
     94         // We do not load config from environment, they should be inside the testsDir of the build
     95         // info.
     96         List<String> configs = configFactory.getConfigList(mSuitePrefix, false);
     97         Set<IAbi> abis = null;
     98         if (getBuildInfo() instanceof IDeviceBuildInfo) {
     99             IDeviceBuildInfo deviceBuildInfo = (IDeviceBuildInfo) getBuildInfo();
    100             File testsDir = deviceBuildInfo.getTestsDir();
    101             if (testsDir != null) {
    102                 if (mAdditionalTestsZip != null) {
    103                     CLog.d(
    104                             "Extract general-tests.zip (%s) to tests directory.",
    105                             mAdditionalTestsZip);
    106                     ZipFile zip = null;
    107                     try {
    108                         zip = new ZipFile(mAdditionalTestsZip);
    109                         ZipUtil2.extractZip(zip, testsDir);
    110                     } catch (IOException e) {
    111                         RuntimeException runtimeException =
    112                                 new RuntimeException(
    113                                         String.format(
    114                                                 "IO error (%s) when unzipping general-tests.zip",
    115                                                 e.toString()),
    116                                         e);
    117                         throw runtimeException;
    118                     } finally {
    119                         StreamUtil.close(zip);
    120                     }
    121                 }
    122 
    123                 CLog.d(
    124                         "Loading extra test configs from the tests directory: %s",
    125                         testsDir.getAbsolutePath());
    126                 List<File> extraTestCasesDirs = Arrays.asList(testsDir);
    127                 configs.addAll(
    128                         ConfigurationUtil.getConfigNamesFromDirs(mSuitePrefix, extraTestCasesDirs));
    129             }
    130         }
    131         // Sort configs to ensure they are always evaluated and added in the same order.
    132         Collections.sort(configs);
    133         for (String configName : configs) {
    134             try {
    135                 IConfiguration testConfig =
    136                         configFactory.createConfigurationFromArgs(new String[]{configName});
    137                 if (testConfig.getConfigurationDescription().getSuiteTags().contains(mSuiteTag)) {
    138                     // If this config supports running against different ABIs we need to queue up
    139                     // multiple instances of this config.
    140                     if (canRunMultipleAbis(testConfig)) {
    141                         if (abis == null) {
    142                             try {
    143                                 abis = getAbis(getDevice());
    144                             } catch (DeviceNotAvailableException e) {
    145                                 throw new RuntimeException(e);
    146                             }
    147                         }
    148                         for (IAbi abi : abis) {
    149                             testConfig =
    150                                     configFactory.createConfigurationFromArgs(
    151                                             new String[] {configName});
    152                             String configNameAbi = abi.getName() + " " + configName;
    153                             for (IRemoteTest test : testConfig.getTests()) {
    154                                 if (test instanceof IAbiReceiver) {
    155                                     ((IAbiReceiver) test).setAbi(abi);
    156                                 }
    157                             }
    158                             for (ITargetPreparer preparer : testConfig.getTargetPreparers()) {
    159                                 if (preparer instanceof IAbiReceiver) {
    160                                     ((IAbiReceiver) preparer).setAbi(abi);
    161                                 }
    162                             }
    163                             for (IMultiTargetPreparer preparer :
    164                                     testConfig.getMultiTargetPreparers()) {
    165                                 if (preparer instanceof IAbiReceiver) {
    166                                     ((IAbiReceiver) preparer).setAbi(abi);
    167                                 }
    168                             }
    169                             LinkedHashMap<String, IConfiguration> expandedConfig =
    170                                     expandTestSuites(
    171                                             configNameAbi, testConfig, parentConfig, graph);
    172                             configMap.putAll(expandedConfig);
    173                         }
    174                     } else {
    175                         LinkedHashMap<String, IConfiguration> expandedConfig =
    176                                 expandTestSuites(configName, testConfig, parentConfig, graph);
    177                         configMap.putAll(expandedConfig);
    178                     }
    179                 }
    180             } catch (ConfigurationException | NoClassDefFoundError e) {
    181                 // Do not print the stack it's too verbose.
    182                 CLog.e("Configuration '%s' cannot be loaded, ignoring.", configName);
    183             }
    184         }
    185         return configMap;
    186     }
    187 
    188     /** Helper to determine if a test configuration can be ran against multiple ABIs. */
    189     private boolean canRunMultipleAbis(IConfiguration testConfig) {
    190         for (IRemoteTest test : testConfig.getTests()) {
    191             if (test instanceof IAbiReceiver) {
    192                 return true;
    193             }
    194         }
    195         for (ITargetPreparer preparer : testConfig.getTargetPreparers()) {
    196             if (preparer instanceof IAbiReceiver) {
    197                 return true;
    198             }
    199         }
    200         for (IMultiTargetPreparer preparer : testConfig.getMultiTargetPreparers()) {
    201             if (preparer instanceof IAbiReceiver) {
    202                 return true;
    203             }
    204         }
    205         return false;
    206     }
    207 
    208     /**
    209      * Helper to expand all TfSuiteRunner included in sub-configuration. Avoid having module inside
    210      * module if a suite is ran as part of another suite. Verifies against circular suite
    211      * dependencies.
    212      */
    213     private LinkedHashMap<String, IConfiguration> expandTestSuites(
    214             String configName,
    215             IConfiguration config,
    216             String parentConfig,
    217             DirectedGraph<String> graph) {
    218         LinkedHashMap<String, IConfiguration> configMap =
    219                 new LinkedHashMap<String, IConfiguration>();
    220         List<IRemoteTest> tests = new ArrayList<>(config.getTests());
    221         // In case some sub-config are suite too, we expand them to avoid weirdness
    222         // of modules inside modules.
    223         if (parentConfig != null) {
    224             graph.addEdge(parentConfig, configName);
    225             if (!graph.isDag()) {
    226                 CLog.e("%s", graph);
    227                 throw new RuntimeException(
    228                         String.format(
    229                                 "Circular configuration detected: %s has been included "
    230                                         + "several times.",
    231                                 configName));
    232             }
    233         }
    234         for (IRemoteTest test : tests) {
    235             if (test instanceof TfSuiteRunner) {
    236                 TfSuiteRunner runner = (TfSuiteRunner) test;
    237                 // Suite runner can only load and run stuff, if it has a suite tag set.
    238                 if (runner.getSuiteTag() != null) {
    239                     LinkedHashMap<String, IConfiguration> subConfigs =
    240                             runner.loadTests(configName, graph);
    241                     configMap.putAll(subConfigs);
    242                 } else {
    243                     CLog.w(
    244                             "Config %s does not have a suite-tag it cannot run anything.",
    245                             configName);
    246                 }
    247                 config.getTests().remove(test);
    248             }
    249         }
    250         // If we have any IRemoteTests remaining in the base configuration, it will run.
    251         if (!config.getTests().isEmpty()) {
    252             configMap.put(sanitizeConfigName(configName), config);
    253         }
    254         return configMap;
    255     }
    256 
    257     private String getSuiteTag() {
    258         return mSuiteTag;
    259     }
    260 
    261     /**
    262      * Sanitize the config name by sanitizing the module name and reattaching the abi if it is part
    263      * of the config name.
    264      */
    265     private String sanitizeConfigName(String originalName) {
    266         String[] segments;
    267         if (originalName.contains(" ")) {
    268             segments = originalName.split(" ");
    269             return segments[0] + " " + sanitizeModuleName(segments[1]);
    270         }
    271         return sanitizeModuleName(originalName);
    272     }
    273 
    274     /**
    275      * Some module names are currently the absolute path name of some config files. We want to
    276      * sanitize that look more like a short included config name.
    277      */
    278     private String sanitizeModuleName(String originalName) {
    279         if (originalName.endsWith(CONFIG_EXT)) {
    280             originalName = originalName.substring(0, originalName.length() - CONFIG_EXT.length());
    281         }
    282         if (!originalName.startsWith("/")) {
    283             return originalName;
    284         }
    285         // if it's an absolute path
    286         String[] segments = originalName.split("/");
    287         if (segments.length < 3) {
    288             return originalName;
    289         }
    290         // return last two segments only
    291         return String.join("/", segments[segments.length - 2], segments[segments.length - 1]);
    292     }
    293 }
    294