/
RecoverMethods.scala
223 lines (214 loc) · 8.88 KB
/
RecoverMethods.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/*
* Copyright 2001-2013 Artima, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.scalatest
import scala.concurrent.Future
import scala.concurrent.ExecutionContext
import scala.reflect.ClassTag
import org.scalactic.source
import org.scalatest.exceptions.StackDepthException._
import org.scalactic.source
import org.scalatest.compatible.Assertion
/**
* Offers two methods for transforming futures when exceptions are expected.
*
* <p>
* This trait offers two methods for testing for expected exceptions in the context of
* futures: <code>recoverToSucceededIf</code> and <code>recoverToExceptionIf</code>.
* Because this trait is mixed into trait <code>AsyncTestSuite</code>, both of its methods are
* available by default in any async-style suite.
* </p>
*
* <p>
* If you just want to ensure that a future fails with a particular exception type, and do
* not need to inspect the exception further, use <code>recoverToSucceededIf</code>:
* </p>
*
* <pre class="stHighlighted">
* recoverToSucceededIf[<span class="stType">IllegalStateException</span>] { <span class="stLineComment">// Result type: Future[Assertion]</span>
* emptyStackActor ? <span class="stType">Peek</span>
* }
* </pre>
*
* <p>
* The <code>recoverToSucceededIf</code> method performs a job similar to
* <a href="Assertions.html#expectedExceptions"><code>assertThrows</code></a>, except
* in the context of a future. It transforms a <code>Future</code> of any type into a
* <code>Future[Assertion]</code> that succeeds only if the original future fails with the specified
* exception. Here's an example in the REPL:
* </p>
*
* <pre class="stREPL">
* scala> import org.scalatest.RecoverMethods._
* import org.scalatest.RecoverMethods._
*
* scala> import scala.concurrent.Future
* import scala.concurrent.Future
*
* scala> import scala.concurrent.ExecutionContext.Implicits.global
* import scala.concurrent.ExecutionContext.Implicits.global
*
* scala> recoverToSucceededIf[IllegalStateException] {
* | Future { throw new IllegalStateException }
* | }
* res0: scala.concurrent.Future[org.scalatest.Assertion] = ...
*
* scala> res0.value
* res1: Option[scala.util.Try[org.scalatest.Assertion]] = Some(Success(Succeeded))
* </pre>
*
* <p>
* Otherwise it fails with an error message similar to those given by <code>assertThrows</code>:
* </p>
*
* <pre class="stREPL">
* scala> recoverToSucceededIf[IllegalStateException] {
* | Future { throw new RuntimeException }
* | }
* res2: scala.concurrent.Future[org.scalatest.Assertion] = ...
*
* scala> res2.value
* res3: Option[scala.util.Try[org.scalatest.Assertion]] =
* Some(Failure(org.scalatest.exceptions.TestFailedException: Expected exception
* java.lang.IllegalStateException to be thrown, but java.lang.RuntimeException
* was thrown))
*
* scala> recoverToSucceededIf[IllegalStateException] {
* | Future { 42 }
* | }
* res4: scala.concurrent.Future[org.scalatest.Assertion] = ...
*
* scala> res4.value
* res5: Option[scala.util.Try[org.scalatest.Assertion]] =
* Some(Failure(org.scalatest.exceptions.TestFailedException: Expected exception
* java.lang.IllegalStateException to be thrown, but no exception was thrown))
* </pre>
*
* <p>
* The <code>recoverToExceptionIf</code> method differs from the <code>recoverToSucceededIf</code> in
* its behavior when the assertion succeeds: <code>recoverToSucceededIf</code> yields a <code>Future[Assertion]</code>,
* whereas <code>recoverToExceptionIf</code> yields a <code>Future[T]</code>, where <code>T</code> is the
* expected exception type.
* </p>
*
* <pre class="stHighlighted">
* recoverToExceptionIf[<span class="stType">IllegalStateException</span>] { <span class="stLineComment">// Result type: Future[IllegalStateException]</span>
* emptyStackActor ? <span class="stType">Peek</span>
* }
* </pre>
*
* <p>
* In other words, <code>recoverToExpectionIf</code> is to
* <a href="Assertions.html#expectedExceptions"><code>intercept</code></a> as
* <code>recovertToSucceededIf</code> is to <code>assertThrows</code>. The first one allows you to perform further
* assertions on the expected exception. The second one gives you a result type that will satisfy the type checker
* at the end of the test body. Here's an example showing <code>recoverToExceptionIf</code> in the REPL:
* </p>
*
* <pre class="stREPL">
* scala> val futureEx =
* | recoverToExceptionIf[IllegalStateException] {
* | Future { throw new IllegalStateException("hello") }
* | }
* futureEx: scala.concurrent.Future[IllegalStateException] = ...
*
* scala> futureEx.value
* res6: Option[scala.util.Try[IllegalStateException]] =
* Some(Success(java.lang.IllegalStateException: hello))
*
* scala> futureEx map { ex => assert(ex.getMessage == "world") }
* res7: scala.concurrent.Future[org.scalatest.Assertion] = ...
*
* scala> res7.value
* res8: Option[scala.util.Try[org.scalatest.Assertion]] =
* Some(Failure(org.scalatest.exceptions.TestFailedException: "[hello]" did not equal "[world]"))
* </pre>
*
* @author Bill Venners
*/
trait RecoverMethods {
/**
* Transforms a future of any type into a <code>Future[T]</code>, where <code>T</code> is a given
* expected exception type, which succeeds if the given future
* completes with a <code>Failure</code> containing the specified exception type.
*
* <p>
* See the main documentation for this trait for more detail and examples.
* </p>
*
* @param future A future of any type, which you expect to fail with an exception of the specified type T
* @return a Future[T] containing on success the expected exception, or containing on failure
* a <code>TestFailedException</code>
*/
def recoverToExceptionIf[T <: AnyRef](future: Future[Any])(implicit classTag: ClassTag[T], exCtx: ExecutionContext, pos: source.Position): Future[T] = {
val clazz = classTag.runtimeClass
future.failed.transform(
ex =>
if (!clazz.isAssignableFrom(ex.getClass)) {
val message = Resources.wrongException(clazz.getName, ex.getClass.getName)
throw newAssertionFailedExceptionForRecover(Some(message), Some(ex), pos)
}
else ex.asInstanceOf[T]
,
ex => {
val message = Resources.exceptionExpected(clazz.getName)
throw newAssertionFailedExceptionForRecover(Some(message), None, pos)
}
)
}
/**
* Transforms a future of any type into a <code>Future[Assertion]</code> that succeeds if the future
* completes with a <code>Failure</code> containing the specified exception type.
*
* <p>
* See the main documentation for this trait for more detail and examples.
* </p>
*
* @param future A future of any type, which you expect to fail with an exception of the specified type T
* @return a Future[Assertion] containing on success the <code>Succeeded</code> singleton, or containing on failure
* a <code>TestFailedException</code>
*/
def recoverToSucceededIf[T <: AnyRef](future: Future[Any])(implicit classTag: ClassTag[T], exCtx: ExecutionContext, pos: source.Position): Future[Assertion] = {
val clazz = classTag.runtimeClass
future.failed.transform(
rawEx => {
val ex =
rawEx match {
case execEx: java.util.concurrent.ExecutionException => execEx.getCause
case other => other
}
if (!clazz.isAssignableFrom(ex.getClass)) {
val message = Resources.wrongException(clazz.getName, ex.getClass.getName)
throw newAssertionFailedExceptionForRecover(Some(message), Some(ex), pos)
}
else Succeeded
},
ex => {
val message = Resources.exceptionExpected(clazz.getName)
throw newAssertionFailedExceptionForRecover(Some(message), None, pos)
}
)
}
private[scalatest] def newAssertionFailedExceptionForRecover(optionalMessage: Option[String], optionalCause: Option[Throwable], pos: source.Position): Throwable =
new org.scalatest.exceptions.TestFailedException(toExceptionFunction(optionalMessage), optionalCause, pos)
}
/**
* Companion object that facilitates the importing of <code>RecoverMethods</code>'s method as
* an alternative to mixing it in. One use case is to import <code>RecoverMethods</code>'s method so you can use
* it in the Scala interpreter.
*
* @author Bill Venners
*/
object RecoverMethods extends RecoverMethods