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