1bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot/*
2bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot * Copyright (C) 2012 The Android Open Source Project
3bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot *
4bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot * Licensed under the Apache License, Version 2.0 (the "License");
5bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot * you may not use this file except in compliance with the License.
6bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot * You may obtain a copy of the License at
7bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot *
8bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot *      http://www.apache.org/licenses/LICENSE-2.0
9bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot *
10bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot * Unless required by applicable law or agreed to in writing, software
11bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot * distributed under the License is distributed on an "AS IS" BASIS,
12bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot * See the License for the specific language governing permissions and
14bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot * limitations under the License.
15bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot */
16bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot
17bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabotpackage com.android.test.runner;
18bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot
19bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabotimport android.util.Log;
20bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot
21bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabotimport org.junit.runner.Description;
22bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabotimport org.junit.runner.notification.Failure;
23bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot
24bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabotimport java.io.PrintStream;
25bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabotimport java.lang.reflect.Method;
269e8a4c5e0d300447f53e32f0c370fbbdccebd982Brett Chabotimport java.lang.reflect.Modifier;
270e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabotimport java.util.LinkedList;
28bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabotimport java.util.List;
29bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot
30bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot/**
31bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot * A class for loading JUnit3 and JUnit4 test classes given a set of potential class names.
32bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot */
33bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabotpublic class TestLoader {
34bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot
35bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot    private static final String LOG_TAG = "TestLoader";
36bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot
370e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    private  List<Class<?>> mLoadedClasses = new LinkedList<Class<?>>();
380e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    private  List<Failure> mLoadFailures = new LinkedList<Failure>();
39bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot
400e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    private PrintStream mWriter;
410e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot
420e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    /**
430e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * Create a {@link TestLoader}.
440e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     *
450e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * @param writer a {@link PrintStream} used for reporting errors.
460e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     */
470e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    public TestLoader(PrintStream writer) {
480e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        mWriter = writer;
490e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    }
50bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot
510e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    /**
520e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * Loads the test class from the given class name.
530e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * <p/>
540e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * Will store the result internally. Successfully loaded classes can be retrieved via
550e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * {@link #getLoadedClasses()}, failures via {@link #getLoadFailures()}.
560e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     *
570e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * @param className the class name to attempt to load
580e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * @return the loaded class or null.
590e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     */
600e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    public Class<?> loadClass(String className) {
610e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        Class<?> loadedClass = doLoadClass(className);
620e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        if (loadedClass != null) {
630e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot            mLoadedClasses.add(loadedClass);
64bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot        }
650e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        return loadedClass;
660e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    }
67bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot
680e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    private Class<?> doLoadClass(String className) {
690e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        try {
700e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot            return Class.forName(className);
710e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        } catch (ClassNotFoundException e) {
720e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot            String errMsg = String.format("Could not find class: %s", className);
730e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot            Log.e(LOG_TAG, errMsg);
740e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot            mWriter.println(errMsg);
750e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot            Description description = Description.createSuiteDescription(className);
760e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot            Failure failure = new Failure(description, e);
770e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot            mLoadFailures.add(failure);
78bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot        }
790e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        return null;
80bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot    }
81bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot
82bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot    /**
830e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * Loads the test class from the given class name.
840e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * <p/>
850e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * Similar to {@link #loadClass(String, PrintStream))}, but will ignore classes that are
860e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * not tests.
87bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot     *
880e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * @param className the class name to attempt to load
890e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * @return the loaded class or null.
90bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot     */
910e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    public Class<?> loadIfTest(String className) {
920e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        Class<?> loadedClass = doLoadClass(className);
930e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        if (loadedClass != null && isTestClass(loadedClass)) {
940e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot            mLoadedClasses.add(loadedClass);
950e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot            return loadedClass;
96bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot        }
970e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        return null;
980e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    }
990e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot
1000e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    /**
1010e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * @return whether this {@link TestLoader} contains any loaded classes or load failures.
1020e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     */
1030e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    public boolean isEmpty() {
1040e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        return mLoadedClasses.isEmpty() && mLoadFailures.isEmpty();
1050e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    }
1060e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot
1070e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    /**
1080e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * Get the {@link List) of classes successfully loaded via
1090e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * {@link #loadTest(String, PrintStream)} calls.
1100e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     */
1110e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    public List<Class<?>> getLoadedClasses() {
1120e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        return mLoadedClasses;
1130e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    }
1140e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot
1150e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    /**
1160e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * Get the {@link List) of {@link Failure} that occurred during
1170e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     * {@link #loadTest(String, PrintStream)} calls.
1180e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot     */
1190e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot    public List<Failure> getLoadFailures() {
1200e1d66fcd74344182e3bfca913744b1a66e7a188Brett Chabot        return mLoadFailures;
121bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot    }
122bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot
123bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot    /**
124bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot     * Determines if given class is a valid test class.
125bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot     *
126bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot     * @param loadedClass
127bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot     * @return <code>true</code> if loadedClass is a test
128bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot     */
129bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot    private boolean isTestClass(Class<?> loadedClass) {
1309e8a4c5e0d300447f53e32f0c370fbbdccebd982Brett Chabot        if (Modifier.isAbstract(loadedClass.getModifiers())) {
1319e8a4c5e0d300447f53e32f0c370fbbdccebd982Brett Chabot            Log.v(LOG_TAG, String.format("Skipping abstract class %s: not a test",
1329e8a4c5e0d300447f53e32f0c370fbbdccebd982Brett Chabot                    loadedClass.getName()));
1339e8a4c5e0d300447f53e32f0c370fbbdccebd982Brett Chabot            return false;
1349e8a4c5e0d300447f53e32f0c370fbbdccebd982Brett Chabot        }
135bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot        // TODO: try to find upstream junit calls to replace these checks
13653196f43b44ff02da07c243798168d7e5614ec34Brett Chabot        if (junit.framework.Test.class.isAssignableFrom(loadedClass)) {
137bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot            return true;
138bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot        }
139bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot        // TODO: look for a 'suite' method?
140bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot        if (loadedClass.isAnnotationPresent(org.junit.runner.RunWith.class)) {
141bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot            return true;
142bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot        }
143bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot        for (Method testMethod : loadedClass.getMethods()) {
144bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot            if (testMethod.isAnnotationPresent(org.junit.Test.class)) {
145bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot                return true;
146bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot            }
147bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot        }
148bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot        Log.v(LOG_TAG, String.format("Skipping class %s: not a test", loadedClass.getName()));
149bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot        return false;
150bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot    }
151bb23e68d4dc19a37df318b8d169e3dfd0dd1c20eBrett Chabot}
152