001 /*
002 * $Id: ProfiledStatement.java,v 1.8 2014/04/28 14:56:48 oboehm Exp $
003 *
004 * Copyright (c) 2010 by Oliver Boehm
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 *
018 * (c)reated 29.03.2010 by oliver (ob@oasd.de)
019 */
020
021 package patterntesting.runtime.junit.internal;
022
023 import java.lang.reflect.*;
024 import java.util.List;
025
026 import junit.framework.TestCase;
027
028 import org.junit.*;
029 import org.junit.Test.None;
030 import org.junit.runners.model.*;
031 import org.slf4j.*;
032
033 import patterntesting.annotation.check.runtime.MayReturnNull;
034
035 /**
036 * The ProfiledStatement measures also the time the different setUp() and
037 * tearDown() methods need. In contradiction to the original JUnit statement
038 * this statement is not only a wrapper around the test method but contains
039 * also the setUp and tearDown methods. And it is able to handle JUnit4
040 * <b>and</b> JUnit3 methods.
041 *
042 * @author oliver
043 * @since 1.0 (29.03.2010)
044 */
045 public class ProfiledStatement extends Statement {
046
047 private static final Logger log = LoggerFactory.getLogger(ProfiledStatement.class);
048 private final TestClass testClass;
049 private final FrameworkMethod frameworkMethod;
050 private long startTime;
051 private long startTimeTest;
052 private long startTimeAfters;
053 private long endTime;
054
055 /**
056 * The default constructor for this class if the call of the
057 * frameworkMethod is ok.
058 *
059 * @param testClass the test class
060 * @param frameworkMethod the FrameworkMethod
061 */
062 public ProfiledStatement(final TestClass testClass, final FrameworkMethod frameworkMethod) {
063 this.testClass = testClass;
064 this.frameworkMethod = frameworkMethod;
065 }
066
067 /**
068 * May be needed by some subclasses.
069 * @return the method name
070 */
071 protected final String getMethodName() {
072 return this.frameworkMethod.getName();
073 }
074
075 /**
076 * Invokes the test method.
077 *
078 * @throws Throwable the throwable
079 * @see org.junit.runners.model.Statement#evaluate()
080 */
081 @Override
082 public void evaluate() throws Throwable {
083 Object target = testClass.getOnlyConstructor().newInstance();
084 try {
085 this.startTimer();
086 invokeBefores(target);
087 this.startTestTimer();
088 invokeTest(target);
089 this.startAftersTimer();
090 invokeAfters(target);
091 } catch (InvocationTargetException ite) {
092 Throwable targetException = ite.getTargetException();
093 if (targetException == null) {
094 throw ite;
095 }
096 throw targetException;
097 } finally {
098 if (this.startTimeTest == 0) {
099 this.startTestTimer();
100 }
101 if (this.startTimeAfters == 0) {
102 this.startAftersTimer();
103 }
104 this.endTimer();
105 }
106 }
107
108 /**
109 * We do not only call the test here but check if an excpected exception
110 * appears or not.
111 *
112 * @param target the test target
113 * @throws Throwable thrown by the test method
114 */
115 private void invokeTest(final Object target) throws Throwable {
116 Class<? extends Throwable> expected = getExpectedException();
117 try {
118 frameworkMethod.invokeExplosively(target);
119 if ((expected != null) && (expected != None.class)) {
120 throw new AssertionError("Expected exception: "
121 + expected.getName());
122 }
123 } catch (Throwable t) { //NOSONAR
124 if ((expected != null) && (expected != None.class)
125 && (expected.isAssignableFrom(t.getClass()))) {
126 log.debug("expected exception appears", t);
127 return;
128 }
129 throw t;
130 }
131 }
132
133 @MayReturnNull
134 private Class<? extends Throwable> getExpectedException() {
135 Test test = frameworkMethod.getAnnotation(Test.class);
136 if (test == null) {
137 return None.class;
138 }
139 return test.expected();
140 }
141
142 /**
143 * Here we invoke all setUp() methods.
144 * @param target the instantiated JUnit test
145 * @throws Throwable if setUp method has a problem
146 */
147 private void invokeBefores(final Object target) throws Throwable {
148 if (ProfiledStatement.isTestCaseClass(testClass)) {
149 invoke("setUp", target);
150 } else {
151 List<FrameworkMethod> befores = testClass.getAnnotatedMethods(Before.class);
152 invoke(befores, target);
153 }
154 }
155
156 /**
157 * Here we invoke all tearDown() methods.
158 * @param stmt the recorded statement
159 * @throws Throwable if tearDown method has a problem
160 */
161 private void invokeAfters(final Object target) throws Throwable {
162 if (ProfiledStatement.isTestCaseClass(testClass)) {
163 invoke("tearDown", target);
164 } else {
165 List<FrameworkMethod> afters = testClass.getAnnotatedMethods(After.class);
166 invoke(afters, target);
167 }
168 }
169
170 private void invoke(final String methodName, final Object target)
171 throws Throwable {
172 FrameworkMethod method = JUnitHelper.getFrameworkMethod(testClass.getJavaClass(), methodName);
173 if (method != null) {
174 invoke(method, target);
175 }
176 }
177
178 private void invoke(final FrameworkMethod fwkMethod, final Object target)
179 throws Throwable {
180 try {
181 fwkMethod.invokeExplosively(target);
182 } catch (IllegalAccessException e) {
183 invokeProtected(fwkMethod, target);
184 } catch (InvocationTargetException e) {
185 throw getThrowableFor(fwkMethod, e);
186 }
187 }
188
189 private void invokeProtected(final FrameworkMethod fwkMethod,
190 final Object target) throws Throwable {
191 Method method = fwkMethod.getMethod();
192 method.setAccessible(true);
193 try {
194 method.invoke(target);
195 } catch (IllegalAccessException e) {
196 throw getAssertionErrorFor(fwkMethod, e);
197 } catch (InvocationTargetException e) {
198 throw getThrowableFor(fwkMethod, e);
199 }
200 }
201
202 private void invoke(final List<FrameworkMethod> fwkMethods,
203 final Object target) throws Throwable {
204 for (FrameworkMethod fwkMethod : fwkMethods) {
205 invoke(fwkMethod, target);
206 }
207 }
208
209 private AssertionError getAssertionErrorFor(final FrameworkMethod method, final Throwable t) {
210 String detailedMessage = "invoke of " + method.getName()
211 + "() failed\n" + t;
212 return new AssertionError(detailedMessage);
213 }
214
215 private Throwable getThrowableFor(final FrameworkMethod fwkMethod,
216 final InvocationTargetException e) throws AssertionError {
217 Throwable t = e.getTargetException();
218 if (t == null) {
219 throw getAssertionErrorFor(fwkMethod, e);
220 }
221 return t;
222 }
223
224 /**
225 * Here we start the timer when the first setUp() method was called.
226 */
227 public final void startTimer() {
228 this.startTime = System.currentTimeMillis();
229 }
230
231 /**
232 * Here we start the timer when the test method was called.
233 */
234 public final void startTestTimer() {
235 this.startTimeTest = System.currentTimeMillis();
236 }
237
238 /**
239 * Here we start the timer when the first tearDown() method was called.
240 */
241 public final void startAftersTimer() {
242 this.startTimeAfters = System.currentTimeMillis();
243 }
244
245 /**
246 * The timer should end after the last tearDown() method was called.
247 */
248 public final void endTimer() {
249 this.endTime = System.currentTimeMillis();
250 }
251
252 /**
253 * Returns the name of the method with the measured times.
254 *
255 * @return the framework method with the measured times
256 * @see java.lang.Object#toString()
257 */
258 @Override
259 public String toString() {
260 return this.testClass.getName() + "." + this.frameworkMethod.getName()
261 + " (" + (this.startTimeTest - this.startTime) + "+"
262 + (this.startTimeAfters - this.startTimeTest) + "+"
263 + (this.endTime - this.startTimeAfters) + " ms)";
264 }
265
266 /**
267 * @param testClass the test class
268 * @return true if testClass is derived from TestCase
269 */
270 public static boolean isTestCaseClass(final TestClass testClass) {
271 return TestCase.class.isAssignableFrom(testClass.getJavaClass());
272 }
273
274 }
275