/* * Copyright 2008, Unitils.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.unitils; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.unitils.TracingTestListener.InvocationSource.TEST; import static org.unitils.TracingTestListener.InvocationSource.UNITILS; import static org.unitils.TracingTestListener.ListenerInvocation.LISTENER_BEFORE_CLASS; import static org.unitils.TracingTestListener.ListenerInvocation.LISTENER_AFTER_CREATE_TEST_OBJECT; import static org.unitils.TracingTestListener.ListenerInvocation.LISTENER_AFTER_TEST_METHOD; import static org.unitils.TracingTestListener.ListenerInvocation.LISTENER_AFTER_TEST_TEARDOWN; import static org.unitils.TracingTestListener.ListenerInvocation.LISTENER_BEFORE_TEST_METHOD; import static org.unitils.TracingTestListener.ListenerInvocation.LISTENER_BEFORE_TEST_SET_UP; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import junit.framework.AssertionFailedError; import org.unitils.core.TestListener; /** * Test listener that records all method invocations. * * @author Tim Ducheyne * @author Filip Neven */ public class TracingTestListener extends TestListener { public static enum TestFramework { JUNIT3, JUNIT4, TESTNG } public interface Invocation {} public static enum ListenerInvocation implements Invocation { LISTENER_BEFORE_CLASS, LISTENER_AFTER_CREATE_TEST_OBJECT, LISTENER_BEFORE_TEST_SET_UP, LISTENER_BEFORE_TEST_METHOD, LISTENER_AFTER_TEST_METHOD, LISTENER_AFTER_TEST_TEARDOWN } public static enum TestInvocation implements Invocation { TEST_BEFORE_CLASS, TEST_SET_UP, TEST_METHOD, TEST_TEAR_DOWN, TEST_AFTER_CLASS } public static enum InvocationSource { TEST, UNITILS } /* List that will contain a string representation of each method call */ private List<Call> callList; private Invocation exceptionMethod; private boolean throwAssertionFailedError; /** * Delegate target for functions that need the original testlistener (with module support). */ private TestListener delegate; /* * Test method that is currently executing */ private Method currentTestMethod; /* * Exception that was thrown during the test method that is currently executing. * Is reset in beforeTestSetUp */ private Throwable currentThrowable; public TracingTestListener(TestListener delegate) { this(); this.delegate = delegate; } public TracingTestListener() { this.callList = new ArrayList<Call>(); } public List<Call> getCallList() { return callList; } public String getCallListAsString() { StringBuffer result = new StringBuffer(); for (Call call : callList) { result.append(call); result.append('\n'); } return result.toString(); } public void expectExceptionInMethod(Invocation exceptionMethod, boolean throwAssertionFailedError) { this.exceptionMethod = exceptionMethod; this.throwAssertionFailedError = throwAssertionFailedError; } public void registerTestInvocation(TestInvocation invocation, Class<?> testClass, String methodName) { callList.add(new Call(invocation, testClass, methodName)); throwExceptionIfRequested(invocation); } public void registerListenerInvocation(ListenerInvocation listenerInvocation, Class<?> testClass, Object test, Method testMethod, Throwable throwable) { callList.add(new Call(listenerInvocation, testClass, testMethod == null ? null : testMethod.getName(), throwable)); } @Override public void beforeTestClass(Class<?> testClass) { registerListenerInvocation(LISTENER_BEFORE_CLASS, testClass, null, null, null); } @Override public void afterCreateTestObject(Object testObject) { registerListenerInvocation(LISTENER_AFTER_CREATE_TEST_OBJECT, testObject.getClass(), testObject, null, null); this.delegate.afterCreateTestObject(testObject); throwExceptionIfRequested(LISTENER_AFTER_CREATE_TEST_OBJECT); } @Override public void beforeTestSetUp(Object testObject, Method testMethod) { currentTestMethod = testMethod; currentThrowable = null; registerListenerInvocation(LISTENER_BEFORE_TEST_SET_UP, testObject.getClass(), testObject, testMethod, null); throwExceptionIfRequested(LISTENER_BEFORE_TEST_SET_UP); } @Override public void beforeTestMethod(Object testObject, Method testMethod) { assertEquals(currentTestMethod, testMethod); registerListenerInvocation(LISTENER_BEFORE_TEST_METHOD, testObject.getClass(), testObject, testMethod, null); throwExceptionIfRequested(LISTENER_BEFORE_TEST_METHOD); } @Override public void afterTestMethod(Object testObject, Method testMethod, Throwable throwable) { assertEquals(currentTestMethod, testMethod); assertTrue(throwable == null || (currentThrowable != null && currentThrowable.equals(throwable))); registerListenerInvocation(LISTENER_AFTER_TEST_METHOD, testObject.getClass(), testObject, testMethod, throwable); throwExceptionIfRequested(LISTENER_AFTER_TEST_METHOD); } @Override public void afterTestTearDown(Object testObject, Method testMethod) { registerListenerInvocation(LISTENER_AFTER_TEST_TEARDOWN, testObject.getClass(), testObject, testMethod, null); throwExceptionIfRequested(LISTENER_AFTER_TEST_TEARDOWN); } @Override public boolean shouldInvokeTestMethod(Object testObject, Method testMethod) { // delegate to the main testlistener, so that the multipart module can do its job (and maybe others in future) return delegate.shouldInvokeTestMethod(testObject, testMethod); } private void throwExceptionIfRequested(Invocation exceptionMethod) { if (this.exceptionMethod == null || !this.exceptionMethod.equals(exceptionMethod)) { return; } if (throwAssertionFailedError) { AssertionFailedError error = new AssertionFailedError(exceptionMethod.toString()); currentThrowable = error; throw error; } RuntimeException exception = new RuntimeException(exceptionMethod.toString()); currentThrowable = exception; throw exception; } public Throwable getCurrentThrowable() { return currentThrowable; } public static class Call { private InvocationSource invocationSource; private Invocation invocation; private Class<?> testClass; private String testMethod; private Throwable throwable; public Call(Invocation invocation, Class<?> testClass) { this(invocation, testClass, null); } public Call(Invocation invocation, Class<?> testClass, String testMethod) { this(invocation, testClass, testMethod, null); } public Call(Invocation invocation, Class<?> testClass, String testMethod, Throwable throwable) { if (invocation instanceof TestInvocation) { this.invocationSource = TEST; } else { this.invocationSource = UNITILS; } this.invocation = invocation; this.testClass = testClass; this.testMethod = testMethod; this.throwable = throwable; } public Invocation getInvocation() { return invocation; } public Class<?> getTestClass() { return testClass; } public String getTestMethod() { return testMethod; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((invocation == null) ? 0 : invocation.hashCode()); result = prime * result + ((invocationSource == null) ? 0 : invocationSource .hashCode()); result = prime * result + ((testClass == null) ? 0 : testClass.hashCode()); result = prime * result + ((testMethod == null) ? 0 : testMethod.hashCode()); result = prime * result + ((throwable == null) ? 0 : throwable.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Call other = (Call) obj; if (invocation == null) { if (other.invocation != null) { return false; } } else if (!invocation.equals(other.invocation)) { return false; } if (invocationSource == null) { if (other.invocationSource != null) { return false; } } else if (!invocationSource.equals(other.invocationSource)) { return false; } if (testClass == null) { if (other.testClass != null) { return false; } } else if (!testClass.equals(other.testClass)) { return false; } return true; } @Override public String toString() { return invocationSource + " " + invocation + " " + (testClass == null ? "" : testClass.getSimpleName()) + (testMethod == null ? "" : " " + testMethod) + (throwable == null ? "" : " " + throwable); } } }