001/**
002 * Powerunit - A JDK1.8 test framework
003 * Copyright (C) 2014 Mathieu Boretti.
004 *
005 * This file is part of Powerunit
006 *
007 * Powerunit is free software: you can redistribute it and/or modify
008 * it under the terms of the GNU General Public License as published by
009 * the Free Software Foundation, either version 3 of the License, or
010 * (at your option) any later version.
011 *
012 * Powerunit is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015 * GNU General Public License for more details.
016 *
017 * You should have received a copy of the GNU General Public License
018 * along with Powerunit. If not, see <http://www.gnu.org/licenses/>.
019 */
020package ch.powerunit.rules;
021
022import java.util.Objects;
023import java.util.function.BiConsumer;
024import java.util.function.Consumer;
025
026import ch.powerunit.Statement;
027import ch.powerunit.TestContext;
028import ch.powerunit.TestRule;
029import ch.powerunit.exception.AssumptionError;
030
031/**
032 * This can be use to support taking action depending on the issue of the test.
033 * <p>
034 * The purpose is here to provide an interface that can be implemented by rule
035 * implementor to do action, before, after and on some condition.
036 * <p>
037 * Rule implementer should implements this interface and then implement the
038 * required methods (which are do-nothing method by default).
039 *
040 * The order of execution is the following :
041 * <ul>
042 * <li>Before the test, the method {@link #onStart(TestContext)} is executed.</li>
043 * <li>Then the test is executed.</li>
044 * <li>In case of error/failure, one (and only one) of the next methods is
045 * executed
046 * <ol>
047 * <li>{@link #onFailure(TestContext, AssertionError)} in case of test failure.</li>
048 * <li>{@link #onError(TestContext, Throwable)} in case of test error.</li>
049 * <li>{@link #onAssumptionSkip(TestContext, AssumptionError)} in case of test
050 * skip.</li>
051 * </ol>
052 * </li>
053 * <li>In all case, after the test, and after the previous method in case of
054 * error/failure, the method {@link #onEnd(TestContext)} is executed.</li>
055 * </ul>
056 *
057 * The {@link ExternalResource} rule implements this interface to provide simple
058 * use case (action before and always after test).
059 *
060 * @author borettim
061 * @see ExternalResource
062 */
063public interface TestListenerRule extends TestRule {
064
065        /**
066         * Method used at the start of the test.
067         * <p>
068         * Default implementation is to do nothing.
069         * 
070         * @param context
071         *            the test context
072         */
073        default void onStart(TestContext<Object> context) {
074                // Do nothing as default
075        }
076
077        /**
078         * Method used when a failure happened.
079         * <p>
080         * Default implementation is to do nothing.
081         * 
082         * @param context
083         *            the test context
084         * @param af
085         *            the failure
086         */
087        default void onFailure(TestContext<Object> context, AssertionError af) {
088                // Do nothing as default
089        }
090
091        /**
092         * Method used when an error happened.
093         * <p>
094         * Default implementation is to do nothing.
095         * 
096         * @param context
097         *            the test context
098         * @param error
099         *            the error
100         */
101        default void onError(TestContext<Object> context, Throwable error) {
102                // Do nothing as default
103        }
104
105        /**
106         * Method used when an assumption error happened.
107         * <p>
108         * Default implementation is to do nothing.
109         * 
110         * @param context
111         *            the test context
112         * @param error
113         *            the assumption error
114         */
115        default void onAssumptionSkip(TestContext<Object> context,
116                        AssumptionError error) {
117                // Do nothing as default
118        }
119
120        /**
121         * Method used at the end of the test.
122         * <p>
123         * Default implementation is to do nothing.
124         * 
125         * @param context
126         *            the test context
127         */
128        default void onEnd(TestContext<Object> context) {
129                // Do nothing as default
130        }
131
132        @Override
133        default Statement<TestContext<Object>, Throwable> computeStatement(
134                        Statement<TestContext<Object>, Throwable> inner) {
135                return (p) -> {
136                        try {
137                                onStart(p);
138                                inner.run(p);
139                        } catch (AssertionError af) {
140                                onFailure(p, af);
141                                throw af;
142                        } catch (InternalError ie) {
143                                onError(p, ie);
144                                throw ie;
145                        } catch (AssumptionError ie) {
146                                onAssumptionSkip(p, ie);
147                                throw ie;
148                        } catch (Throwable t) {
149                                onError(p, t);
150                                throw t;
151                        } finally {
152                                onEnd(p);
153                        }
154                };
155        }
156
157        /**
158         * Build a {@link TestListenerRule} based on the various method.
159         * 
160         * @param onStart
161         *            {@link #onStart(TestContext) the action to be done before the
162         *            test start}. If null, nothing is done.
163         * @param onEnd
164         *            {@link #onEnd(TestContext) the action to be done after the
165         *            test end}. If null, nothing is done.
166         * @param onFailure
167         *            {@link #onFailure(TestContext, AssertionError) the action to
168         *            be done in case of failure}. If null, nothing is done.
169         * @param onError
170         *            {@link #onError(TestContext, Throwable) the action to be done
171         *            in case of error}. If null, nothing is done.
172         * @param onAssumptionSkip
173         *            {@link #onAssumptionSkip(TestContext, AssumptionError) the
174         *            action to be done in case of assumption skipped}. If null
175         *            nothing is done.
176         * @return the Test Rule.
177         * @since 0.4.0
178         */
179        static TestListenerRule of(Consumer<TestContext<Object>> onStart,
180                        Consumer<TestContext<Object>> onEnd,
181                        BiConsumer<TestContext<Object>, AssertionError> onFailure,
182                        BiConsumer<TestContext<Object>, Throwable> onError,
183                        BiConsumer<TestContext<Object>, AssumptionError> onAssumptionSkip) {
184                return new TestListenerRule() {
185
186                        @Override
187                        public void onStart(TestContext<Object> context) {
188                                if (onStart != null) {
189                                        onStart.accept(context);
190                                }
191                        }
192
193                        @Override
194                        public void onFailure(TestContext<Object> context, AssertionError af) {
195                                if (onFailure != null) {
196                                        onFailure.accept(context, af);
197                                }
198                        }
199
200                        @Override
201                        public void onError(TestContext<Object> context, Throwable error) {
202                                if (onError != null) {
203                                        onError.accept(context, error);
204                                }
205                        }
206
207                        @Override
208                        public void onAssumptionSkip(TestContext<Object> context,
209                                        AssumptionError error) {
210                                if (onAssumptionSkip != null) {
211                                        onAssumptionSkip.accept(context, error);
212                                }
213                        }
214
215                        @Override
216                        public void onEnd(TestContext<Object> context) {
217                                if (onEnd != null) {
218                                        onEnd.accept(context);
219                                }
220                        }
221
222                };
223        }
224
225        /**
226         * Build a {@link TestListenerRule} with only an action at start.
227         * 
228         * @param onStart
229         *            {@link #onStart(TestContext) the action to be done before the
230         *            test start}.
231         * @return the Test Rule.
232         * @since 0.4.0
233         */
234        static TestListenerRule onStart(Consumer<TestContext<Object>> onStart) {
235                return of(Objects.requireNonNull(onStart, "onStart can't be null"),
236                                null, null, null, null);
237        }
238
239        /**
240         * Build a {@link TestListenerRule} with only an action at end.
241         * 
242         * @param onEnd
243         *            {@link #onEnd(TestContext) the action to be done after the
244         *            test end}.
245         * @return the Test Rule.
246         * @since 0.4.0
247         */
248        static TestListenerRule onEnd(Consumer<TestContext<Object>> onEnd) {
249                return of(null, Objects.requireNonNull(onEnd, "onEnd can't be null"),
250                                null, null, null);
251        }
252}