001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.bct.assertions; 018 019import static java.util.Collections.*; 020import static org.apache.juneau.commons.utils.CollectionUtils.list; 021import static org.apache.juneau.commons.utils.Shorts.*; 022import static org.apache.juneau.commons.utils.Shorts.eq; 023import static org.apache.juneau.commons.utils.ThrowableUtils.*; 024 025import java.io.*; 026import java.util.*; 027import java.util.function.*; 028 029import org.apache.juneau.commons.function.*; 030 031 032/** 033 * Used for fluent assertion calls against throwables. 034 * 035 * <h5 class='section'>Test Methods:</h5> 036 * <p> 037 * <ul class='javatree'> 038 * <li class='jc'>{@link FluentObjectAssertion} 039 * <ul class='javatreec'> 040 * <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()} 041 * <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)} 042 * <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)} 043 * <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)} 044 * <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)} 045 * <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)} 046 * <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()} 047 * <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()} 048 * <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)} 049 * <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)} 050 * <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)} 051 * <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)} 052 * </ul> 053 * </ul> 054 * 055 * <h5 class='section'>Transform Methods:</h5> 056 * <p> 057 * <ul class='javatree'> 058 * <li class='jc'>{@link FluentThrowableAssertion} 059 * <ul class='javatreec'> 060 * <li class='jm'>{@link FluentThrowableAssertion#asMessage() asMessage()} 061 * <li class='jm'>{@link FluentThrowableAssertion#asMessages() asMessages()} 062 * <li class='jm'>{@link FluentThrowableAssertion#asLocalizedMessage() asLocalizedMessage()} 063 * <li class='jm'>{@link FluentThrowableAssertion#asLocalizedMessages() asLocalizedMessages()} 064 * <li class='jm'>{@link FluentThrowableAssertion#asStackTrace() asStackTrace()} 065 * <li class='jm'>{@link FluentThrowableAssertion#asCausedBy() asCausedBy()} 066 * <li class='jm'>{@link FluentThrowableAssertion#asCausedBy(Class) asCausedBy(Class)} 067 * <li class='jm'>{@link FluentThrowableAssertion#asFind(Class) asFind(Class)} 068 * </ul> 069 * <li class='jc'>{@link FluentObjectAssertion} 070 * <ul class='javatreec'> 071 * <li class='jm'>{@link FluentObjectAssertion#asString() asString()} 072 * <li class='jm'>{@link FluentObjectAssertion#asString(ThrowingFunction) asString(ThrowingFunction)} 073 * <li class='jm'>{@link FluentObjectAssertion#asTransformed(UnaryOperator) asTransformed(UnaryOperator)} 074 * <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()} 075 * </ul> 076 * </ul> 077 * 078 * <h5 class='section'>Configuration Methods:</h5> 079 * <p> 080 * <ul class='javatree'> 081 * <li class='jc'>{@link Assertion} 082 * <ul class='javatreec'> 083 * <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)} 084 * <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)} 085 * <li class='jm'>{@link Assertion#setSilent() setSilent()} 086 * <li class='jm'>{@link Assertion#setStdOut() setStdOut()} 087 * <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)} 088* </ul> 089 * </ul> 090 * 091 * <h5 class='section'>See Also:</h5><ul> 092 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem</a> 093 * </ul> 094 * 095 * @param <T> The throwable type. 096 * @param <R> The return type. 097 */ 098@SuppressWarnings({ 099 "java:S115", // Constants use UPPER_snakeCase convention 100 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 101}) 102public class FluentThrowableAssertion<T extends Throwable,R> extends FluentObjectAssertion<T,R> { 103 104 private static final Messages MESSAGES = Messages.of(FluentThrowableAssertion.class, "Messages"); 105 private static final String MSG_exceptionWasNotExpectedType = MESSAGES.getString("exceptionWasNotExpectedType"); 106 private static final String MSG_exceptionWasNotThrown = MESSAGES.getString("exceptionWasNotThrown"); 107 private static final String MSG_causedByExceptionNotExpectedType = MESSAGES.getString("causedByExceptionNotExpectedType"); 108 109 /** 110 * Chained constructor. 111 * 112 * <p> 113 * Used when transforming one assertion into another so that the assertion config can be used by the new assertion. 114 * 115 * @param creator 116 * The assertion that created this assertion. 117 * <br>Should be <jk>null</jk> if this is the top-level assertion. 118 * @param value 119 * The object being tested. 120 * <br>Can be <jk>null</jk> (assertions such as {@code isNull()} then operate on the absence of a value). 121 * @param returns 122 * The object to return after a test method is called. 123 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 124 * used on the same assertion. 125 */ 126 public FluentThrowableAssertion(Assertion creator, T value, R returns) { 127 super(creator, value, returns); 128 } 129 130 /** 131 * Constructor. 132 * 133 * @param value 134 * The object being tested. 135 * <br>Can be <jk>null</jk> (assertions such as {@code isNull()} then operate on the absence of a value). 136 * @param returns 137 * The object to return after a test method is called. 138 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 139 * used on the same assertion. 140 */ 141 public FluentThrowableAssertion(T value, R returns) { 142 this(null, value, returns); 143 } 144 145 /** 146 * Returns an assertion against the caused-by throwable. 147 * 148 * <h5 class='section'>Example:</h5> 149 * <p class='bjava'> 150 * <jc>// Asserts that the specified method throws an exception whose 151 * // caused-by message contains 'foobar'. </jc> 152 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 153 * .asCausedBy() 154 * .asMessage() 155 * .isPattern(<js>"foobar"</js>); 156 * </p> 157 * 158 * @return An assertion against the caused-by. Never <jk>null</jk>. 159 */ 160 public FluentThrowableAssertion<Throwable,R> asCausedBy() { 161 return asCausedBy(Throwable.class); 162 } 163 164 /** 165 * Returns an assertion against the caused-by throwable. 166 * 167 * <h5 class='section'>Example:</h5> 168 * <p class='bjava'> 169 * <jc>// Asserts that the specified method throws an exception whose 170 * // caused-by message contains 'foobar'. </jc> 171 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 172 * .asCausedBy(RuntimeException.<jk>class</jk>) 173 * .asMessage() 174 * .isPattern(<js>"foobar"</js>); 175 * </p> 176 * 177 * @param <X> The throwable type. 178 * @param type The expected exception type. 179 * @return An assertion against the caused-by. Never <jk>null</jk>. 180 */ 181 public <X extends Throwable> FluentThrowableAssertion<X,R> asCausedBy(Class<X> type) { 182 var t = map(Throwable::getCause).orElse(null); 183 if (t == null || type.isInstance(t)) 184 return new FluentThrowableAssertion<>(this, type.cast(t), returns()); 185 throw error(MSG_causedByExceptionNotExpectedType, cn(type), cn(t.getClass())); 186 } 187 188 /** 189 * Returns an assertion against the throwable localized message. 190 * 191 * <h5 class='section'>Example:</h5> 192 * <p class='bjava'> 193 * <jc>// Asserts that the specified method throws an exception with a 194 * // caused-by RuntimeException containing 'foobar'</jc> 195 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 196 * .findCausedBy(RuntimeException.<jk>class</jk>) 197 * .isExists() 198 * .asMessage() 199 * .isPattern(<js>"foobar"</js>); 200 * </p> 201 * 202 * @param <X> The throwable type. 203 * @param throwableClass The class type to search for in the caused-by chain. 204 * @return An assertion against the caused-by throwable. Never <jk>null</jk>. 205 */ 206 public <X extends Throwable> FluentThrowableAssertion<X,R> asFind(Class<X> throwableClass) { 207 Throwable t = orElse(null); 208 while (nn(t)) { 209 if (throwableClass.isInstance(t)) 210 return new FluentThrowableAssertion<>(this, throwableClass.cast(t), returns()); 211 t = t.getCause(); 212 } 213 return new FluentThrowableAssertion<>(this, (X)null, returns()); 214 } 215 216 /** 217 * Returns an assertion against the throwable localized message. 218 * 219 * <h5 class='section'>Example:</h5> 220 * <p class='bjava'> 221 * <jc>// Asserts that the specified method throws an exception with 222 * // 'foobar' somewhere in the localized messages. </jc> 223 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 224 * .asLocalizedMessage() 225 * .isPattern(<js>".*foobar.*"</js>); 226 * </p> 227 * 228 * @return An assertion against the throwable localized message. Never <jk>null</jk>. 229 */ 230 public FluentStringAssertion<R> asLocalizedMessage() { 231 return new FluentStringAssertion<>(this, map(Throwable::getLocalizedMessage).orElse(null), returns()); 232 } 233 234 /** 235 * Returns an assertion against the throwable message and all caused-by messages. 236 * 237 * <h5 class='section'>Example:</h5> 238 * <p class='bjava'> 239 * <jc>// Asserts that the specified method throws an exception with 240 * // 'foobar' somewhere in the messages. </jc> 241 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 242 * .asLocalizedMessages() 243 * .isPattern(<js>".*foobar.*"</js>); 244 * </p> 245 * 246 * @return An assertion against the throwable message. Never <jk>null</jk>. 247 */ 248 public FluentListAssertion<String,R> asLocalizedMessages() { 249 List<String> l = null; 250 Throwable t = orElse(null); 251 if (nn(t)) { 252 if (t.getCause() == null) 253 l = singletonList(t.getMessage()); 254 else { 255 l = list(); 256 while (nn(t)) { 257 l.add(localizedMessage(t)); 258 t = t.getCause(); 259 } 260 } 261 } 262 return new FluentListAssertion<>(this, l, returns()); 263 } 264 265 /** 266 * Returns an assertion against the throwable message. 267 * 268 * <h5 class='section'>Example:</h5> 269 * <p class='bjava'> 270 * <jc>// Asserts that the specified method throws an exception 271 * // with 'foobar' somewhere in the messages. </jc> 272 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 273 * .asMessage() 274 * .isPattern(<js>".*foobar.*"</js>); 275 * </p> 276 * 277 * @return An assertion against the throwable message. Never <jk>null</jk>. 278 */ 279 public FluentStringAssertion<R> asMessage() { 280 return new FluentStringAssertion<>(this, map(Throwable::getMessage).orElse(null), returns()); 281 } 282 283 /** 284 * Returns an assertion against the throwable message and all caused-by messages. 285 * 286 * <h5 class='section'>Example:</h5> 287 * <p class='bjava'> 288 * <jc>// Asserts that the specified method throws an exception with 289 * // 'foobar' somewhere in the messages. </jc> 290 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 291 * .asMessages() 292 * .isPattern(<js>".*foobar.*"</js>); 293 * </p> 294 * 295 * @return An assertion against the throwable message. Never <jk>null</jk>. 296 */ 297 public FluentListAssertion<String,R> asMessages() { 298 List<String> l = null; 299 Throwable t = orElse(null); 300 if (nn(t)) { 301 if (t.getCause() == null) 302 l = singletonList(t.getMessage()); 303 else { 304 l = list(); 305 while (nn(t)) { 306 l.add(t.getMessage()); 307 t = t.getCause(); 308 } 309 } 310 } 311 return new FluentListAssertion<>(this, l, returns()); 312 } 313 314 /** 315 * Returns an assertion against the throwable localized message. 316 * 317 * <h5 class='section'>Example:</h5> 318 * <p class='bjava'> 319 * <jc>// Asserts that the specified method throws an exception with 320 * // 'foobar' somewhere in the stack trace. </jc> 321 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 322 * .asStackTrace() 323 * .isPattern(<js>"foobar"</js>); 324 * </p> 325 * 326 * @return An assertion against the throwable stacktrace. Never <jk>null</jk>. 327 */ 328 public FluentStringListAssertion<R> asStackTrace() { 329 return new FluentStringListAssertion<>(this, valueIsNull() ? null : l(getStackTrace(value())), returns()); 330 } 331 332 @Override /* Overridden from FluentObjectAssertion */ 333 public FluentThrowableAssertion<T,R> asTransformed(UnaryOperator<T> function) { 334 return new FluentThrowableAssertion<>(this, function.apply(orElse(null)), returns()); 335 } 336 337 /** 338 * Asserts that this throwable is exactly the specified type. 339 * 340 * <h5 class='section'>Example:</h5> 341 * <p class='bjava'> 342 * <jc>// Asserts that the specified method throws a RuntimeException. </jc> 343 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 344 * .isExactType(RuntimeException.<jk>class</jk>); 345 * </p> 346 * 347 * @param type The type. 348 * @return The fluent return object. 349 */ 350 @Override 351 public R isExactType(Class<?> type) { 352 reqnn("type", type); 353 if (type != value().getClass()) 354 throw error(MSG_exceptionWasNotExpectedType, cn(type), cn(value())); 355 return returns(); 356 } 357 358 /** 359 * Asserts that this throwable exists. 360 * 361 * <h5 class='section'>Example:</h5> 362 * <p class='bjava'> 363 * <jc>// Asserts that the specified method throws any exception.</jc> 364 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()).isExists(); 365 * </p> 366 * 367 * @return The fluent return object. 368 */ 369 @Override 370 public R isExists() { 371 if (valueIsNull()) 372 throw error(MSG_exceptionWasNotThrown); 373 return returns(); 374 } 375 376 /** 377 * Asserts that this throwable is of the specified type. 378 * 379 * <h5 class='section'>Example:</h5> 380 * <p class='bjava'> 381 * <jc>// Asserts that the specified method throws a RuntimeException. </jc> 382 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 383 * .isType(RuntimeException.<jk>class</jk>); 384 * </p> 385 * 386 * @param parent The type. 387 * @return The fluent return object. 388 */ 389 @Override 390 public R isType(Class<?> parent) { 391 reqnn("parent", parent); 392 if (! parent.isInstance(value())) 393 throw error(MSG_exceptionWasNotExpectedType, cn(parent), cn(value())); 394 return returns(); 395 } 396 397 @Override /* Overridden from Assertion */ 398 public FluentThrowableAssertion<T,R> setMsg(String msg, Object...args) { 399 super.setMsg(msg, args); 400 return this; 401 } 402 403 @Override /* Overridden from Assertion */ 404 public FluentThrowableAssertion<T,R> setOut(PrintStream value) { 405 super.setOut(value); 406 return this; 407 } 408 409 @Override /* Overridden from Assertion */ 410 public FluentThrowableAssertion<T,R> setSilent() { 411 super.setSilent(); 412 return this; 413 } 414 415 @Override /* Overridden from Assertion */ 416 public FluentThrowableAssertion<T,R> setStdOut() { 417 super.setStdOut(); 418 return this; 419 } 420 421 @Override /* Overridden from Assertion */ 422 public FluentThrowableAssertion<T,R> setThrowable(Class<? extends RuntimeException> value) { 423 super.setThrowable(value); 424 return this; 425 } 426 427 @Override 428 protected boolean equals(Object o1, Object o2) { 429 if (o1 instanceof Throwable o1t && o2 instanceof Throwable o2t) 430 return eq(o1t, o2t, (x, y) -> eq(x.getClass(), y.getClass()) && eq(x.getMessage(), y.getMessage())); 431 return super.equals(o1, o2); 432 } 433}