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 */
16package com.google.coretests;
17
18import java.io.File;
19import java.io.FileInputStream;
20import java.io.ObjectInputStream;
21
22import junit.framework.AssertionFailedError;
23import junit.framework.Protectable;
24import junit.framework.TestCase;
25import junit.framework.TestResult;
26import junit.textui.TestRunner;
27
28/**
29 * A wrapper around a single test that allows to execute the test either in the
30 * same thread, in a separate thread, or even in a different process.
31 */
32public class CoreTestRunnable implements Runnable {
33
34    private static boolean IS_DALVIK = "Dalvik".equals(
35            System.getProperty("java.vm.name"));
36
37    /**
38     * The test case we are supposed to run.
39     */
40    private TestCase fTest;
41
42    /**
43     * The TestResult we need to update after the run.
44     */
45    private TestResult fResult;
46
47    /**
48     * The Protectable that JUnit has created for us.
49     */
50    private Protectable fProtectable;
51
52    /**
53     * Reflects whether we need to invert the test result, which is used for
54     * treating known failures.
55     */
56    private boolean fInvert;
57
58    /**
59     * Reflects whether we need to isolate the test, which means we run it in
60     * a separate process.
61     */
62    private boolean fIsolate;
63
64    /**
65     * If we are isolating the test case, this holds the process that is running
66     * it.
67     */
68    private Process fProcess;
69
70    /**
71     * Creates a new CoreTestRunnable for the given parameters.
72     */
73    public CoreTestRunnable(TestCase test, TestResult result,
74            Protectable protectable, boolean invert, boolean isolate) {
75
76        this.fTest = test;
77        this.fProtectable = protectable;
78        this.fResult = result;
79        this.fInvert = invert;
80        this.fIsolate = isolate;
81    }
82
83    /**
84     * Executes the test and stores the results. May be run from a secondary
85     * Thread.
86     */
87    public void run() {
88        try {
89            if (fIsolate) {
90                runExternally();
91            } else {
92                runInternally();
93            }
94
95            if (fInvert) {
96                fInvert = false;
97                throw new AssertionFailedError(
98                        "@KnownFailure expected to fail, but succeeded");
99            }
100        } catch (AssertionFailedError e) {
101            if (!fInvert) {
102                fResult.addFailure(fTest, e);
103            }
104        } catch (ThreadDeath e) { // don't catch ThreadDeath by accident
105            throw e;
106        } catch (Throwable e) {
107            if (!fInvert) {
108                fResult.addError(fTest, e);
109            }
110        }
111    }
112
113    /**
114     * Tells the test case to stop. Only used with isolation. We need to kill
115     * the external process in this case.
116     */
117    public void stop() {
118        if (fProcess != null) {
119            fProcess.destroy();
120        }
121    }
122
123    /**
124     * Runs the test case in the same process. This is basically what a
125     * run-of-the-mill JUnit does, except we might also do it in a secondary
126     * thread.
127     */
128    private void runInternally() throws Throwable {
129        fProtectable.protect();
130    }
131
132    /**
133     * Runs the test case in a different process. This is what we do for
134     * isolating test cases that have side effects or do suffer from them.
135     */
136    private void runExternally() throws Throwable {
137        Throwable throwable = null;
138
139        File file = File.createTempFile("isolation", ".tmp");
140
141        fProcess = Runtime.getRuntime().exec(
142                (IS_DALVIK ? "dalvikvm" : "java") +
143                " -classpath " + System.getProperty("java.class.path") +
144                " -Djava.home=" + System.getProperty("java.home") +
145                " -Duser.home=" + System.getProperty("user.home") +
146                " -Djava.io.tmpdir=" + System.getProperty("user.home") +
147                " com.google.coretests.CoreTestIsolator" +
148                " " + fTest.getClass().getName() +
149                " " + fTest.getName() +
150                " " + file.getAbsolutePath());
151
152        int result = fProcess.waitFor();
153
154        if (result != TestRunner.SUCCESS_EXIT) {
155            try {
156                FileInputStream fis = new FileInputStream(file);
157                ObjectInputStream ois = new ObjectInputStream(fis);
158                throwable = (Throwable)ois.readObject();
159                ois.close();
160            } catch (Exception ex) {
161                throwable = new RuntimeException("Error isolating test", ex);
162            }
163        }
164
165        file.delete();
166
167        if (throwable != null) {
168            throw throwable;
169        }
170    }
171
172}
173