001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.assertions;
014
015import java.util.function.*;
016
017import org.apache.juneau.internal.*;
018
019/**
020 * Used for fluent assertion calls against throwables.
021 *
022 * @param <R> The return type.
023 */
024@FluentSetters(returns="FluentThrowableAssertion<R>")
025public class FluentThrowableAssertion<R> extends FluentAssertion<R> {
026
027   private final Throwable value;
028
029   /**
030    * Constructor.
031    *
032    * @param value The throwable being tested.
033    * @param returns The object to return after the test.
034    */
035   public FluentThrowableAssertion(Throwable value, R returns) {
036      this(null, value, returns);
037   }
038
039   /**
040    * Constructor.
041    *
042    * @param creator The assertion that created this assertion.
043    * @param value The throwable being tested.
044    * @param returns The object to return after the test.
045    */
046   public FluentThrowableAssertion(Assertion creator, Throwable value, R returns) {
047      super(creator, returns);
048      this.value = value;
049   }
050
051   /**
052    * Asserts that this throwable is of the specified type.
053    *
054    * <h5 class='section'>Example:</h5>
055    * <p class='bcode w800'>
056    *    <jc>// Asserts that the specified method throws a RuntimeException. </jc>
057    *    ThrowableAssertion.<jsm>assertThrown</jsm>(() -&gt; {foo.getBar();})
058    *       .isType(RuntimeException.<jk>class</jk>);
059    * </p>
060    *
061    * @param type The type.
062    * @return This object (for method chaining).
063    */
064   public R isType(Class<?> type) {
065      assertNotNull("type", type);
066      if (! type.isInstance(value))
067         throw error("Exception was not expected type.\n\tExpected=[{0}]\n\tActual=[{1}]", className(type), className(value));
068      return returns();
069   }
070
071   /**
072    * Asserts that this throwable or any parent throwables contains all of the specified substrings.
073    *
074    * <h5 class='section'>Example:</h5>
075    * <p class='bcode w800'>
076    *    <jc>// Asserts that the specified method throws an exception with 'foobar' somewhere in the messages. </jc>
077    *    ThrowableAssertion.<jsm>assertThrown</jsm>(() -&gt; {foo.getBar();}).contains(<js>"foobar"</js>);
078    * </p>
079    *
080    * @param substrings The substrings to check for.
081    * @return This object (for method chaining).
082    */
083   public R contains(String...substrings) {
084      assertNotNull("substrings", substrings);
085      exists();
086      for (String substring : substrings) {
087         if (substring != null) {
088            Throwable e2 = value;
089            boolean found = false;
090            while (e2 != null && ! found) {
091               found |= StringUtils.contains(e2.getMessage(), substring);
092               e2 = e2.getCause();
093            }
094            if (! found) {
095               throw error("Exception message did not contain expected substring.\n\tSubstring=[{0}]\n\tText=[{1}]", substring, value.getMessage());
096            }
097         }
098      }
099      return returns();
100   }
101
102   /**
103    * Asserts that this throwable has the specified message.
104    *
105    * <h5 class='section'>Example:</h5>
106    * <p class='bcode w800'>
107    *    <jc>// Asserts that the specified method throws an exception with the message 'foobar'.</jc>
108    *    ThrowableAssertion.<jsm>assertThrown</jsm>(() -&gt; {foo.getBar();}).is(<js>"foobar"</js>);
109    * </p>
110    *
111    * @param msg The message to check for.
112    * @return This object (for method chaining).
113    */
114   public R is(String msg) {
115      return message().is(msg);
116   }
117
118   /**
119    * Asserts that this throwable exists.
120    *
121    * <h5 class='section'>Example:</h5>
122    * <p class='bcode w800'>
123    *    <jc>// Asserts that the specified method throws any exception.</jc>
124    *    ThrowableAssertion.<jsm>assertThrown</jsm>(() -&gt; {foo.getBar();}).exists();
125    * </p>
126    *
127    * @return This object (for method chaining).
128    */
129   public R exists() {
130      if (value == null)
131         throw error("Exception was not thrown.");
132      return returns();
133   }
134
135   /**
136    * Asserts that this throwable doesn't exist.
137    *
138    * <h5 class='section'>Example:</h5>
139    * <p class='bcode w800'>
140    *    <jc>// Asserts that the specified method doesn't throw any exception.</jc>
141    *    ThrowableAssertion.<jsm>assertThrown</jsm>(() -&gt; {foo.getBar();}).notExists();
142    * </p>
143    *
144    * @return This object (for method chaining).
145    */
146   public R doesNotExist() {
147      if (value != null)
148         throw error("Exception was thrown.");
149      return returns();
150   }
151
152   /**
153    * Asserts that the value passes the specified predicate test.
154    *
155    * @param test The predicate to use to test the value.
156    * @return The response object (for method chaining).
157    * @throws AssertionError If assertion failed.
158    */
159   public R passes(Predicate<Throwable> test) throws AssertionError {
160      if (! test.test(value))
161         throw error("Value did not pass predicate test.\n\tValue=[{0}]", value);
162      return returns();
163   }
164
165   /**
166    * Asserts that the value passes the specified predicate test.
167    *
168    * @param c The class to cast to for the predicate.
169    * @param <T> The class to cast to for the predicate.
170    * @param test The predicate to use to test the value.
171    * @return The response object (for method chaining).
172    * @throws AssertionError If assertion failed.
173    */
174   @SuppressWarnings("unchecked")
175   public <T extends Throwable> R passes(Class<T> c, Predicate<T> test) throws AssertionError {
176      isType(c);
177      if (! test.test((T) value))
178         throw error("Value did not pass predicate test.\n\tValue=[{0}]", value);
179      return returns();
180   }
181
182   /**
183    * Returns an assertion against the throwable message.
184    *
185    * <h5 class='section'>Example:</h5>
186    * <p class='bcode w800'>
187    *    <jc>// Asserts that the specified method throws an exception with 'foobar' somewhere in the messages. </jc>
188    *    ThrowableAssertion.<jsm>assertThrown</jsm>(() -&gt; {foo.getBar();}).message().matches(<js>".*foobar.*"</js>);
189    * </p>
190    *
191    * @return An assertion against the throwable message.  Never <jk>null</jk>.
192    */
193   public FluentStringAssertion<R> message() {
194      return new FluentStringAssertion<>(this, value == null ? null : value.getMessage(), returns());
195   }
196
197   /**
198    * Returns an assertion against the throwable localized message.
199    *
200    * <h5 class='section'>Example:</h5>
201    * <p class='bcode w800'>
202    *    <jc>// Asserts that the specified method throws an exception with 'foobar' somewhere in the localized messages. </jc>
203    *    ThrowableAssertion.<jsm>assertThrown</jsm>(() -&gt; {foo.getBar();}).localizedMessage().matches(<js>".*foobar.*"</js>);
204    * </p>
205    *
206    * @return An assertion against the throwable localized message.  Never <jk>null</jk>.
207    */
208   public FluentStringAssertion<R> localizedMessage() {
209      return new FluentStringAssertion<>(this, value == null ? null : value.getLocalizedMessage(), returns());
210   }
211
212   /**
213    * Returns an assertion against the throwable localized message.
214    *
215    * <h5 class='section'>Example:</h5>
216    * <p class='bcode w800'>
217    *    <jc>// Asserts that the specified method throws an exception with 'foobar' somewhere in the stack trace. </jc>
218    *    ThrowableAssertion.<jsm>assertThrown</jsm>(() -&gt; {foo.getBar();}).stackTrace().contains(<js>"foobar"</js>);
219    * </p>
220    *
221    * @return An assertion against the throwable stacktrace.  Never <jk>null</jk>.
222    */
223   public FluentStringAssertion<R> stackTrace() {
224      return new FluentStringAssertion<>(this, value == null ? null : StringUtils.getStackTrace(value), returns());
225   }
226
227   /**
228    * Returns an assertion against the caused-by throwable.
229    *
230    * <h5 class='section'>Example:</h5>
231    * <p class='bcode w800'>
232    *    <jc>// Asserts that the specified method throws an exception whose caused-by message contains 'foobar'. </jc>
233    *    ThrowableAssertion.<jsm>assertThrown</jsm>(() -&gt; {foo.getBar();}).causedBy().message().contains(<js>"foobar"</js>);
234    * </p>
235    *
236    * @return An assertion against the caused-by.  Never <jk>null</jk>.
237    */
238   public FluentThrowableAssertion<R> causedBy() {
239      return new FluentThrowableAssertion<>(this, value == null ? null : value.getCause(), returns());
240   }
241
242   /**
243    * Returns an assertion against the throwable localized message.
244    *
245    * <h5 class='section'>Example:</h5>
246    * <p class='bcode w800'>
247    *    <jc>// Asserts that the specified method throws an exception with a caused-by RuntimeException containing 'foobar'</jc>
248    *    ThrowableAssertion.<jsm>assertThrown</jsm>(() -&gt; {foo.getBar();}).causedBy(RuntimeException.<jk>class</jk>).exists().contains(<js>"foobar"</js>);
249    * </p>
250    *
251    * @param throwableClass The class type to search for in the caused-by chain.
252    * @return An assertion against the caused-by throwable.  Never <jk>null</jk>.
253    */
254   public FluentThrowableAssertion<R> find(Class<?> throwableClass) {
255      Throwable t = value;
256      while (t != null) {
257         if (throwableClass.isInstance(t))
258            return new FluentThrowableAssertion<>(this, t, returns());
259         t = t.getCause();
260      }
261      return new FluentThrowableAssertion<>(this, null, returns());
262   }
263
264   // <FluentSetters>
265
266   @Override /* GENERATED - Assertion */
267   public FluentThrowableAssertion<R> msg(String msg, Object...args) {
268      super.msg(msg, args);
269      return this;
270   }
271
272   @Override /* GENERATED - Assertion */
273   public FluentThrowableAssertion<R> stderr() {
274      super.stderr();
275      return this;
276   }
277
278   @Override /* GENERATED - Assertion */
279   public FluentThrowableAssertion<R> stdout() {
280      super.stdout();
281      return this;
282   }
283
284   // </FluentSetters>
285}