All Downloads are FREE. Search and download functionalities are using the official Maven repository.

patterntesting.concurrent.junit.ParallelProxyRunner Maven / Gradle / Ivy

/*
 * $Id: ParallelProxyRunner.java,v 1.2 2014/12/21 17:29:34 oboehm Exp $
 *
 * Copyright (c) 2011 by Oliver Boehm
 *
 * 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 orimplied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * (c)reated 18.12.2011 by oliver ([email protected])
 */

package patterntesting.concurrent.junit;

import java.util.*;
import java.util.concurrent.*;

import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import patterntesting.annotation.check.runtime.MayReturnNull;
import patterntesting.concurrent.junit.internal.RecordingRunNotifier;
import patterntesting.runtime.junit.ProxyRunner;
import patterntesting.runtime.util.Environment;

/**
 * This is the parallel version of the {@link ProxyRunner}.
 * Till 1.2.10-YEARS it was placed in the experimental package.
 *
 * @author oliver
 * @since 1.2 (18.12.2011)
 */
public class ParallelProxyRunner extends ProxyRunner {

    private static final Logger LOG = LoggerFactory.getLogger(ParallelProxyRunner.class);
    private final Map results = new HashMap();
    private final Executor executor = Executors.newCachedThreadPool();

    /**
     * Class for the result and the used FutureTask.
     * Also it looks like the Result class in ParallelRunner a
     * {@link RecordingRunNotifier} is stored in here (and not
     * a Statement).
     */
    private static class Result {
        /**
         * @param method the method
         */
        Result(final FrameworkMethod method) {
            this.method = method;
        }
        /** The called method. */
        FrameworkMethod method;
        /** The (future) result. */
        FutureTask future;
    }

    /**
     * Instantiates a new parallel proxy runner.
     *
     * @param testClass the test class
     * @throws InitializationError the initialization error
     */
    public ParallelProxyRunner(final Class testClass) throws InitializationError {
        super(testClass);
    }

    /**
     * Returns a {@link Statement}: Call {@link #runChild(FrameworkMethod, RunNotifier)}
     * on each object returned by {@link #getChildren()} (subject to any imposed
     * filter and sort).
     * @see org.junit.runners.ParentRunner#childrenInvoker(org.junit.runner.notification.RunNotifier)
     */
    @Override
    protected Statement childrenInvoker(final RunNotifier notifier) {
        if (!Environment.areThreadsAllowed()) {
            return super.childrenInvoker(notifier);
        }
        return new Statement() {
            @Override
            public void evaluate() {
                runChildren(notifier);
            }
        };
    }

    /**
     * The tests are started parallel and recorded at the beginning. Later in
     * runChild(..) only the recorded result will be returned.
     *
     * @param notifier the RunNotifier
     */
    private void runChildren(final RunNotifier notifier) {
        List methods = getChildren();
        LOG.trace("Running {} methods in parallel...", methods.size());
        this.recordResults(methods);
        for (final FrameworkMethod each : methods) {
            Runnable r = (new Runnable() {
                public void run() {
                    runChild(each, notifier);
                }
            });
            Thread t = new Thread(r, each.getName());
            t.start();
            LOG.trace("{} started.", t);
        }
    }

    /**
     * Replays the recorded test notifications. The tests itself were started
     * by the method recordResults(List).
     * In case of a failing test this test is started normal (not parallel) to
     * the delegated runner class the chance to do its initialization stuff
     * without being disturbed from other tests running in parallel.
     *
     * @param method the method
     * @param notifier the notifier
     * @see org.junit.runners.ParentRunner#runChild(java.lang.Object, org.junit.runner.notification.RunNotifier)
     */
    @Override
    protected void runChild(final FrameworkMethod method, final RunNotifier notifier) {
        if (this.shouldBeIgnored(method)) {
            Description description = describeChild(method);
            notifier.fireTestIgnored(description);
            return;
        }
        if (Environment.areThreadsAllowed()) {
            Result result = results.get(method);
//            Description description = describeChild(method);
//            notifier.fireTestStarted(description);
            try {
                RecordingRunNotifier recorder = result.future.get();
                // in case of failure we restart original test as fallback
                if (recorder.failureRecorded()) {
                    LOG.info("parallel call of " + method.getMethod()
                            + " failed - retry with normal call...");
                    super.runChild(method, notifier);
                } else {
                    recorder.replay(notifier);
                }
            } catch (InterruptedException ie) {
                LOG.info(method.getMethod() + " was interrupted - retrying...", ie);
                super.runChild(method, notifier);
            } catch (ExecutionException ee) {
                LOG.info("recording of " + method.getMethod() + " failed - calling it direct...", ee);
                super.runChild(method, notifier);
            }
        } else {
            super.runChild(method, notifier);
        }
    }



    /////   concurrency section   /////////////////////////////////////////////

    /**
     * Here we will start the tests in parallel and record the results.
     *
     * @param testMethods the test methods
     */
    private void recordResults(final List testMethods) {
        if (LOG.isTraceEnabled()) {
            LOG.trace(testMethods.size() + " test methods found: "
                    + testMethods);
        }
        for (FrameworkMethod method : testMethods) {
            Result result = new Result(method);
            results.put(method, result);
            triggerTest(result);
        }
    }

    /**
     * Here we trigger the test only and store the result (a Statement) in a
     * "Future" object.
     *
     * @param result this contains the JUnit method
     */
    @MayReturnNull
    private void triggerTest(final Result result) {
        Callable callable = new Callable() {
            public RecordingRunNotifier call() {
                return invokeTest(result.method);
            }
        };
        result.future = new FutureTask(callable);
        executor.execute(result.future);
    }

    /**
     * Here we start the original {@link #runChild(FrameworkMethod, RunNotifier)}
     * method of the wrapped JUnit runner.
     *
     * @param method the test method
     * @return the RecordingRunNotifier
     */
    private RecordingRunNotifier invokeTest(final FrameworkMethod method) {
        RecordingRunNotifier notifier = new RecordingRunNotifier();
        super.runChild(method, notifier);
        return notifier;
    }

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy