001: /*
002: * Copyright 2006-2007, Unitils.org
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.unitils;
017:
018: import org.apache.commons.logging.Log;
019: import org.apache.commons.logging.LogFactory;
020: import org.junit.internal.runners.*;
021: import org.junit.runner.Description;
022: import org.junit.runner.notification.Failure;
023: import org.junit.runner.notification.RunNotifier;
024: import org.unitils.core.TestListener;
025: import org.unitils.core.Unitils;
026:
027: import java.lang.reflect.InvocationTargetException;
028: import java.lang.reflect.Method;
029:
030: /**
031: * Custom test runner that will Unitils-enable your test. This will make sure that the
032: * core unitils test listener methods are invoked in the expected order. See {@link TestListener} for
033: * more information on the listener invocation order.
034: * <p/>
035: * NOTE: if a test fails, the error is logged as debug logging. This is a temporary work-around for
036: * a problem with IntelliJ JUnit-4 runner that reports a 'Wrong test finished' error when something went wrong
037: * in the before. [IDEA-12498]
038: *
039: * @author Tim Ducheyne
040: * @author Filip Neven
041: */
042: public class UnitilsJUnit4TestClassRunner extends JUnit4ClassRunner {
043:
044: /* The logger instance for this class */
045: private static Log logger = LogFactory
046: .getLog(UnitilsJUnit4TestClassRunner.class);
047:
048: /* The main test listener, that hooks this test into unitils */
049: private static TestListener testListener;
050:
051: /* True if beforeAll was succesfully called */
052: private static boolean beforeAllCalled;
053:
054: /**
055: * Creates a test runner that runs all test methods in the given class.
056: *
057: * @param testClass the class, not null
058: * @throws InitializationError
059: */
060: public UnitilsJUnit4TestClassRunner(Class<?> testClass)
061: throws InitializationError {
062: super (testClass);
063:
064: if (testListener == null) {
065: testListener = getUnitils().createTestListener();
066: createShutdownHook();
067: }
068: }
069:
070: @Override
071: public void run(final RunNotifier notifier) {
072: // if this is the first test, call beforeAll
073: if (!beforeAllCalled) {
074: try {
075: testListener.beforeAll();
076: beforeAllCalled = true;
077:
078: } catch (Throwable t) {
079: logger.debug(getDescription(), t);
080: notifier.testAborted(getDescription(), t);
081: return;
082: }
083: }
084:
085: ClassRoadie classRoadie = new ClassRoadie(notifier,
086: getTestClass(), getDescription(), new Runnable() {
087: public void run() {
088: runMethods(notifier);
089: }
090: });
091:
092: Throwable throwable = null;
093: try {
094: testListener.beforeTestClass(getTestClass().getJavaClass());
095: classRoadie.runProtected();
096: } catch (Throwable t) {
097: notifier.fireTestFailure(new Failure(getDescription(), t));
098: throwable = t;
099: }
100: try {
101: testListener.afterTestClass(getTestClass().getJavaClass());
102: } catch (Throwable t) {
103: // first exception is typically the most meaningful, so ignore second exception
104: if (throwable == null) {
105: notifier.fireTestFailure(new Failure(getDescription(),
106: t));
107: }
108: }
109: }
110:
111: /**
112: * Overriden JUnit4 method to be able to create a CustomMethodRoadie that will invoke the
113: * unitils test listener methods at the apropriate moments.
114: */
115: protected void invokeTestMethod(Method method, RunNotifier notifier) {
116: Description description = methodDescription(method);
117: Object testObject;
118: try {
119: testObject = createTest();
120: } catch (InvocationTargetException e) {
121: notifier.testAborted(description, e.getCause());
122: return;
123: } catch (Exception e) {
124: notifier.testAborted(description, e);
125: return;
126: }
127: TestMethod testMethod = wrapMethod(method);
128: new CustomMethodRoadie(testObject, method, testMethod,
129: notifier, description).run();
130: }
131:
132: /**
133: * Custom method roadie that invokes the unitils test listener methods at the apropriate moments.
134: */
135: public static class CustomMethodRoadie extends MethodRoadie {
136:
137: /* Instance under test */
138: protected Object testObject;
139:
140: /* Method under test */
141: protected Method testMethod;
142:
143: /**
144: * Creates a method roadie.
145: *
146: * @param testObject The test instance, not null
147: * @param testMethod The test method, not null
148: * @param jUnitTestMethod The JUnit test method
149: * @param notifier The run listener, not null
150: * @param description A test description
151: */
152: public CustomMethodRoadie(Object testObject, Method testMethod,
153: TestMethod jUnitTestMethod, RunNotifier notifier,
154: Description description) {
155: super (testObject, jUnitTestMethod, notifier, description);
156: this .testObject = testObject;
157: this .testMethod = testMethod;
158: }
159:
160: /**
161: * Overriden JUnit4 method to be able to call {@link TestListener#afterTestTearDown}.
162: */
163: @Override
164: public void runBeforesThenTestThenAfters(Runnable test) {
165: Throwable throwable = null;
166: try {
167: testListener.beforeTestSetUp(testObject);
168: super .runBeforesThenTestThenAfters(test);
169: } catch (Throwable t) {
170: addFailure(t);
171: throwable = t;
172: }
173: try {
174: testListener.afterTestTearDown(testObject);
175: } catch (Throwable t) {
176: // first exception is typically the most meaningful, so ignore second exception
177: if (throwable == null) {
178: addFailure(t);
179: }
180: }
181: }
182:
183: protected void runTestMethod() {
184: Throwable throwable = null;
185: try {
186: testListener.beforeTestMethod(testObject, testMethod);
187: super .runTestMethod();
188: } catch (Throwable t) {
189: addFailure(t);
190: throwable = t;
191: }
192: try {
193: testListener.afterTestMethod(testObject, testMethod,
194: throwable);
195: } catch (Throwable t) {
196: // first exception is typically the most meaningful, so ignore second exception
197: if (throwable == null) {
198: addFailure(t);
199: }
200: }
201: }
202: }
203:
204: /**
205: * This will return the default singleton instance by calling {@link Unitils#getInstance()}.
206: * <p/>
207: * You can override this method to let it create and set your own singleton instance. For example, you
208: * can let it create an instance of your own Unitils subclass and set it by using {@link Unitils#setInstance}.
209: *
210: * @return the unitils core instance, not null
211: */
212: protected Unitils getUnitils() {
213: return Unitils.getInstance();
214: }
215:
216: /**
217: * Creates a hook that will call {@link TestListener#afterAll} during the shutdown of the VM.
218: * <p/>
219: * This seems te be the only way in JUnit4 to this, since there is no way to
220: * able to know when all tests have run.
221: */
222: protected void createShutdownHook() {
223: Runtime.getRuntime().addShutdownHook(new Thread() {
224: @Override
225: public void run() {
226: if (testListener != null) {
227: testListener.afterAll();
228: }
229: }
230: });
231: }
232: }
|