-
Notifications
You must be signed in to change notification settings - Fork 2
/
JUnit3Suite.scala
357 lines (314 loc) · 14.9 KB
/
JUnit3Suite.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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*
* 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.scalatestplus.junit
import org.scalatest.{Resources => _, _}
import org.scalatest.events._
import exceptions._
import org.scalactic.Requirements._
import org.scalactic.NameUtil
import JUnitHelper.getIndentedTextForTest
import org.scalatest.tools.Utils.wrapReporterIfNecessary
import _root_.junit.framework.AssertionFailedError
import _root_.junit.framework.Test
import _root_.junit.framework.TestCase
import _root_.junit.framework.TestListener
import _root_.junit.framework.TestResult
import _root_.junit.framework.TestSuite
import collection.immutable.TreeSet
import java.lang.reflect.{Method, Modifier}
import org.scalactic.source
import org.scalatest.events.MotionToSuppress
import org.scalatest.events.TestFailed
import org.scalatest.events.TestStarting
import org.scalatest.events.TestSucceeded
import scala.collection.mutable.HashSet
/**
* A <code>Suite</code> that is also a <code>junit.framework.TestCase</code>.
*
* <p>
* A <code>JUnit3Suite</code> may be run by either JUnit 3 (such as JUnit 3.8) or ScalaTest's runner. You write it the way
* you write a JUnit 3 <code>TestCase</code>. Tests are methods that start with <code>test</code>, take no parameters, and
* have a <code>Unit</code> return type. You manage fixtures with methods <code>setUp</code> and <code>tearDown</code>.
* Here's an example:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">import</span> org.scalatest.junit.JUnit3Suite
* <span class="stReserved">import</span> scala.collection.mutable.ListBuffer
* <br/><span class="stReserved">class</span> <span class="stType">BlastFromThePastSuite</span> <span class="stReserved">extends</span> <span class="stType">JUnit3Suite</span> {
* <br/> <span class="stReserved">var</span> sb: <span class="stType">StringBuilder</span> = _
* <span class="stReserved">var</span> lb: <span class="stType">ListBuffer[String]</span> = _
* <br/> <span class="stReserved">override</span> <span class="stReserved">def</span> setUp(): <span class="stType">Unit</span> = {
* sb = <span class="stReserved">new</span> <span class="stType">StringBuilder</span>(<span class="stQuotedString">"ScalaTest is "</span>)
* lb = <span class="stReserved">new</span> <span class="stType">ListBuffer[String]</span>
* }
* <br/> <span class="stReserved">def</span> testEasy(): <span class="stType">Unit</span> = { <span class="stLineComment">// Uses JUnit-style assertions</span>
* sb.append(<span class="stQuotedString">"easy!"</span>)
* assertEquals(<span class="stQuotedString">"ScalaTest is easy!"</span>, sb.toString)
* assertTrue(lb.isEmpty)
* lb += <span class="stQuotedString">"sweet"</span>
* }
* <br/> <span class="stReserved">def</span> testFun(): <span class="stType">Unit</span> = { <span class="stLineComment">// Uses ScalaTest assertions</span>
* sb.append(<span class="stQuotedString">"fun!"</span>)
* assert(sb.toString === <span class="stQuotedString">"ScalaTest is fun!"</span>)
* assert(lb.isEmpty)
* }
* }
* </pre>
*
* <p>
* You can use either JUnit's assertions, inherited from <code>TestCase</code>, or ScalaTest's, inherited from <code>AssertionsForJUnit</code>.
* </p>
*
* <p>
* When writing JUnit 3 tests in Scala, you should keep in mind that JUnit 3 will not run tests that have a return type other than
* <code>Unit</code>. Thus it is best to explicitly state the <code>Unit</code> result type, like this:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">def</span> testGoodIdea(): <span class="stType">Unit</span> = { <span class="stLineComment">// result type will be Unit</span>
* <span class="stLineComment">// ...</span>
* }
* </pre>
*
* <p>
* Instead of this:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">def</span> testBadIdea() = { <span class="stLineComment">// result type will be inferred</span>
* <span class="stLineComment">// ...</span>
* }
* </pre>
*
* <p>
* If the <code>testBadIdea</code> method ends in an expression that has a result type other than <code>Unit</code>, the Scala
* compiler will infer a result type to the <code>testBadIdea</code> method to be the same non-<code>Unit</code> type. As a "result,"
* JUnit 3 will not discover or run the <code>testBadIdea</code> method at all.
* </p>
*
* @author Bill Venners
*/
class JUnit3Suite extends TestCase with Suite with AssertionsForJUnit { thisSuite =>
// This is volatile, because who knows what Thread JUnit will fire through this.
@volatile private var theTracker = new Tracker
/**
* Returns the set of test names that will be executed by JUnit when <code>run</code> is invoked
* on an instance of this class, or the instance is passed directly to JUnit for running.
*
* <p>
* The iterator obtained by invoking <code>elements</code> on this
* returned <code>Set</code> will produce the test names in their <em>natural order</em>, as determined by <code>String</code>'s
* <code>compareTo</code> method. Nevertheless, this method is not consulted by JUnit when it
* runs the tests, and JUnit may run the tests in any order.
* </p>
*/
override def testNames: Set[String] = {
def isTestMethod(m: Method) = {
val isInstanceMethod = !Modifier.isStatic(m.getModifiers())
// name must have at least 4 chars (minimum is "test")
val simpleName = m.getName
val firstFour = if (simpleName.length >= 4) simpleName.substring(0, 4) else ""
val paramTypes = m.getParameterTypes
val hasNoParams = paramTypes.length == 0
val hasVoidReturnType = m.getReturnType == Void.TYPE
// won't discover testNames because it has a non-Unit return type
isInstanceMethod && (firstFour == "test") && hasNoParams && hasVoidReturnType
}
val testNameArray =
for (m <- getClass.getMethods; if isTestMethod(m))
yield m.getName
TreeSet[String]() ++ testNameArray
}
/**
* Returns an empty <code>Map</code>, because tags are not supported by JUnit 3.
*/
override def tags = Map()
/**
* Returns the number of tests expected to be run by JUnit when <code>run</code> is invoked
* on this <code>Suite</code>.
*
* <p>
* If <code>tagsToInclude</code> in the passed <code>Filter</code> is defined, this class's
* implementation of this method returns 0. Else this class's implementation of this method
* returns the size of the set returned by <code>testNames</code> on the current instance.
* </p>
*/
override def expectedTestCount(filter: Filter) =
if (filter.tagsToInclude.isDefined) 0 else testNames.size
/**
* Throws <code>UnsupportedOperationException</code>, because this method is unused by this
* class, given this class's <code>run</code> method delegates to JUnit to run
* its tests.
*
* <p>
* The main purpose of this method implementation is to render a compiler error an attempt
* to mix in a trait that overrides <code>runNestedSuites</code>. Because this
* trait does not actually use <code>runNestedSuites</code>, the attempt to mix
* in behavior would very likely not work.
* </p>
*
* @param args the <code>Args</code> for this run
*
* @throws UnsupportedOperationException always.
*/
override final protected def runNestedSuites(args: Args): Status = {
throw new UnsupportedOperationException
}
/**
* Throws <code>UnsupportedOperationException</code>, because this method is unused by this
* class, given this class's <code>run</code> method delegates to JUnit to run
* its tests.
*
* <p>
* The main purpose of this method implementation is to render a compiler error an attempt
* to mix in a trait that overrides <code>runTests</code>. Because this
* trait does not actually use <code>runTests</code>, the attempt to mix
* in behavior would very likely not work.
* </p>
*
* @param testName an optional name of one test to run. If <code>None</code>, all relevant tests should be run.
* I.e., <code>None</code> acts like a wildcard that means run all relevant tests in this <code>Suite</code>.
* @param args the <code>Args</code> for this run
*
* @throws UnsupportedOperationException always.
*/
override protected final def runTests(testName: Option[String], args: Args): Status = {
throw new UnsupportedOperationException
}
/**
* Throws <code>UnsupportedOperationException</code>, because this method is unused by this
* class, given this class's <code>run</code> method delegates to JUnit to run
* its tests.
*
* <p>
* The main purpose of this method implementation is to render a compiler error an attempt
* to mix in a trait that overrides <code>runTest</code>. Because this
* trait does not actually use <code>runTest</code>, the attempt to mix
* in behavior would very likely not work.
* </p>
*
* @param testName the name of one test to run.
* @param args the <code>Args</code> for this run
*
* @throws UnsupportedOperationException always.
*/
override protected final def runTest(testName: String, args: Args): Status = {
throw new UnsupportedOperationException
}
/**
* Overrides to use JUnit 3 to run the test(s).
*
* @param testName an optional name of one test to run. If <code>None</code>, all relevant tests should be run.
* I.e., <code>None</code> acts like a wildcard that means run all relevant tests in this <code>Suite</code>.
* @param args the <code>Args</code> for this run
* @return a <code>Status</code> object that indicates when all tests and nested suites started by this method have completed, and whether or not a failure occurred.
*
*/
override def run(testName: Option[String], args: Args): Status = {
import args._
theTracker = tracker
val status = new StatefulStatus
if (!filter.tagsToInclude.isDefined) {
val testResult = new TestResult
testResult.addListener(new MyTestListener(wrapReporterIfNecessary(thisSuite, reporter), tracker, status))
testName match {
case None => new TestSuite(this.getClass).run(testResult)
case Some(tn) =>
if (!testNames.contains(tn))
throw new IllegalArgumentException(Resources.testNotFound(testName))
setName(tn)
run(testResult)
}
}
status.setCompleted()
status
}
/**
* Suite style name.
*
* @return <code>JUnit3Suite</code>
*/
final override val styleName: String = "JUnit3Suite"
final override def testDataFor(testName: String, theConfigMap: ConfigMap = ConfigMap.empty): TestData =
new TestData {
val configMap = theConfigMap
val name = testName
val scopes = Vector.empty
val text = testName
val tags = Set.empty[String]
val pos = None
}
}
private[junit] class MyTestListener(report: Reporter, tracker: Tracker, status: StatefulStatus) extends TestListener {
// TODO: worry about threading
private val failedTestsSet = scala.collection.mutable.Set[Test]()
private def getSuiteNameForTestCase(testCase: Test) =
testCase match {
case junit3Suite: JUnit3Suite => junit3Suite.suiteName
case _ => NameUtil.getSimpleNameOfAnObjectsClass(testCase) // Should never happen, but just in case
}
def getMessageGivenThrowable(throwable: Throwable, isAssertionFailedError: Boolean) =
if (throwable.getMessage == null)
"A JUnit3Suite test failed with an " + (if (isAssertionFailedError) "AssertionFailedError" else "exception") // Hopefully will never happen
else
throwable.getMessage
def getTopOfMethod(className: String, methodName: String) = Some(TopOfMethod(className, "public void " + className + "." + methodName + "()"))
// The Test passed to these methods is an instance of the JUnit3Suite class, Calling
// test.getClass.getName on it gets the fully qualified name of the class
// test.asInstanceOf[TestCase].getName gives you the name of the test method, without any parens
// Calling test.toSring gives you testError(org.scalatestexamples.junit.JUnit3ExampleSuite)
// So that's that old JUnit-style test name thing.
def startTest(testCase: Test): Unit = {
requireNonNull(testCase)
val suiteName = getSuiteNameForTestCase(testCase)
report(TestStarting(tracker.nextOrdinal(), suiteName, testCase.getClass.getName, Some(testCase.getClass.getName), testCase.toString, testCase.toString, Some(MotionToSuppress), getTopOfMethod(testCase.getClass.getName, testCase.asInstanceOf[TestCase].getName)))
}
def addError(testCase: Test, throwable: Throwable): Unit = {
requireNonNull(testCase, throwable)
val formatter = getIndentedTextForTest(testCase.toString, 1, true)
val suiteName = getSuiteNameForTestCase(testCase)
val payload =
throwable match {
case optPayload: PayloadField =>
optPayload.payload
case _ =>
None
}
report(TestFailed(tracker.nextOrdinal(), getMessageGivenThrowable(throwable, false), suiteName, testCase.getClass.getName, Some(testCase.getClass.getName), testCase.toString, testCase.toString, Vector.empty, Vector.empty, Some(throwable), None, Some(formatter), Some(SeeStackDepthException), None, payload))
failedTestsSet += testCase
}
def addFailure(testCase: Test, assertionFailedError: AssertionFailedError): Unit = {
requireNonNull(testCase, assertionFailedError)
val formatter = getIndentedTextForTest(testCase.toString, 1, true)
val suiteName = getSuiteNameForTestCase(testCase)
report(TestFailed(tracker.nextOrdinal(), getMessageGivenThrowable(assertionFailedError, true), suiteName, testCase.getClass.getName, Some(testCase.getClass.getName), testCase.toString, testCase.toString, Vector.empty, Vector.empty, Some(assertionFailedError), None, Some(formatter), Some(SeeStackDepthException), None))
failedTestsSet += testCase
}
def endTest(testCase: Test): Unit = {
val testHadFailed = failedTestsSet.contains(testCase)
if (!testHadFailed) {
requireNonNull(testCase)
val formatter = getIndentedTextForTest(testCase.toString, 1, true)
val suiteName = getSuiteNameForTestCase(testCase)
report(TestSucceeded(tracker.nextOrdinal(), suiteName, testCase.getClass.getName, Some(testCase.getClass.getName), testCase.toString, testCase.toString, Vector.empty, None, Some(formatter), getTopOfMethod(testCase.getClass.getName, testCase.asInstanceOf[TestCase].getName)))
}
else {
failedTestsSet -= testCase
status.setFailed()
}
}
}