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

patterntesting.concurrent.junit.internal.RecordingRunNotifier Maven / Gradle / Ivy

/*
 * $Id: RecordingRunNotifier.java,v 1.19 2016/12/18 21:56:49 oboehm Exp $
 *
 * Copyright (c) 2012 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.internal;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runner.notification.StoppedByUserException;
import patterntesting.runtime.util.Converter;
import patterntesting.runtime.util.ThreadUtil;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeoutException;

/**
 * This is a RunNotifier which records the single fire events. These events
 * can be replayed later.
 *
 * @author oliver
 * @since 1.2 (18.12.2011)
 */
public final class RecordingRunNotifier extends RunNotifier {

    private static final Logger log = LogManager.getLogger(RecordingRunNotifier.class);
    private static final Object NO_ARG = createNoArgDescription();

    private static Object createNoArgDescription() {
        try {
            return Description.createTestDescription(RecordingRunNotifier.class, "NO_ARG");
        } catch (RuntimeException ex) {
            log.error("Cannot create test description", ex);
            return "NO_ARG";
        }
    }

    /**
     * The Enum FireEvent. It is protected because it is used for testing.
     */
    protected enum FireEvent {
        /** Testrun started. */
        TEST_RUN_STARTED,
        /** Testrun finished. */
        TEST_RUN_FINISHED,
        /** Test started. */
        TEST_STARTED,
        /** Test failure. */
        TEST_FAILURE,
        /** Test assumption failed. */
        TEST_ASSUMPTION_FAILED,
        /** Test ignored. */
        TEST_IGNORED,
        /** Test finished. */
        TEST_FINISHED,
        /** Please stop. */
        PLEASE_STOP;
    }

    /**
     * Class for the recorded events and arguments.
     * The class is protected because it is used for testing.
     */
    protected static class FireRecord {
        /**
         * Instantiates a new fire record.
         *
         * @param event the event
         * @param arg the arg
         */
        FireRecord(final FireEvent event, final Object arg) {
            this.event = event;
            this.arg = arg;
            this.time = System.currentTimeMillis();
        }
        /** The stored event. */
        final FireEvent event;
        /** The stored argument. */
        private final Object arg;
        /** The recorded start time. */
        final long time;
        /**
         * Gets the description.
         *
         * @return the description
         */
        Description getDescription() {
            if (this.arg instanceof Failure) {
                return ((Failure) this.arg).getDescription();
            } else if (this.arg instanceof Description) {
                return (Description) this.arg;
            } else {
                throw new IllegalStateException("arg is a " + arg.getClass().getName()
                    + "; expected a Failure or Description. Arg: " + arg);
            }
        }
        /**
         * Gets the result.
         *
         * @return the result
         */
        Result getResult() {
            return (Result) this.arg;
        }
        /**
         * Gets the failure.
         *
         * @return the failure
         */
        Failure getFailure() {
            return (Failure) this.arg;
        }

        /**
         * To string.
         *
         * @return the string
         * @see Object#toString()
         */
        @Override
        public String toString() {
            return this.arg + " " + this.event;
        }
    }

    /** The recorded events and arguments. */
    private final Queue startRecords = new ConcurrentLinkedQueue();
    private final Map> endRecords = new ConcurrentHashMap>();
    private FireRecord firstRecord;
    private FireRecord lastRecord;

    /**
     * Fire test run started.
     *
     * @param description the description
     * @see RunNotifier#fireTestRunStarted(Description)
     */
    @Override
    public void fireTestRunStarted(final Description description) {
        this.recordStart(FireEvent.TEST_RUN_STARTED, description);
    }

    /**
     * Fire test run finished.
     *
     * @param result the result
     * @see RunNotifier#fireTestRunFinished(Result)
     */
    @Override
    public void fireTestRunFinished(final Result result) {
        this.recordEnd(FireEvent.TEST_RUN_FINISHED, result);
    }

    /**
     * Fire test started.
     *
     * @param description the description
     * @throws StoppedByUserException the stopped by user exception
     * @see RunNotifier#fireTestStarted(Description)
     */
    @Override
    public void fireTestStarted(final Description description) throws StoppedByUserException {
        this.recordStart(FireEvent.TEST_STARTED, description);
    }

    /**
     * Fire test failure.
     *
     * @param failure the failure
     * @see RunNotifier#fireTestFailure(Failure)
     */
    @Override
    public void fireTestFailure(final Failure failure) {
        this.recordEnd(FireEvent.TEST_FAILURE, failure);
    }

    /**
     * Fire test assumption failed.
     *
     * @param failure the failure
     * @see RunNotifier#fireTestAssumptionFailed(Failure)
     */
    @Override
    public void fireTestAssumptionFailed(final Failure failure) {
        this.recordEnd(FireEvent.TEST_ASSUMPTION_FAILED, failure);
    }

    /**
     * Fire test ignored.
     *
     * @param description the description
     * @see RunNotifier#fireTestIgnored(Description)
     */
    @Override
    public void fireTestIgnored(final Description description) {
        this.recordStart(FireEvent.TEST_IGNORED, description);
    }

    /**
     * Fire test finished.
     *
     * @param description the description
     * @see RunNotifier#fireTestFinished(Description)
     */
    @Override
    public void fireTestFinished(final Description description) {
        this.recordEnd(FireEvent.TEST_FINISHED, description);
    }

    /**
     * Please stop.
     *
     * @see RunNotifier#pleaseStop()
     */
    @Override
    public void pleaseStop() {
        this.recordEnd(FireEvent.PLEASE_STOP, NO_ARG);
    }

    /**
     * Record a start event.
     * 

* NOTE: Also an ignore event is considered as start event. *

* * @param event the event * @param arg the argument to be stored for the given event. */ private void recordStart(final FireEvent event, final Object arg) { FireRecord frec = new FireRecord(event, arg); log.trace("RECORDING: {}", frec); this.startRecords.add(frec); if (this.firstRecord == null) { this.firstRecord = frec; } this.endRecords.put(frec.getDescription(), new ArrayList()); this.lastRecord = frec; } private void recordEnd(final FireEvent event, final Object arg) { FireRecord frec = new FireRecord(event, arg); log.trace("RECORDING: {}", frec); List endRecordList = this.endRecords.get(frec.getDescription()); endRecordList.add(frec); this.lastRecord = frec; } /** * Checks if something was recorded. * * @return true, if is empty */ public boolean isEmpty() { return this.startRecords.isEmpty(); } /** * Replays the recorded events for the given notifier. * But not the TEST_STARTED event - this must be sent by * the calling method. *

* We have one problem with the used queue here: if the queue is empty * we do not know if there are some tests are missing - perhaps some tests * are not yet finised. *

*

* We keep the start and end events separate. The end events are stored * in a hash map. Normally we would remove the replayed end event but * this does not work - probably the synchronisation does not work perfect. *

* * @param notifier the notifier */ public void replay(final RunNotifier notifier) { while (!this.startRecords.isEmpty()) { FireRecord frec = this.startRecords.poll(); replay(notifier, Arrays.asList(frec)); if (frec.event == FireEvent.TEST_STARTED) { Description descr = frec.getDescription(); try { List endRecordList = waitForEndRecordList(descr, 20000); replay(notifier, endRecordList); //this.endRecords.remove(descr); } catch (TimeoutException ex) { log.warn("Giving up to wait longer - will continue with next event!", ex); } } } } private List waitForEndRecordList(final Description descr, final long waitTimeInMillis) throws TimeoutException { long endTime = System.currentTimeMillis() + waitTimeInMillis; while (System.currentTimeMillis() < endTime) { List endRecordList = this.endRecords.get(descr); if ((endRecordList != null) && !endRecordList.isEmpty()) { return endRecordList; } ThreadUtil.sleep(); } throw new TimeoutException("waited " + Converter.getTimeAsString(waitTimeInMillis) + " in vain for end of " + descr.getDisplayName()); } /** * Replays the record events till the given thread has ended. * * @param notifier the notifier * @param waitFor the thread to wait for */ @SuppressWarnings("squid:S2142") public void replay(final RunNotifier notifier, final Thread waitFor) { this.replay(notifier); try { while (waitFor.getState() != Thread.State.TERMINATED) { waitFor.join(1); log.trace("Waited for {} to replay {}.", waitFor, notifier); replay(notifier); } } catch (InterruptedException ie) { log.info("{} was interrupted", waitFor, ie); this.replay(notifier, Arrays.asList(new FireRecord(FireEvent.PLEASE_STOP, NO_ARG))); } } private void replay(final RunNotifier notifier, final List frecList) { for (FireRecord frec : frecList) replayRecord(notifier, frec); } private void replayRecord(final RunNotifier notifier, final FireRecord frec) { log.trace("REPLAYING: {}", frec); switch (frec.event) { case TEST_RUN_STARTED: notifier.fireTestRunStarted(frec.getDescription()); break; case TEST_RUN_FINISHED: notifier.fireTestRunFinished(frec.getResult()); break; case TEST_STARTED: notifier.fireTestStarted(frec.getDescription()); break; case TEST_FAILURE: notifier.fireTestFailure(frec.getFailure()); break; case TEST_ASSUMPTION_FAILED: notifier.fireTestAssumptionFailed(frec.getFailure()); break; case TEST_IGNORED: notifier.fireTestIgnored(frec.getDescription()); break; case TEST_FINISHED: notifier.fireTestFinished(frec.getDescription()); break; case PLEASE_STOP: notifier.pleaseStop(); break; default: log.warn("Unknown event \"{}\" recorded.", frec.event); break; } } /** * Looks if a failure event was recorded. * * @return true, if TEST_FAILURE or TEST_ASSUMPTION_FAILED event was recorded */ @SuppressWarnings("incomplete-switch") public boolean failureRecorded() { for (List frecList : this.endRecords.values()) { for (FireRecord frec : frecList) { switch (frec.event) { case TEST_FAILURE: case TEST_ASSUMPTION_FAILED: return true; } } } return false; } /** * Gets the time difference between first and last recorded event. * * @return the time in milliseconds */ public long getTimeInMillis() { if ((this.firstRecord == null) || (this.lastRecord == null)) { throw new IllegalStateException("nothing recorded"); } return this.lastRecord.time - this.firstRecord.time; } /** * A toString() implementation for better logging and easier debugging. * * @return the string */ @Override public String toString() { return this.getClass().getSimpleName() + " with " + this.startRecords.size() + "/" + this.endRecords.size() + " start/end record(s)"; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy