Home | History | Annotate | Download | only in dalvik
      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 
     17 package com.android.compatibility.dalvik;
     18 
     19 import com.android.compatibility.common.util.TestSuiteFilter;
     20 
     21 import dalvik.system.DexFile;
     22 import dalvik.system.PathClassLoader;
     23 
     24 import junit.framework.AssertionFailedError;
     25 import junit.framework.Test;
     26 import junit.framework.TestCase;
     27 import junit.framework.TestListener;
     28 import junit.framework.TestResult;
     29 import junit.framework.TestSuite;
     30 
     31 import java.io.File;
     32 import java.io.FileNotFoundException;
     33 import java.io.IOException;
     34 import java.lang.annotation.Annotation;
     35 import java.lang.reflect.Method;
     36 import java.lang.reflect.Modifier;
     37 import java.util.ArrayList;
     38 import java.util.Arrays;
     39 import java.util.Enumeration;
     40 import java.util.HashSet;
     41 import java.util.List;
     42 import java.util.Scanner;
     43 import java.util.Set;
     44 
     45 /**
     46  * Runs tests against the Dalvik VM.
     47  */
     48 public class DalvikTestRunner {
     49 
     50     private static final String ABI = "--abi=";
     51     private static final String INCLUDE = "--include-filter=";
     52     private static final String EXCLUDE = "--exclude-filter=";
     53     private static final String INCLUDE_FILE = "--include-filter-file=";
     54     private static final String EXCLUDE_FILE = "--exclude-filter-file=";
     55     private static final String COLLECT_TESTS_ONLY = "--collect-tests-only";
     56     private static final String JUNIT_IGNORE = "org.junit.Ignore";
     57 
     58     public static void main(String[] args) {
     59         String abiName = null;
     60         Set<String> includes = new HashSet<>();
     61         Set<String> excludes = new HashSet<>();
     62         boolean collectTestsOnly = false;
     63 
     64         for (String arg : args) {
     65             if (arg.startsWith(ABI)) {
     66                 abiName = arg.substring(ABI.length());
     67             } else if (arg.startsWith(INCLUDE)) {
     68                 for (String include : arg.substring(INCLUDE.length()).split(",")) {
     69                     includes.add(include);
     70                 }
     71             } else if (arg.startsWith(EXCLUDE)) {
     72                 for (String exclude : arg.substring(EXCLUDE.length()).split(",")) {
     73                     excludes.add(exclude);
     74                 }
     75             } else if (arg.startsWith(INCLUDE_FILE)) {
     76                 loadFilters(arg.substring(INCLUDE_FILE.length()), includes);
     77             } else if (arg.startsWith(EXCLUDE_FILE)) {
     78                 loadFilters(arg.substring(EXCLUDE_FILE.length()), excludes);
     79             } else if (COLLECT_TESTS_ONLY.equals(arg)) {
     80                 collectTestsOnly = true;
     81             }
     82         }
     83 
     84         TestListener listener = new DalvikTestListener();
     85         String[] classPathItems = System.getProperty("java.class.path").split(File.pathSeparator);
     86         List<Class<?>> classes = getClasses(classPathItems, abiName);
     87         TestSuite suite = TestSuiteFilter.createSuite(classes, includes, excludes);
     88         int count = suite.countTestCases();
     89         System.out.println(String.format("start-run:%d", count));
     90         long start = System.currentTimeMillis();
     91 
     92         if (collectTestsOnly) { // only simulate running/passing the tests with the listener
     93             collectTests(suite, listener, includes, excludes);
     94         } else { // run the tests
     95             TestResult result = new TestResult();
     96             result.addListener(listener);
     97             suite.run(result);
     98         }
     99 
    100         long end = System.currentTimeMillis();
    101         System.out.println(String.format("end-run:%d", end - start));
    102     }
    103 
    104     /* Recursively collect tests, since Test elements of the TestSuite may also be TestSuite
    105      * objects containing Tests. */
    106     private static void collectTests(TestSuite suite, TestListener listener,
    107             Set<String> includes, Set<String> excludes) {
    108 
    109         Enumeration<Test> tests = suite.tests();
    110         while (tests.hasMoreElements()) {
    111             Test test = tests.nextElement();
    112             if (test instanceof TestSuite) {
    113                 collectTests((TestSuite) test, listener, includes, excludes);
    114             } else if (shouldCollect(test, includes, excludes)) {
    115                 listener.startTest(test);
    116                 listener.endTest(test);
    117             }
    118         }
    119     }
    120 
    121     /* Copied from FilterableTestSuite.shouldRun(), which is private */
    122     private static boolean shouldCollect(Test test, Set<String> includes, Set<String> excludes) {
    123         String fullName = test.toString();
    124         String[] parts = fullName.split("[\\(\\)]");
    125         String className = parts[1];
    126         String methodName = String.format("%s#%s", className, parts[0]);
    127         int index = className.lastIndexOf('.');
    128         String packageName = index < 0 ? "" : className.substring(0, index);
    129 
    130         if (excludes.contains(packageName)) {
    131             // Skip package because it was excluded
    132             return false;
    133         }
    134         if (excludes.contains(className)) {
    135             // Skip class because it was excluded
    136             return false;
    137         }
    138         if (excludes.contains(methodName)) {
    139             // Skip method because it was excluded
    140             return false;
    141         }
    142         return includes.isEmpty()
    143                 || includes.contains(methodName)
    144                 || includes.contains(className)
    145                 || includes.contains(packageName);
    146     }
    147 
    148     private static void loadFilters(String filename, Set<String> filters) {
    149         try {
    150             Scanner in = new Scanner(new File(filename));
    151             while (in.hasNextLine()) {
    152                 filters.add(in.nextLine());
    153             }
    154             in.close();
    155         } catch (FileNotFoundException e) {
    156             System.out.println(String.format("File %s not found when loading filters", filename));
    157         }
    158     }
    159 
    160     private static List<Class<?>> getClasses(String[] jars, String abiName) {
    161         List<Class<?>> classes = new ArrayList<>();
    162         for (String jar : jars) {
    163             try {
    164                 ClassLoader loader = createClassLoader(jar, abiName);
    165                 DexFile file = new DexFile(jar);
    166                 Enumeration<String> entries = file.entries();
    167                 while (entries.hasMoreElements()) {
    168                     String e = entries.nextElement();
    169                     Class<?> cls = loader.loadClass(e);
    170                     if (isTestClass(cls)) {
    171                         classes.add(cls);
    172                     }
    173                 }
    174             } catch (IllegalAccessError | IOException | ClassNotFoundException e) {
    175                 e.printStackTrace();
    176             }
    177         }
    178         return classes;
    179     }
    180 
    181     private static ClassLoader createClassLoader(String jar, String abiName) {
    182         StringBuilder libPath = new StringBuilder();
    183         libPath.append(jar).append("!/lib/").append(abiName);
    184         return new PathClassLoader(
    185                 jar, libPath.toString(), DalvikTestRunner.class.getClassLoader());
    186     }
    187 
    188     private static boolean isTestClass(Class<?> cls) {
    189         // FIXME(b/25154702): have to have a null check here because some
    190         // classes such as
    191         // SQLite.JDBC2z.JDBCPreparedStatement can be found in the classes.dex
    192         // by DexFile.entries
    193         // but trying to load them with DexFile.loadClass returns null.
    194         if (cls == null) {
    195             return false;
    196         }
    197         for (Annotation a : cls.getAnnotations()) {
    198             if (a.annotationType().getName().equals(JUNIT_IGNORE)) {
    199                 return false;
    200             }
    201         }
    202 
    203         if (!hasPublicTestMethods(cls)) {
    204             return false;
    205         }
    206 
    207         // TODO: Add junit4 support here
    208         int modifiers = cls.getModifiers();
    209         return (Test.class.isAssignableFrom(cls)
    210                 && Modifier.isPublic(modifiers)
    211                 && !Modifier.isStatic(modifiers)
    212                 && !Modifier.isInterface(modifiers)
    213                 && !Modifier.isAbstract(modifiers));
    214     }
    215 
    216     private static boolean hasPublicTestMethods(Class<?> cls) {
    217         for (Method m : cls.getDeclaredMethods()) {
    218             if (isPublicTestMethod(m)) {
    219                 return true;
    220             }
    221         }
    222         return false;
    223     }
    224 
    225     private static boolean isPublicTestMethod(Method m) {
    226         boolean hasTestName = m.getName().startsWith("test");
    227         boolean takesNoParameters = (m.getParameterTypes().length == 0);
    228         boolean returnsVoid = m.getReturnType().equals(Void.TYPE);
    229         boolean isPublic = Modifier.isPublic(m.getModifiers());
    230         return hasTestName && takesNoParameters && returnsVoid && isPublic;
    231     }
    232 
    233     // TODO: expand this to setup and teardown things needed by Dalvik tests.
    234     private static class DalvikTestListener implements TestListener {
    235         /**
    236          * {@inheritDoc}
    237          */
    238         @Override
    239         public void startTest(Test test) {
    240             System.out.println(String.format("start-test:%s", getId(test)));
    241         }
    242 
    243         /**
    244          * {@inheritDoc}
    245          */
    246         @Override
    247         public void endTest(Test test) {
    248             System.out.println(String.format("end-test:%s", getId(test)));
    249         }
    250 
    251         /**
    252          * {@inheritDoc}
    253          */
    254         @Override
    255         public void addFailure(Test test, AssertionFailedError error) {
    256             System.out.println(String.format("failure:%s", stringify(error)));
    257         }
    258 
    259         /**
    260          * {@inheritDoc}
    261          */
    262         @Override
    263         public void addError(Test test, Throwable error) {
    264             System.out.println(String.format("failure:%s", stringify(error)));
    265         }
    266 
    267         private String getId(Test test) {
    268             String className = test.getClass().getName();
    269             if (test instanceof TestCase) {
    270                 return String.format("%s#%s", className, ((TestCase) test).getName());
    271             }
    272             return className;
    273         }
    274 
    275         private String stringify(Throwable error) {
    276             return Arrays.toString(error.getStackTrace()).replaceAll("\n", " ");
    277         }
    278     }
    279 }
    280