• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
main(String[] args)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. */
collectTests(TestSuite suite, TestListener listener, Set<String> includes, Set<String> excludes)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 */
shouldCollect(Test test, Set<String> includes, Set<String> excludes)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