TestListenerRule.java

/**
 * Powerunit - A JDK1.8 test framework
 * Copyright (C) 2014 Mathieu Boretti.
 *
 * This file is part of Powerunit
 *
 * Powerunit is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Powerunit is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Powerunit. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.powerunit.rules;

import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import ch.powerunit.Statement;
import ch.powerunit.TestContext;
import ch.powerunit.TestRule;
import ch.powerunit.exception.AssumptionError;

/**
 * This can be use to support taking action depending on the issue of the test.
 * <p>
 * The purpose is here to provide an interface that can be implemented by rule
 * implementor to do action, before, after and on some condition.
 * <p>
 * Rule implementer should implements this interface and then implement the
 * required methods (which are do-nothing method by default).
 *
 * The order of execution is the following :
 * <ul>
 * <li>Before the test, the method {@link #onStart(TestContext)} is executed.</li>
 * <li>Then the test is executed.</li>
 * <li>In case of error/failure, one (and only one) of the next methods is
 * executed
 * <ol>
 * <li>{@link #onFailure(TestContext, AssertionError)} in case of test failure.</li>
 * <li>{@link #onError(TestContext, Throwable)} in case of test error.</li>
 * <li>{@link #onAssumptionSkip(TestContext, AssumptionError)} in case of test
 * skip.</li>
 * </ol>
 * </li>
 * <li>In all case, after the test, and after the previous method in case of
 * error/failure, the method {@link #onEnd(TestContext)} is executed.</li>
 * </ul>
 *
 * The {@link ExternalResource} rule implements this interface to provide simple
 * use case (action before and always after test).
 *
 * @author borettim
 * @see ExternalResource
 */
public interface TestListenerRule extends TestRule {

	/**
	 * Method used at the start of the test.
	 * <p>
	 * Default implementation is to do nothing.
	 * 
	 * @param context
	 *            the test context
	 */
	default void onStart(TestContext<Object> context) {
		// Do nothing as default
	}

	/**
	 * Method used when a failure happened.
	 * <p>
	 * Default implementation is to do nothing.
	 * 
	 * @param context
	 *            the test context
	 * @param af
	 *            the failure
	 */
	default void onFailure(TestContext<Object> context, AssertionError af) {
		// Do nothing as default
	}

	/**
	 * Method used when an error happened.
	 * <p>
	 * Default implementation is to do nothing.
	 * 
	 * @param context
	 *            the test context
	 * @param error
	 *            the error
	 */
	default void onError(TestContext<Object> context, Throwable error) {
		// Do nothing as default
	}

	/**
	 * Method used when an assumption error happened.
	 * <p>
	 * Default implementation is to do nothing.
	 * 
	 * @param context
	 *            the test context
	 * @param error
	 *            the assumption error
	 */
	default void onAssumptionSkip(TestContext<Object> context,
			AssumptionError error) {
		// Do nothing as default
	}

	/**
	 * Method used at the end of the test.
	 * <p>
	 * Default implementation is to do nothing.
	 * 
	 * @param context
	 *            the test context
	 */
	default void onEnd(TestContext<Object> context) {
		// Do nothing as default
	}

	@Override
	default Statement<TestContext<Object>, Throwable> computeStatement(
			Statement<TestContext<Object>, Throwable> inner) {
		return (p) -> {
			try {
				onStart(p);
				inner.run(p);
			} catch (AssertionError af) {
				onFailure(p, af);
				throw af;
			} catch (InternalError ie) {
				onError(p, ie);
				throw ie;
			} catch (AssumptionError ie) {
				onAssumptionSkip(p, ie);
				throw ie;
			} catch (Throwable t) {
				onError(p, t);
				throw t;
			} finally {
				onEnd(p);
			}
		};
	}

	/**
	 * Build a {@link TestListenerRule} based on the various method.
	 * 
	 * @param onStart
	 *            {@link #onStart(TestContext) the action to be done before the
	 *            test start}. If null, nothing is done.
	 * @param onEnd
	 *            {@link #onEnd(TestContext) the action to be done after the
	 *            test end}. If null, nothing is done.
	 * @param onFailure
	 *            {@link #onFailure(TestContext, AssertionError) the action to
	 *            be done in case of failure}. If null, nothing is done.
	 * @param onError
	 *            {@link #onError(TestContext, Throwable) the action to be done
	 *            in case of error}. If null, nothing is done.
	 * @param onAssumptionSkip
	 *            {@link #onAssumptionSkip(TestContext, AssumptionError) the
	 *            action to be done in case of assumption skipped}. If null
	 *            nothing is done.
	 * @return the Test Rule.
	 * @since 0.4.0
	 */
	static TestListenerRule of(Consumer<TestContext<Object>> onStart,
			Consumer<TestContext<Object>> onEnd,
			BiConsumer<TestContext<Object>, AssertionError> onFailure,
			BiConsumer<TestContext<Object>, Throwable> onError,
			BiConsumer<TestContext<Object>, AssumptionError> onAssumptionSkip) {
		return new TestListenerRule() {

			@Override
			public void onStart(TestContext<Object> context) {
				if (onStart != null) {
					onStart.accept(context);
				}
			}

			@Override
			public void onFailure(TestContext<Object> context, AssertionError af) {
				if (onFailure != null) {
					onFailure.accept(context, af);
				}
			}

			@Override
			public void onError(TestContext<Object> context, Throwable error) {
				if (onError != null) {
					onError.accept(context, error);
				}
			}

			@Override
			public void onAssumptionSkip(TestContext<Object> context,
					AssumptionError error) {
				if (onAssumptionSkip != null) {
					onAssumptionSkip.accept(context, error);
				}
			}

			@Override
			public void onEnd(TestContext<Object> context) {
				if (onEnd != null) {
					onEnd.accept(context);
				}
			}

		};
	}

	/**
	 * Build a {@link TestListenerRule} with only an action at start.
	 * 
	 * @param onStart
	 *            {@link #onStart(TestContext) the action to be done before the
	 *            test start}.
	 * @return the Test Rule.
	 * @since 0.4.0
	 */
	static TestListenerRule onStart(Consumer<TestContext<Object>> onStart) {
		return of(Objects.requireNonNull(onStart, "onStart can't be null"),
				null, null, null, null);
	}

	/**
	 * Build a {@link TestListenerRule} with only an action at end.
	 * 
	 * @param onEnd
	 *            {@link #onEnd(TestContext) the action to be done after the
	 *            test end}.
	 * @return the Test Rule.
	 * @since 0.4.0
	 */
	static TestListenerRule onEnd(Consumer<TestContext<Object>> onEnd) {
		return of(null, Objects.requireNonNull(onEnd, "onEnd can't be null"),
				null, null, null);
	}
}