1/*
2 * Copyright (C) 2012 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
17package com.android.test.runner;
18
19import android.util.Log;
20
21import org.junit.runner.Description;
22import org.junit.runner.notification.Failure;
23
24import java.io.PrintStream;
25import java.lang.reflect.Method;
26import java.lang.reflect.Modifier;
27import java.util.LinkedList;
28import java.util.List;
29
30/**
31 * A class for loading JUnit3 and JUnit4 test classes given a set of potential class names.
32 */
33public class TestLoader {
34
35    private static final String LOG_TAG = "TestLoader";
36
37    private  List<Class<?>> mLoadedClasses = new LinkedList<Class<?>>();
38    private  List<Failure> mLoadFailures = new LinkedList<Failure>();
39
40    private PrintStream mWriter;
41
42    /**
43     * Create a {@link TestLoader}.
44     *
45     * @param writer a {@link PrintStream} used for reporting errors.
46     */
47    public TestLoader(PrintStream writer) {
48        mWriter = writer;
49    }
50
51    /**
52     * Loads the test class from the given class name.
53     * <p/>
54     * Will store the result internally. Successfully loaded classes can be retrieved via
55     * {@link #getLoadedClasses()}, failures via {@link #getLoadFailures()}.
56     *
57     * @param className the class name to attempt to load
58     * @return the loaded class or null.
59     */
60    public Class<?> loadClass(String className) {
61        Class<?> loadedClass = doLoadClass(className);
62        if (loadedClass != null) {
63            mLoadedClasses.add(loadedClass);
64        }
65        return loadedClass;
66    }
67
68    private Class<?> doLoadClass(String className) {
69        try {
70            return Class.forName(className);
71        } catch (ClassNotFoundException e) {
72            String errMsg = String.format("Could not find class: %s", className);
73            Log.e(LOG_TAG, errMsg);
74            mWriter.println(errMsg);
75            Description description = Description.createSuiteDescription(className);
76            Failure failure = new Failure(description, e);
77            mLoadFailures.add(failure);
78        }
79        return null;
80    }
81
82    /**
83     * Loads the test class from the given class name.
84     * <p/>
85     * Similar to {@link #loadClass(String, PrintStream))}, but will ignore classes that are
86     * not tests.
87     *
88     * @param className the class name to attempt to load
89     * @return the loaded class or null.
90     */
91    public Class<?> loadIfTest(String className) {
92        Class<?> loadedClass = doLoadClass(className);
93        if (loadedClass != null && isTestClass(loadedClass)) {
94            mLoadedClasses.add(loadedClass);
95            return loadedClass;
96        }
97        return null;
98    }
99
100    /**
101     * @return whether this {@link TestLoader} contains any loaded classes or load failures.
102     */
103    public boolean isEmpty() {
104        return mLoadedClasses.isEmpty() && mLoadFailures.isEmpty();
105    }
106
107    /**
108     * Get the {@link List) of classes successfully loaded via
109     * {@link #loadTest(String, PrintStream)} calls.
110     */
111    public List<Class<?>> getLoadedClasses() {
112        return mLoadedClasses;
113    }
114
115    /**
116     * Get the {@link List) of {@link Failure} that occurred during
117     * {@link #loadTest(String, PrintStream)} calls.
118     */
119    public List<Failure> getLoadFailures() {
120        return mLoadFailures;
121    }
122
123    /**
124     * Determines if given class is a valid test class.
125     *
126     * @param loadedClass
127     * @return <code>true</code> if loadedClass is a test
128     */
129    private boolean isTestClass(Class<?> loadedClass) {
130        if (Modifier.isAbstract(loadedClass.getModifiers())) {
131            Log.v(LOG_TAG, String.format("Skipping abstract class %s: not a test",
132                    loadedClass.getName()));
133            return false;
134        }
135        // TODO: try to find upstream junit calls to replace these checks
136        if (junit.framework.Test.class.isAssignableFrom(loadedClass)) {
137            return true;
138        }
139        // TODO: look for a 'suite' method?
140        if (loadedClass.isAnnotationPresent(org.junit.runner.RunWith.class)) {
141            return true;
142        }
143        for (Method testMethod : loadedClass.getMethods()) {
144            if (testMethod.isAnnotationPresent(org.junit.Test.class)) {
145                return true;
146            }
147        }
148        Log.v(LOG_TAG, String.format("Skipping class %s: not a test", loadedClass.getName()));
149        return false;
150    }
151}
152