/
OptionValues.scala
172 lines (164 loc) · 6.51 KB
/
OptionValues.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
/*
* 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 org.scalactic._
import java.util.NoSuchElementException
import org.scalatest.exceptions.StackDepthException
import org.scalatest.exceptions.TestFailedException
/**
* Trait that provides an implicit conversion that adds a <code>value</code> method
* to <code>Option</code>, which will return the value of the option if it is defined,
* or throw <code>TestFailedException</code> if not.
*
* <p>
* This construct allows you to express in one statement that an option should be defined
* and that its value should meet some expectation. Here's an example:
* </p>
*
* <pre class="stHighlighted">
* opt.value should be > <span class="stLiteral">9</span>
* </pre>
*
* <p>
* Or, using an assertion instead of a matcher expression:
* </p>
*
* <pre class="stHighlighted">
* assert(opt.value > <span class="stLiteral">9</span>)
* </pre>
*
* <p>
* Were you to simply invoke <code>get</code> on the <code>Option</code>,
* if the option wasn't defined, it would throw a <code>NoSuchElementException</code>:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">val</span> opt: <span class="stType">Option[Int]</span> = <span class="stType">None</span>
* <br/>opt.get should be > <span class="stLiteral">9</span> <span class="stLineComment">// opt.get throws NoSuchElementException</span>
* </pre>
*
* <p>
* The <code>NoSuchElementException</code> would cause the test to fail, but without providing a <a href="exceptions/StackDepth.html">stack depth</a> pointing
* to the failing line of test code. This stack depth, provided by <a href="exceptions/TestFailedException.html"><code>TestFailedException</code></a> (and a
* few other ScalaTest exceptions), makes it quicker for
* users to navigate to the cause of the failure. Without <code>OptionValues</code>, to get
* a stack depth exception you would need to make two statements, like this:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">val</span> opt: <span class="stType">Option[Int]</span> = <span class="stType">None</span>
* <br/>opt should be (<span class="stQuotedString">'defined</span>) <span class="stLineComment">// throws TestFailedException</span>
* opt.get should be > <span class="stLiteral">9</span>
* </pre>
*
* <p>
* The <code>OptionValues</code> trait allows you to state that more concisely:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">val</span> opt: <span class="stType">Option[Int]</span> = <span class="stType">None</span>
* <br/>opt.value should be > <span class="stLiteral">9</span> <span class="stLineComment">// opt.value throws TestFailedException</span>
* </pre>
*/
trait OptionValues {
import scala.language.implicitConversions
/**
* Implicit conversion that adds a <code>value</code> method to <code>Option</code>.
*
* @param opt the <code>Option</code> on which to add the <code>value</code> method
*/
// SKIP-DOTTY-START
implicit def convertOptionToValuable[T](opt: Option[T])(implicit pos: source.Position): Valuable[T] = new Valuable(opt, pos)
// SKIP-DOTTY-END
//DOTTY-ONLY implicit def convertOptionToValuable[T](opt: Option[T])(implicit pos: source.Position): OptionValuable[T] = new OptionValuable(opt, pos)
// SKIP-DOTTY-START
/**
* Wrapper class that adds a <code>value</code> method to <code>Option</code>, allowing
* you to make statements like:
*
* <pre class="stHighlighted">
* opt.value should be > <span class="stLiteral">9</span>
* </pre>
*
* @param opt An option to convert to <code>Valuable</code>, which provides the <code>value</code> method.
*/
class Valuable[T](opt: Option[T], pos: source.Position) {
// SKIP-DOTTY-END
//DOTTY-ONLY /**
//DOTTY-ONLY * Wrapper class that adds a <code>value</code> method to <code>Option</code>, allowing
//DOTTY-ONLY * you to make statements like:
//DOTTY-ONLY *
//DOTTY-ONLY * <pre class="stHighlighted">
//<span class="stLineComment">//DOTTY-ONLY * opt.value should be > 9</span>
//<span class="stLineComment"></span>
//DOTTY-ONLY * <!-- -->
//</pre>
//DOTTY-ONLY *
//DOTTY-ONLY * @param opt An option to convert to <code>OptionValuable</code>, which provides the <code>value</code> method.
//DOTTY-ONLY */
//DOTTY-ONLY class OptionValuable[T](opt: Option[T], pos: source.Position) {
/**
* Returns the value contained in the wrapped <code>Option</code>, if defined, else throws <code>TestFailedException</code> with
* a detail message indicating the option was not defined.
*/
def value: T = {
try {
opt.get
}
catch {
case cause: NoSuchElementException =>
throw new TestFailedException((_: StackDepthException) => Some(Resources.optionValueNotDefined), Some(cause), pos)
}
}
}
}
/**
* Companion object that facilitates the importing of <code>OptionValues</code> members as
* an alternative to mixing it in. One use case is to import <code>OptionValues</code>'s members so you can use
* <code>value</code> on option in the Scala interpreter:
*
* <pre class="stREPL">
* $ scala -cp scalatest-1.7.jar
* Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_29).
* Type in expressions to have them evaluated.
* Type :help for more information.
*
* scala> import org.scalatest._
* import org.scalatest._
*
* scala> import matchers.Matchers._
* import matchers.Matchers._
*
* scala> import OptionValues._
* import OptionValues._
*
* scala> val opt1: Option[Int] = Some(1)
* opt1: Option[Int] = Some(1)
*
* scala> val opt2: Option[Int] = None
* opt2: Option[Int] = None
*
* scala> opt1.value should be < 10
*
* scala> opt2.value should be < 10
* org.scalatest.TestFailedException: The Option on which value was invoked was not defined.
* at org.scalatest.OptionValues$Valuable.value(OptionValues.scala:68)
* at .<init>(<console>:18)
* ...
* </pre>
*
*/
object OptionValues extends OptionValues