TestRunner.java revision a20d57c3defdf4faad67b052e29aaa8be099cae6
1/*
2 * Copyright (C) 2009 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 vogar.target;
18
19import java.io.File;
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.PrintStream;
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.HashSet;
26import java.util.Iterator;
27import java.util.List;
28import java.util.Properties;
29import java.util.Set;
30import vogar.Result;
31import vogar.TestProperties;
32import vogar.monitor.TargetMonitor;
33
34/**
35 * Runs an action, in process on the target.
36 */
37public final class TestRunner {
38
39    protected final Properties properties;
40
41    protected final String qualifiedName;
42    protected final String qualifiedClassOrPackageName;
43
44    /** the monitor port if a monitor is expected, or null for no monitor */
45    protected final Integer monitorPort;
46    protected final String skipPast;
47    protected final int timeoutSeconds;
48    protected final List<Runner> runners;
49    private final boolean profile;
50    private final int profileDepth;
51    private final int profileInterval;
52    private final File profileFile;
53    private final boolean profileThreadGroup;
54    protected final String[] args;
55    private boolean useSocketMonitor;
56
57    public TestRunner(List<String> argsList) {
58        properties = loadProperties();
59        qualifiedName = properties.getProperty(TestProperties.QUALIFIED_NAME);
60        qualifiedClassOrPackageName = properties.getProperty(TestProperties.TEST_CLASS_OR_PACKAGE);
61        timeoutSeconds = Integer.parseInt(properties.getProperty(TestProperties.TIMEOUT));
62        runners = Arrays.asList(new JUnitRunner(),
63                                new JUnit4Runner(),
64                                new CaliperRunner(),
65                                new MainRunner());
66
67        int monitorPort = Integer.parseInt(properties.getProperty(TestProperties.MONITOR_PORT));
68        String skipPast = null;
69        boolean profile = Boolean.parseBoolean(properties.getProperty(TestProperties.PROFILE));
70        int profileDepth = Integer.parseInt(properties.getProperty(TestProperties.PROFILE_DEPTH));
71        int profileInterval
72                = Integer.parseInt(properties.getProperty(TestProperties.PROFILE_INTERVAL));
73        File profileFile = new File(properties.getProperty(TestProperties.PROFILE_FILE));
74        boolean profileThreadGroup
75                = Boolean.parseBoolean(properties.getProperty(TestProperties.PROFILE_THREAD_GROUP));
76
77        for (Iterator<String> i = argsList.iterator(); i.hasNext(); ) {
78            String arg = i.next();
79            if (arg.equals("--monitorPort")) {
80                i.remove();
81                monitorPort = Integer.parseInt(i.next());
82                i.remove();
83            }
84            if (arg.equals("--skipPast")) {
85                i.remove();
86                skipPast = i.next();
87                i.remove();
88            }
89        }
90
91        this.monitorPort = monitorPort;
92        this.skipPast = skipPast;
93        this.profile = profile;
94        this.profileDepth = profileDepth;
95        this.profileInterval = profileInterval;
96        this.profileFile = profileFile;
97        this.profileThreadGroup = profileThreadGroup;
98        this.args = argsList.toArray(new String[argsList.size()]);
99    }
100
101    private Properties loadProperties() {
102        try {
103            InputStream in = getPropertiesStream();
104            Properties properties = new Properties();
105            properties.load(in);
106            in.close();
107            return properties;
108        } catch (IOException e) {
109            throw new RuntimeException(e);
110        }
111    }
112
113    /**
114     * Configure this test runner to await an incoming socket connection when
115     * writing test results. Otherwise all communication happens over
116     * System.out.
117     */
118    public void useSocketMonitor() {
119        this.useSocketMonitor = true;
120    }
121
122    /**
123     * Attempt to load the test properties file from both the application and system classloader.
124     * This is necessary because sometimes we run tests from the boot classpath.
125     */
126    private InputStream getPropertiesStream() throws IOException {
127        for (Class<?> classToLoadFrom : new Class<?>[] { TestRunner.class, Object.class }) {
128            InputStream propertiesStream = classToLoadFrom.getResourceAsStream(
129                    "/" + TestProperties.FILE);
130            if (propertiesStream != null) {
131                return propertiesStream;
132            }
133        }
134        throw new IOException(TestProperties.FILE + " missing!");
135    }
136
137    /**
138     * Returns the class to run the test with based on {@param klass}. For instance, a class
139     * that extends junit.framework.TestCase should be run with JUnitSpec.
140     *
141     * Returns null if no such associated runner exists.
142     */
143    private Class<?> runnerClass(Class<?> klass) {
144        for (Runner runner : runners) {
145            if (runner.supports(klass)) {
146                return runner.getClass();
147            }
148        }
149
150        return null;
151    }
152
153    public void run() throws IOException {
154        TargetMonitor monitor = useSocketMonitor
155                ? TargetMonitor.await(monitorPort)
156                : TargetMonitor.forPrintStream(System.out);
157        try {
158            run(monitor);
159        } finally {
160            monitor.close();
161        }
162    }
163
164    public void run(final TargetMonitor monitor) {
165        PrintStream monitorPrintStream = new PrintStream(System.out) {
166            @Override public void print(long l) {
167                print(String.valueOf(l));
168            }
169
170            @Override public void print(int i) {
171                print(String.valueOf(i));
172            }
173
174            @Override public void print(float f) {
175                print(String.valueOf(f));
176            }
177
178            @Override public void print(double d) {
179                print(String.valueOf(d));
180            }
181
182            @Override public void print(char[] s) {
183                print(String.valueOf(s));
184            }
185
186            @Override public void print(char c) {
187                print(String.valueOf(c));
188            }
189
190            @Override public void print(Object obj) {
191                print(obj != null ? obj.toString() : "null");
192            }
193
194            @Override public void print(String str) {
195                monitor.output(str != null ? str : "null");
196            }
197
198            @Override public void println() {
199                print("\n");
200            }
201
202            /**
203             * Although println() is documented to be equivalent to print()
204             * followed by println(), this isn't the behavior on HotSpot
205             * and we must manually override println(String) to ensure that
206             * newlines aren't dropped.
207             */
208            @Override public void println(String s) {
209                print(s + "\n");
210            }
211
212            @Override public void println(long l) {
213                println(String.valueOf(l));
214            }
215
216            @Override public void println(int i) {
217                println(String.valueOf(i));
218            }
219
220            @Override public void println(float f) {
221                println(String.valueOf(f));
222            }
223
224            @Override public void println(double d) {
225                println(String.valueOf(d));
226            }
227
228            @Override public void println(char[] s) {
229                println(String.valueOf(s));
230            }
231
232            @Override public void println(char c) {
233                println(String.valueOf(c));
234            }
235
236            @Override public void println(Object obj) {
237                println(obj != null ? obj.toString() : "null");
238            }
239        };
240        System.setOut(monitorPrintStream);
241        System.setErr(monitorPrintStream);
242
243        TestEnvironment testEnvironment = new TestEnvironment();
244        testEnvironment.reset();
245
246        String classOrPackageName;
247        String qualification;
248
249        // Check whether the class or package is qualified and, if so, strip it off and pass it
250        // separately to the runners. For instance, may qualify a junit class by appending
251        // #method_name, where method_name is the name of a single test of the class to run.
252        int hash_position = qualifiedClassOrPackageName.indexOf("#");
253        if (hash_position != -1) {
254            classOrPackageName = qualifiedClassOrPackageName.substring(0, hash_position);
255            qualification = qualifiedClassOrPackageName.substring(hash_position + 1);
256        } else {
257            classOrPackageName = qualifiedClassOrPackageName;
258            qualification = null;
259        }
260
261        Set<Class<?>> classes = new ClassFinder().find(classOrPackageName);
262
263        // if there is more than one class in the set, this must be a package. Since we're
264        // running everything in the package already, remove any class called AllTests.
265        if (classes.size() > 1) {
266            Set<Class<?>> toRemove = new HashSet<Class<?>>();
267            for (Class<?> klass : classes) {
268                if (klass.getName().endsWith(".AllTests")) {
269                    toRemove.add(klass);
270                }
271            }
272            classes.removeAll(toRemove);
273        }
274
275        Profiler profiler = profile ? Profiler.getInstance() : null;
276        if (profiler != null) {
277            profiler.setup(profileThreadGroup, profileDepth, profileInterval);
278        }
279        for (Class<?> klass : classes) {
280            Class<?> runnerClass = runnerClass(klass);
281            if (runnerClass == null) {
282                monitor.outcomeStarted(null, klass.getName(), qualifiedName);
283                System.out.println("Skipping " + klass.getName()
284                        + ": no associated runner class");
285                monitor.outcomeFinished(Result.UNSUPPORTED);
286                continue;
287            }
288
289            Runner runner;
290            try {
291                runner = (Runner) runnerClass.newInstance();
292                runner.init(monitor, qualifiedName, qualification, klass, testEnvironment,
293                        timeoutSeconds, profile);
294            } catch (Exception e) {
295                monitor.outcomeStarted(null, qualifiedName, qualifiedName);
296                e.printStackTrace();
297                monitor.outcomeFinished(Result.ERROR);
298                return;
299            }
300            boolean completedNormally = runner.run(qualifiedName, klass, skipPast, profiler, args);
301            if (!completedNormally) {
302                return; // let the caller start another process
303            }
304        }
305        if (profiler != null) {
306            profiler.shutdown(profileFile);
307        }
308
309        monitor.completedNormally(true);
310    }
311
312    public static void main(String[] args) throws IOException {
313        new TestRunner(new ArrayList<String>(Arrays.asList(args))).run();
314    }
315}
316