001    /*
002     * $Id: ProxyRunner.java,v 1.2 2014/01/04 19:28:54 oboehm Exp $
003     *
004     * Copyright (c) 2011 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 11.11.2011 by oliver (ob@oasd.de)
019     */
020    
021    package patterntesting.runtime.junit;
022    
023    import java.lang.annotation.Annotation;
024    import java.lang.reflect.*;
025    import java.util.*;
026    
027    import org.junit.runner.Description;
028    import org.junit.runner.notification.*;
029    import org.junit.runners.ParentRunner;
030    import org.junit.runners.model.*;
031    import org.slf4j.*;
032    
033    import patterntesting.runtime.annotation.DelegateTo;
034    import patterntesting.runtime.junit.SmokeRunner;
035    import patterntesting.runtime.junit.internal.*;
036    import patterntesting.runtime.util.ReflectionHelper;
037    
038    /**
039     * This is a JUnit runner which delegates the call one (or perhaps several)
040     * other JUnit runners.
041     * </br>
042     * Till 1.2.10-YEARS it was placed in the experimental package.
043     *
044     * @author oliver (ob@aosd.de)
045     * @since 1.2 (11.11.2011)
046     */
047    public class ProxyRunner extends SmokeRunner {
048    
049        private static final Logger log = LoggerFactory.getLogger(ProxyRunner.class);
050        private ParentRunner<FrameworkMethod> delegateRunner;
051    
052        /**
053         * Instantiates a new proxy runner.
054         *
055         * @param testClass the test class
056         * @throws InitializationError the initialization error
057         */
058        public ProxyRunner(final Class<?> testClass) throws InitializationError {
059            super(testClass);
060        }
061    
062        /**
063         * Gets the Runner defined by <code>&#064;DelegateTo</code>.
064         * <br/>
065         * Note: This class is public for testing reasons.
066         *
067         * @return the delegate runner
068         */
069        public final ParentRunner<FrameworkMethod> getDelegateRunner() {
070            if (this.delegateRunner == null) {
071                try {
072                    this.delegateRunner = createDelegateRunner();
073                } catch (InitializationError ie) {
074                    throw new RuntimeException(ie);
075                }
076            }
077            return this.delegateRunner;
078        }
079    
080        private ParentRunner<FrameworkMethod> createDelegateRunner() throws InitializationError {
081            Class<? extends ParentRunner<FrameworkMethod>> runnerClass = getDelegateRunnerClass();
082            try {
083                Constructor<? extends ParentRunner<FrameworkMethod>> ctor = runnerClass.getDeclaredConstructor(Class.class);
084                return ctor.newInstance(this.getTestClass().getJavaClass());
085            } catch (SecurityException e) {
086                throw new ModelInitializationError(e);
087            } catch (NoSuchMethodException e) {
088                throw new ModelInitializationError(e);
089            } catch (IllegalArgumentException e) {
090                throw new ModelInitializationError(e);
091            } catch (InstantiationException e) {
092                throw new ModelInitializationError(e);
093            } catch (IllegalAccessException e) {
094                throw new ModelInitializationError(e);
095            } catch (InvocationTargetException e) {
096                throw new ModelInitializationError(e);
097            }
098        }
099    
100        private Class<? extends ParentRunner<FrameworkMethod>> getDelegateRunnerClass() {
101            DelegateTo delegateTo = this.getTestClass().getJavaClass().getAnnotation(DelegateTo.class);
102            return delegateTo.value();
103        }
104    
105        /**
106         * Gets the description.
107         *
108         * @return the description
109         * @see org.junit.runner.Runner#getDescription()
110         */
111        @Override
112        public Description getDescription() {
113            Description description = this.getDelegateRunner().getDescription();
114            Collection<Annotation> annotationList = description.getAnnotations();
115            Annotation[] annotations = annotationList.toArray(new Annotation[annotationList.size()]);
116            Description filtered = DescriptionUtils.createTestDescription(description, annotations);
117            for (Description descr : description.getChildren()) {
118                filtered.addChild(descr);
119            }
120            return filtered;
121        }
122    
123        /**
124         * Here we decide if the whole class is to be run.
125         *
126         * @param notifier the notifier
127         * @see org.junit.runner.Runner#run(org.junit.runner.notification.RunNotifier)
128         */
129        @Override
130        public void run(final RunNotifier notifier) {
131            //delegateRunner.run(notifier);
132            super.run(notifier);
133        }
134    
135        /**
136         * Gets the children.
137         *
138         * @return the children
139         * @see org.junit.runners.ParentRunner#getChildren()
140         */
141        @SuppressWarnings("unchecked")
142        @Override
143        protected List<FrameworkMethod> getChildren() {
144            // TODO: put a Proxy around each FrameworkMethod
145            List<FrameworkMethod> methods = (List<FrameworkMethod>) ReflectionHelper.invokeMethod(this.getDelegateRunner(), "getChildren");
146            return methods;
147        }
148    
149        /**
150         * Run child. This method may be not called (e.g. it is not called by
151         * SpringJUnit4ClassRunner) but must be implemented because of the abstract
152         * superclass (ParentRunner).
153         *
154         * @param method the method
155         * @param notifier the notifier
156         * @see org.junit.runners.ParentRunner#runChild(java.lang.Object, org.junit.runner.notification.RunNotifier)
157         */
158        @Override
159        protected void runChild(final FrameworkMethod method, final RunNotifier notifier) {
160            Description description = describeChild(method);
161            try {
162                if (shouldBeIgnored(method)) {
163                    notifier.fireTestIgnored(description);
164                    return;
165                }
166            } catch (IllegalArgumentException iae) {
167                fireTestAssumptionFailed(notifier, description, iae);
168                notifier.fireTestFinished(description);
169                return;
170            }
171            long startTime = System.currentTimeMillis();
172            ReflectionHelper.invokeMethod(delegateRunner, "runChild", method, notifier);
173            logMethod(method, System.currentTimeMillis() - startTime);
174        }
175    
176        private static void logMethod(final FrameworkMethod method, final long time) {
177            log.info("{}.{} (" + time + " ms)", method.getMethod().getDeclaringClass().getName(),
178                    method.getName());
179        }
180    
181    }
182