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}