1/* 2 * Copyright 2001-2008 Artima, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package org.scalatest.tools 17 18import org.scalatest._ 19import scala.collection.mutable.ListBuffer 20import java.lang.reflect.Constructor 21import java.lang.reflect.Modifier 22import java.net.URL 23import java.net.MalformedURLException 24import java.net.URLClassLoader 25import java.io.File 26import java.io.IOException 27import javax.swing.SwingUtilities 28import java.util.concurrent.ArrayBlockingQueue 29import java.util.regex.Pattern 30import org.scalatest.testng.TestNGWrapperSuite 31import java.util.concurrent.Semaphore 32import org.scalatest.events._ 33import org.scalatest.junit.JUnitWrapperSuite 34import java.util.concurrent.Executors 35import java.util.concurrent.ExecutorService 36 37/** 38 * <p> 39 * Application that runs a suite of tests. 40 * The application accepts command line arguments that specify optional <em>config map</em> (key-value pairs), an optional 41 * <em>runpath</em>, zero to many <code>Reporter</code>s, optional lists of tags to include and/or exclude, zero to many 42 * <code>Suite</code> class names, zero to many "members-only" <code>Suite</code> paths, zero to many "wildcard" <code>Suite</code> paths, 43 * and zero to many TestNG XML config file paths. 44 * All of these arguments are described in more detail below. Here's a summary: 45 * </p> 46 * 47 * <pre class="stExamples"> 48 * scala [-classpath scalatest-<version>.jar:...] org.scalatest.tools.Runner 49[-D<key>=<value> [...]] [-p <runpath>] [reporter [...]] 50[-n <includes>] [-l <excludes>] [-c] [-s <suite class name> 51[...]] [-j <junit class name> [...]] [-m <members-only suite path> 52[...]] [-w <wildcard suite path> [...]] [-t <TestNG config file 53path> [...]] 54 * </pre> 55 * 56 * <p> 57 * The simplest way to start <code>Runner</code> is to specify the directory containing your compiled tests as the sole element of the runpath, for example: 58 * </p> 59 * 60 * <pre class="stExamples">scala -classpath scalatest-<version>.jar org.scalatest.tools.Runner -p compiled_tests</pre> 61 * 62 * <p> 63 * Given the previous command, <code>Runner</code> will discover and execute all <code>Suite</code>s in the <code>compiled_tests</code> directory and its subdirectories, 64 * and show results in graphical user interface (GUI). 65 * </p> 66 * 67 * <a name="configMapSection"></a> 68 * <h2>Specifying the config map</h2> 69 * 70 * <p> 71 * A <em>config map</em> contains pairs consisting of a string key and a value that may be of any type. (Keys that start with 72 * "org.scalatest." are reserved for ScalaTest. Configuration values that are themselves strings may be specified on the 73 * <code>Runner</code> command line. 74 * Each configuration pair is denoted with a "-D", followed immediately by the key string, an "=", and the value string. 75 * For example: 76 * </p> 77 * 78 * <pre class="stExamples">-Ddbname=testdb -Dserver=192.168.1.188</pre> 79 * 80 * <h2>Specifying a runpath</h2> 81 * 82 * <p> 83 * A runpath is the list of filenames, directory paths, and/or URLs that <code>Runner</code> 84 * uses to load classes for the running test. If runpath is specified, <code>Runner</code> creates 85 * a custom class loader to load classes available on the runpath. 86 * The graphical user interface reloads the test classes anew for each run 87 * by creating and using a new instance of the custom class loader for each run. 88 * The classes that comprise the test may also be made available on 89 * the classpath, in which case no runpath need be specified. 90 * </p> 91 * 92 * <p> 93 * The runpath is specified with the <b>-p</b> option. The <b>-p</b> must be followed by a space, 94 * a double quote (<code>"</code>), a white-space-separated list of 95 * paths and URLs, and a double quote. If specifying only one element in the runpath, you can leave off 96 * the double quotes, which only serve to combine a white-space separated list of strings into one 97 * command line argument. If you have path elements that themselves have a space in them, you must 98 * place a backslash (\) in front of the space. Here's an example: 99 * </p> 100 * 101 * <pre class="stExamples">-p "serviceuitest-1.1beta4.jar myjini http://myhost:9998/myfile.jar target/class\ files"</pre> 102 * 103 * <h2>Specifying reporters</h2> 104 * 105 * <p> 106 * Reporters can be specified on the command line in any of the following 107 * ways: 108 * </p> 109 * 110 * <ul> 111 * <li> <code><b>-g[configs...]</b></code> - causes display of a graphical user interface that allows 112 * tests to be run and results to be investigated</li> 113 * <li> <code><b>-f[configs...] <filename></b></code> - causes test results to be written to 114 * the named file</li> 115 * <li> <code><b>-u <directory></b></code> - causes test results to be written to 116 * xml files in the named directory</li> 117 * <li> <code><b>-o[configs...]</b></code> - causes test results to be written to 118 * the standard output</li> 119 * <li> <code><b>-e[configs...]</b></code> - causes test results to be written to 120 * the standard error</li> 121 * <li> <code><b>-r[configs...] <reporterclass></b></code> - causes test results to be reported to 122 * an instance of the specified fully qualified <code>Reporter</code> class name</li> 123 * </ul> 124 * 125 * <p> 126 * The <code><b>[configs...]</b></code> parameter, which is used to configure reporters, is described in the next section. 127 * </p> 128 * 129 * <p> 130 * The <code><b>-r</b></code> option causes the reporter specified in 131 * <code><b><reporterclass></b></code> to be 132 * instantiated. 133 * Each reporter class specified with a <b>-r</b> option must be public, implement 134 * <code>org.scalatest.Reporter</code>, and have a public no-arg constructor. 135 * Reporter classes must be specified with fully qualified names. 136 * The specified reporter classes may be 137 * deployed on the classpath. If a runpath is specified with the 138 * <code>-p</code> option, the specified reporter classes may also be loaded from the runpath. 139 * All specified reporter classes will be loaded and instantiated via their no-arg constructor. 140 * </p> 141 * 142 * <p> 143 * For example, to run a suite named <code>MySuite</code> from the <code>mydir</code> directory 144 * using two reporters, the graphical reporter and a file reporter 145 * writing to a file named <code>"test.out"</code>, you would type: 146 * </p> 147 * 148 * <pre class="stExamples">java -jar scalatest.jar -p mydir <b>-g -f test.out</b> -s MySuite</pre> 149 * 150 * <p> 151 * The <code><b>-g</b></code>, <code><b>-o</b></code>, or <code><b>-e</b></code> options can 152 * appear at most once each in any single command line. 153 * Multiple appearances of <code><b>-f</b></code> and <code><b>-r</b></code> result in multiple reporters 154 * unless the specified <code><b><filename></b></code> or <code><b><reporterclass></b></code> is 155 * repeated. If any of <code><b>-g</b></code>, <code><b>-o</b></code>, <code><b>-e</b></code>, 156 * <code><b><filename></b></code> or <code><b><reporterclass></b></code> are repeated on 157 * the command line, the <code>Runner</code> will print an error message and not run the tests. 158 * </p> 159 * 160 * <p> 161 * <code>Runner</code> adds the reporters specified on the command line to a <em>dispatch reporter</em>, 162 * which will dispatch each method invocation to each contained reporter. <code>Runner</code> will pass 163 * the dispatch reporter to executed suites. As a result, every 164 * specified reporter will receive every report generated by the running suite of tests. 165 * If no reporters are specified, a graphical 166 * runner will be displayed that provides a graphical report of 167 * executed suites. 168 * </p> 169 * 170 * <h2>Configuring Reporters</h2> 171 * 172 * <p> 173 * Each reporter option on the command line can include configuration characters. Configuration characters 174 * are specified immediately following the <code><b>-g</b></code>, <code><b>-o</b></code>, 175 * <code><b>-e</b></code>, <code><b>-f</b></code>, or <code><b>-r</b></code>. The following configuration 176 * characters, which cause reports to be dropped, are valid for any reporter: 177 * </p> 178 * 179 * <ul> 180 * <li> <code><b>N</b></code> - drop <code>TestStarting</code> events</li> 181 * <li> <code><b>C</b></code> - drop <code>TestSucceeded</code> events</li> 182 * <li> <code><b>X</b></code> - drop <code>TestIgnored</code> events</li> 183 * <li> <code><b>E</b></code> - drop <code>TestPending</code> events</li> 184 * <li> <code><b>H</b></code> - drop <code>SuiteStarting</code> events</li> 185 * <li> <code><b>L</b></code> - drop <code>SuiteCompleted</code> events</li> 186 * <li> <code><b>O</b></code> - drop <code>InfoProvided</code> events</li> 187 * </ul> 188 * 189 * <p> 190 * A dropped event will not be delivered to the reporter at all. So the reporter will not know about it and therefore not 191 * present information about the event in its report. For example, if you specify <code>-oN</code>, the standard output reporter 192 * will never receive any <code>TestStarting</code> events and will therefore never report them. The purpose of these 193 * configuration parameters is to allow users to selectively remove events they find add clutter to the report without 194 * providing essential information. 195 * </p> 196 * 197 * <p> 198 * The following three reporter configuration parameters may additionally be used on standard output (-o), standard error (-e), 199 * and file (-f) reporters: 200 * </p> 201 * 202 * <ul> 203 * <li> <code><b>W</b></code> - without color</li> 204 * <li> <code><b>D</b></code> - show all durations</li> 205 * <li> <code><b>S</b></code> - show short stack traces</li> 206 * <li> <code><b>F</b></code> - show full stack traces</li> 207 * </ul> 208 * 209 * <p> 210 * If you specify a W, D, S, or F for any reporter other than standard output, standard error, or file reporters, <code>Runner</code> 211 * will complain with an error message and not perform the run. 212 * </p> 213 * 214 * <p> 215 * Configuring a standard output, error, or file reporter with <code>D</code> will cause that reporter to 216 * print a duration for each test and suite. When running in the default mode, a duration will only be printed for 217 * the entire run. 218 * </p> 219 * 220 * <p> 221 * Configuring a standard output, error, or file reporter with <code>F</code> will cause that reporter to print full stack traces for all exceptions, 222 * including <code>TestFailedExceptions</code>. Every <code>TestFailedException</code> contains a stack depth of the 223 * line of test code that failed so that users won't need to search through a stack trace to find it. When running in the default, 224 * mode, these reporters will only show full stack traces when other exceptions are thrown, such as an exception thrown 225 * by production code. When a <code>TestFailedException</code> is thrown in default mode, only the source filename and 226 * line number of the line of test code that caused the test to fail are printed along with the error message, not the full stack 227 * trace. 228 * </p> 229 * 230 * <p> 231 * By default, a standard output, error, or file reporter inserts ansi escape codes into the output printed to change and later reset 232 * terminal colors. Information printed as a result of run starting, completed, and stopped events 233 * is printed in cyan. Information printed as a result of ignored or pending test events is shown in yellow. Information printed 234 * as a result of test failed, suite aborted, or run aborted events is printed in red. All other information is printed in green. 235 * The purpose of these colors is to facilitate speedy reading of the output, especially the finding of failed tests, which can 236 * get lost in a sea of passing tests. Configuring a standard output, error, or file reporter into without-color mode ('W') will 237 * turn off this behavior. No ansi codes will be inserted. 238 * </p> 239 * 240 * <p> 241 * For example, to run a suite using two reporters, the graphical reporter configured to present every reported event 242 * and a standard error reporter configured to present everything but test starting, test succeeded, test ignored, test 243 * pending, suite starting, suite completed, and info provided events, you would type: 244 * </p> 245 * 246 * <p> 247 * <code>scala -classpath scalatest-<version>.jar -p mydir <strong>-g -eNDXEHLO</strong> -s MySuite</code> 248 * </p> 249 * 250 * <p> 251 * Note that no white space is allowed between the reporter option and the initial configuration 252 * parameters. So <code>"-e NDXEHLO"</code> will not work, 253 * <code>"-eNDXEHLO"</code> will work. 254 * </p> 255 * 256 * <h2>Specifying tags to include and exclude</h2> 257 * 258 * <p> 259 * You can specify tag names of tests to include or exclude from a run. To specify tags to include, 260 * use <code>-n</code> followed by a white-space-separated list of tag names to include, surrounded by 261 * double quotes. (The double quotes are not needed if specifying just one tag.) Similarly, to specify tags 262 * to exclude, use <code>-l</code> followed by a white-space-separated 263 * list of tag names to exclude, surrounded by double quotes. (As before, the double quotes are not needed 264 * if specifying just one tag.) If tags to include is not specified, then all tests 265 * except those mentioned in the tags to exclude (and in the <code>org.scalatest.Ignore</code> tag), will be executed. 266 * (In other words, the absence of a <code>-n</code> option is like a wildcard, indicating all tests be included.) 267 * If tags to include is specified, then only those tests whose tags are mentioned in the argument following <code>-n</code> 268 * and not mentioned in the tags to exclude, will be executed. For more information on test tags, see 269 * the <a href="../Suite.html">documentation for <code>Suite</code></a>. Here are some examples: 270 * </p> 271 * 272 * <p> 273 * <ul> 274 * <li><code>-n CheckinTests</code></li> 275 * <li><code>-n FunctionalTests -l SlowTests</code></li> 276 * <li><code>-n "CheckinTests FunctionalTests" -l "SlowTests NetworkTests"</code></li> 277 * </ul> 278 * </p> 279 * 280 * <h2>Executing <code>Suite</code>s in parallel</h2> 281 * 282 * <p> 283 * With the proliferation of multi-core architectures, and the often parallelizable nature of tests, it is useful to be able to run 284 * tests in parallel. If you include <code>-c</code> on the command line, <code>Runner</code> will pass a <code>Distributor</code> to 285 * the <code>Suite</code>s you specify with <code>-s</code>. <code>Runner</code> will set up a thread pool to execute any <code>Suite</code>s 286 * passed to the <code>Distributor</code>'s <code>put</code> method in parallel. Trait <code>Suite</code>'s implementation of 287 * <code>runNestedSuites</code> will place any nested <code>Suite</code>s into this <code>Distributor</code>. Thus, if you have a <code>Suite</code> 288 * of tests that must be executed sequentially, you should override <code>runNestedSuites</code> as described in the <a href="../Distributor.html">documentation for <code>Distributor</code></a>. 289 * </p> 290 * 291 * <p> 292 * The <code>-c</code> option may optionally be appended with a number (e.g. 293 * "<code>-c10</code>" -- no intervening space) to specify the number of 294 * threads to be created in the thread pool. If no number (or 0) is 295 * specified, the number of threads will be decided based on the number of 296 * processors available. 297 * </p> 298 * 299 * <h2>Specifying <code>Suite</code>s</h2> 300 * 301 * <p> 302 * Suites are specified on the command line with a <b>-s</b> followed by the fully qualified 303 * name of a <code>Suite</code> subclass, as in: 304 * </p> 305 * 306 * <pre class="stExamples">-s com.artima.serviceuitest.ServiceUITestkit</pre> 307 * 308 * <p> 309 * Each specified suite class must be public, a subclass of 310 * <code>org.scalatest.Suite</code>, and contain a public no-arg constructor. 311 * <code>Suite</code> classes must be specified with fully qualified names. 312 * The specified <code>Suite</code> classes may be 313 * loaded from the classpath. If a runpath is specified with the 314 * <code>-p</code> option, specified <code>Suite</code> classes may also be loaded from the runpath. 315 * All specified <code>Suite</code> classes will be loaded and instantiated via their no-arg constructor. 316 * </p> 317 * 318 * <p> 319 * The runner will invoke <code>execute</code> on each instantiated <code>org.scalatest.Suite</code>, 320 * passing in the dispatch reporter to each <code>execute</code> method. 321 * </p> 322 * 323 * <p> 324 * <code>Runner</code> is intended to be used from the command line. It is included in <code>org.scalatest</code> 325 * package as a convenience for the user. If this package is incorporated into tools, such as IDEs, which take 326 * over the role of runner, object <code>org.scalatest.tools.Runner</code> may be excluded from that implementation of the package. 327 * All other public types declared in package <code>org.scalatest.tools.Runner</code> should be included in any such usage, however, 328 * so client software can count on them being available. 329 * </p> 330 * 331 * <a name="membersOnlyWildcard"> 332 * <h2>Specifying "members-only" and "wildcard" <code>Suite</code> paths</h2> 333 * </a> 334 * 335 * <p> 336 * If you specify <code>Suite</code> path names with <code>-m</code> or <code>-w</code>, <code>Runner</code> will automatically 337 * discover and execute accessible <code>Suite</code>s in the runpath that are either a member of (in the case of <code>-m</code>) 338 * or enclosed by (in the case of <code>-w</code>) the specified path. As used in this context, a <em>path</em> is a portion of a fully qualified name. 339 * For example, the fully qualifed name <code>com.example.webapp.MySuite</code> contains paths <code>com</code>, <code>com.example</code>, and <code>com.example.webapp</code>. 340 * The fully qualifed name <code>com.example.webapp.MyObject.NestedSuite</code> contains paths <code>com</code>, <code>com.example</code>, 341 * <code>com.example.webapp</code>, and <code>com.example.webapp.MyObject</code>. 342 * An <em>accessible <code>Suite</code></em> is a public class that extends <code>org.scalatest.Suite</code> 343 * and defines a public no-arg constructor. Note that <code>Suite</code>s defined inside classes and traits do not have no-arg constructors, 344 * and therefore won't be discovered. <code>Suite</code>s defined inside singleton objects, however, do get a no-arg constructor by default, thus 345 * they can be discovered. 346 * </p> 347 * 348 * <p> 349 * For example, if you specify <code>-m com.example.webapp</code> 350 * on the command line, and you've placed <code>com.example.webapp.RedSuite</code> and <code>com.example.webapp.BlueSuite</code> 351 * on the runpath, then <code>Runner</code> will instantiate and execute both of those <code>Suite</code>s. The difference 352 * between <code>-m</code> and <code>-w</code> is that for <code>-m</code>, only <code>Suite</code>s that are direct members of the named path 353 * will be discovered. For <code>-w</code>, any <code>Suite</code>s whose fully qualified 354 * name begins with the specified path will be discovered. Thus, if <code>com.example.webapp.controllers.GreenSuite</code> 355 * exists on the runpath, invoking <code>Runner</code> with <code>-w com.example.webapp</code> will cause <code>GreenSuite</code> 356 * to be discovered, because its fully qualifed name begins with <code>"com.example.webapp"</code>. But if you invoke <code>Runner</code> 357 * with <code>-m com.example.webapp</code>, <code>GreenSuite</code> will <em>not</em> be discovered because it is directly 358 * a member of <code>com.example.webapp.controllers</code>, not <code>com.example.webapp</code>. 359 * </p> 360 * 361 * <p> 362 * If you specify no <code>-s</code>, <code>-m</code>, or <code>-w</code> arguments on the command line to <code>Runner</code>, it will discover and execute all accessible <code>Suite</code>s 363 * in the runpath. 364 * </p> 365 * 366 * <h2>Specifying TestNG XML config file paths</h2> 367 * 368 * <p> 369 * If you specify one or more file paths with <code>-t</code>, <code>Runner</code> will create a <code>org.scalatest.testng.TestNGWrapperSuite</code>, 370 * passing in a <code>List</code> of the specified paths. When executed, the <code>TestNGWrapperSuite</code> will create one <code>TestNG</code> instance 371 * and pass each specified file path to it for running. If you include <code>-t</code> arguments, you must include TestNG's jar file on the class path or runpath. 372 * The <code>-t</code> argument will enable you to run existing <code>TestNG</code> tests, including tests written in Java, as part of a ScalaTest run. 373 * You need not use <code>-t</code> to run suites written in Scala that extend <code>TestNGSuite</code>. You can simply run such suites with 374 * <code>-s</code>, <code>-m</code>, or </code>-w</code> parameters. 375 * </p> 376 * 377 * <h2>Specifying JUnit tests</h2> 378 * 379 * <p> 380 * JUnit tests, including ones written in Java, may be run by specifying 381 * <code>-j classname</code>, where the classname is a valid JUnit class 382 * such as a TestCase, TestSuite, or a class implementing a static suite() 383 * method returning a TestSuite. </p> 384 * <p> 385 * To use this option you must include a JUnit jar file on your classpath. 386 * </p> 387 * 388 * @author Bill Venners 389 * @author George Berger 390 * @author Josh Cough 391 */ 392object Runner { 393 394 private val RUNNER_JFRAME_START_X: Int = 150 395 private val RUNNER_JFRAME_START_Y: Int = 100 396 397 // TO 398 // We always include a PassFailReporter on runs in order to determine 399 // whether or not all tests passed. 400 // 401 // The thread that calls Runner.run() will either start a GUI, if a graphic 402 // reporter was requested, or just run the tests itself. If a GUI is started, 403 // an event handler thread will get going, and it will start a RunnerThread, 404 // which will actually do the running. The GUI can repeatedly start RunnerThreads 405 // and RerunnerThreads, until the GUI is closed. If -c is specified, that means 406 // the tests should be run concurrently, which in turn means a Distributor will 407 // be passed to the execute method of the Suites, which will in turn populate 408 // it with its nested suites instead of executing them directly in the same 409 // thread. The Distributor works in conjunction with a pool of threads that 410 // will take suites out of the distributor queue and execute them. The DispatchReporter 411 // will serialize all reports via an actor, which because that actor uses receive 412 // not react, will have its own thread. So the DispatchReporter actor's thread will 413 // be the one that actually invokes TestFailed, RunAborted, etc., on this PassFailReporter. 414 // The thread that invoked Runner.run(), will be the one that calls allTestsPassed. 415 // 416 // The thread that invoked Runner.run() will be the one to instantiate the PassFailReporter 417 // and in its primary constructor acquire the single semaphore permit. This permit will 418 // only be released by the DispatchReporter's actor thread when a runAborted, runStopped, 419 // or runCompleted is invoked. allTestsPassed will block until it can reacquire the lone 420 // semaphore permit. Thus, a PassFailReporter can just be used for one run, then it is 421 // spent. A new PassFailReporter is therefore created each time the Runner.run() method is invoked. 422 // 423 private class PassFailReporter extends Reporter { 424 425 @volatile private var failedAbortedOrStopped = false 426 private val runDoneSemaphore = new Semaphore(1) 427 runDoneSemaphore.acquire() 428 429 override def apply(event: Event) { 430 event match { 431 case _: TestFailed => 432 failedAbortedOrStopped = true 433 434 case _: RunAborted => 435 failedAbortedOrStopped = true 436 runDoneSemaphore.release() 437 438 case _: SuiteAborted => 439 failedAbortedOrStopped = true 440 441 case _: RunStopped => 442 failedAbortedOrStopped = true 443 runDoneSemaphore.release() 444 445 case _: RunCompleted => 446 runDoneSemaphore.release() 447 448 case _ => 449 } 450 } 451 452 def allTestsPassed = { 453 runDoneSemaphore.acquire() 454 !failedAbortedOrStopped 455 } 456 } 457 458 // TODO: I don't think I'm enforcing that properties can't start with "org.scalatest" 459 // TODO: I don't think I'm handling rejecting multiple -f/-r with the same arg. -f fred.txt -f fred.txt should 460 // fail, as should -r MyReporter -r MyReporter. I'm failing on -o -o, -g -g, and -e -e, but the error messages 461 // could indeed be nicer. 462 /** 463 * Runs a suite of tests, with optional GUI. See the main documentation for this singleton object for the details. 464 */ 465 def main(args: Array[String]) { 466 val result = runOptionallyWithPassFailReporter(args, true) 467 468 if (result) 469 exit(0) 470 else 471 exit(1) 472 } 473 474 /** 475 * Runs a suite of tests, with optional GUI. See the main documentation for this singleton object for the details. 476 * The difference between this method and <code>main</code> is simply that this method will block until the run 477 * has completed, aborted, or been stopped, and return <code>true</code> if all tests executed and passed. In other 478 * words, if any test fails, or if any suite aborts, or if the run aborts or is stopped, this method will 479 * return <code>false</code>. This value is used, for example, by the ScalaTest ant task to determine whether 480 * to continue the build if <code>haltOnFailure</code> is set to <code>true</code>. 481 * 482 * @return true if all tests were executed and passed. 483 */ 484 def run(args: Array[String]): Boolean = { 485 runOptionallyWithPassFailReporter(args, true) 486 } 487 488 private def runOptionallyWithPassFailReporter(args: Array[String], runWithPassFailReporter: Boolean): Boolean = { 489 490 checkArgsForValidity(args) match { 491 case Some(s) => { 492 println(s) 493 exit(1) 494 } 495 case None => 496 } 497 498 val ( 499 runpathArgsList, 500 reporterArgsList, 501 suiteArgsList, 502 junitArgsList, 503 propertiesArgsList, 504 includesArgsList, 505 excludesArgsList, 506 concurrentList, 507 membersOnlyArgsList, 508 wildcardArgsList, 509 testNGArgsList 510 ) = parseArgs(args) 511 512 val fullReporterConfigurations: ReporterConfigurations = 513 if (reporterArgsList.isEmpty) 514 // If no reporters specified, just give them a graphic reporter 515 new ReporterConfigurations(Some(GraphicReporterConfiguration(Set())), Nil, Nil, None, None, Nil, Nil) 516 else 517 parseReporterArgsIntoConfigurations(reporterArgsList) 518 519 val suitesList: List[String] = parseSuiteArgsIntoNameStrings(suiteArgsList, "-s") 520 val junitsList: List[String] = parseSuiteArgsIntoNameStrings(junitArgsList, "-j") 521 val runpathList: List[String] = parseRunpathArgIntoList(runpathArgsList) 522 val propertiesMap: Map[String, String] = parsePropertiesArgsIntoMap(propertiesArgsList) 523 val tagsToInclude: Set[String] = parseCompoundArgIntoSet(includesArgsList, "-n") 524 val tagsToExclude: Set[String] = parseCompoundArgIntoSet(excludesArgsList, "-l") 525 val concurrent: Boolean = !concurrentList.isEmpty 526 val numThreads: Int = parseConcurrentNumArg(concurrentList) 527 val membersOnlyList: List[String] = parseSuiteArgsIntoNameStrings(membersOnlyArgsList, "-m") 528 val wildcardList: List[String] = parseSuiteArgsIntoNameStrings(wildcardArgsList, "-w") 529 val testNGList: List[String] = parseSuiteArgsIntoNameStrings(testNGArgsList, "-t") 530 531 val filter = Filter(if (tagsToInclude.isEmpty) None else Some(tagsToInclude), tagsToExclude) 532 533 // If there's a graphic reporter, we need to leave it out of 534 // reporterSpecs, because we want to pass all reporterSpecs except 535 // the graphic reporter's to the RunnerJFrame (because RunnerJFrame *is* 536 // the graphic reporter). 537 val reporterConfigs: ReporterConfigurations = 538 fullReporterConfigurations.graphicReporterConfiguration match { 539 case None => fullReporterConfigurations 540 case Some(grs) => { 541 new ReporterConfigurations( 542 None, 543 fullReporterConfigurations.fileReporterConfigurationList, 544 fullReporterConfigurations.xmlReporterConfigurationList, 545 fullReporterConfigurations.standardOutReporterConfiguration, 546 fullReporterConfigurations.standardErrReporterConfiguration, 547 fullReporterConfigurations.htmlReporterConfigurationList, 548 fullReporterConfigurations.customReporterConfigurationList 549 ) 550 } 551 } 552 553 val passFailReporter = if (runWithPassFailReporter) Some(new PassFailReporter) else None 554 555 fullReporterConfigurations.graphicReporterConfiguration match { 556 case Some(GraphicReporterConfiguration(configSet)) => { 557 val graphicEventsToPresent: Set[EventToPresent] = EventToPresent.allEventsToPresent filter 558 (if (configSet.contains(FilterTestStarting)) {_ != PresentTestStarting} else etp => true) filter 559 (if (configSet.contains(FilterTestSucceeded)) {_ != PresentTestSucceeded} else etp => true) filter 560 (if (configSet.contains(FilterTestIgnored)) {_ != PresentTestIgnored} else etp => true) filter 561 (if (configSet.contains(FilterTestPending)) {_ != PresentTestPending} else etp => true) filter 562 (if (configSet.contains(FilterSuiteStarting)) {_ != PresentSuiteStarting} else etp => true) filter 563 (if (configSet.contains(FilterSuiteCompleted)) {_ != PresentSuiteCompleted} else etp => true) filter 564 (if (configSet.contains(FilterInfoProvided)) {_ != PresentInfoProvided} else etp => true) 565 566 val abq = new ArrayBlockingQueue[RunnerJFrame](1) 567 usingEventDispatchThread { 568 val rjf = new RunnerJFrame(graphicEventsToPresent, reporterConfigs, suitesList, junitsList, runpathList, 569 filter, propertiesMap, concurrent, membersOnlyList, wildcardList, testNGList, passFailReporter, numThreads) 570 rjf.setLocation(RUNNER_JFRAME_START_X, RUNNER_JFRAME_START_Y) 571 rjf.setVisible(true) 572 rjf.prepUIForRunning() 573 rjf.runFromGUI() 574 abq.put(rjf) 575 } 576 // To get the Ant task to work, the main thread needs to block until 577 // The GUI window exits. 578 val rjf = abq.take() 579 rjf.blockUntilWindowClosed() 580 } 581 case None => { // Run the test without a GUI 582 withClassLoaderAndDispatchReporter(runpathList, reporterConfigs, None, passFailReporter) { 583 (loader, dispatchReporter) => { 584 doRunRunRunDaDoRunRun(dispatchReporter, suitesList, junitsList, new Stopper {}, filter, 585 propertiesMap, concurrent, membersOnlyList, wildcardList, testNGList, runpathList, loader, new RunDoneListener {}, 1, numThreads) 586 } 587 } 588 } 589 } 590 591 passFailReporter match { 592 case Some(pfr) => pfr.allTestsPassed 593 case None => false 594 } 595 } 596 597 // Returns an Option[String]. Some is an error message. None means no error. 598 private[scalatest] def checkArgsForValidity(args: Array[String]) = { 599 600 val lb = new ListBuffer[String] 601 val it = args.iterator 602 while (it.hasNext) { 603 val s = it.next 604 // Style advice 605 // If it is multiple else ifs, then make it symetrical. If one needs an open curly brace, put it on all 606 // If an if just has another if, a compound statement, go ahead and put the open curly brace's around the outer one 607 if (s.startsWith("-p") || s.startsWith("-f") || s.startsWith("-u") || s.startsWith("-h") || s.startsWith("-r") || s.startsWith("-n") || s.startsWith("-x") || s.startsWith("-l") || s.startsWith("-s") || s.startsWith("-j") || s.startsWith("-m") || s.startsWith("-w") || s.startsWith("-t")) { 608 if (it.hasNext) 609 it.next 610 } 611 else if (!s.startsWith("-D") && !s.startsWith("-g") && !s.startsWith("-o") && !s.startsWith("-e") && !s.startsWith("-c")) { 612 lb += s 613 } 614 } 615 val argsList = lb.toList 616 if (argsList.length != 0) 617 Some("Unrecognized argument" + (if (argsList.isEmpty) ": " else "s: ") + argsList.mkString("", ", ", ".")) 618 else 619 None 620 } 621 622 // 623 // Examines concurrent option arg to see if it contains an optional numeric 624 // value representing the number of threads to use, e.g. -c10 for 10 threads. 625 // 626 // It's possible for user to specify the -c option multiple times on the 627 // command line, although it isn't particularly useful. This method scans 628 // through multiples until it finds one with a number appended and uses 629 // that. If none have a number it just returns 0. 630 // 631 private[scalatest] def parseConcurrentNumArg(concurrentList: List[String]): 632 Int = { 633 val opt = concurrentList.find(_.matches("-c\\d+")) 634 635 opt match { 636 case Some(arg) => arg.replace("-c", "").toInt 637 case None => 0 638 } 639 } 640 641 private[scalatest] def parseArgs(args: Array[String]) = { 642 643 val runpath = new ListBuffer[String]() 644 val reporters = new ListBuffer[String]() 645 val suites = new ListBuffer[String]() 646 val junits = new ListBuffer[String]() 647 val props = new ListBuffer[String]() 648 val includes = new ListBuffer[String]() 649 val excludes = new ListBuffer[String]() 650 val concurrent = new ListBuffer[String]() 651 val membersOnly = new ListBuffer[String]() 652 val wildcard = new ListBuffer[String]() 653 val testNGXMLFiles = new ListBuffer[String]() 654 655 val it = args.iterator 656 while (it.hasNext) { 657 658 val s = it.next 659 660 if (s.startsWith("-D")) { 661 props += s 662 } 663 else if (s.startsWith("-p")) { 664 runpath += s 665 if (it.hasNext) 666 runpath += it.next 667 } 668 else if (s.startsWith("-g")) { 669 reporters += s 670 } 671 else if (s.startsWith("-o")) { 672 reporters += s 673 } 674 else if (s.startsWith("-e")) { 675 reporters += s 676 } 677 else if (s.startsWith("-f")) { 678 reporters += s 679 if (it.hasNext) 680 reporters += it.next 681 } 682 else if (s.startsWith("-u")) { 683 reporters += s 684 if (it.hasNext) 685 reporters += it.next 686 } 687 else if (s.startsWith("-h")) { 688 reporters += s 689 if (it.hasNext) 690 reporters += it.next 691 } 692 else if (s.startsWith("-n")) { 693 includes += s 694 if (it.hasNext) 695 includes += it.next 696 } 697 else if (s.startsWith("-x")) { 698 System.err.println(Resources("dashXDeprecated")) 699 excludes += s.replace("-x", "-l") 700 if (it.hasNext) 701 excludes += it.next 702 } 703 else if (s.startsWith("-l")) { 704 excludes += s 705 if (it.hasNext) 706 excludes += it.next 707 } 708 else if (s.startsWith("-r")) { 709 710 reporters += s 711 if (it.hasNext) 712 reporters += it.next 713 } 714 else if (s.startsWith("-s")) { 715 716 suites += s 717 if (it.hasNext) 718 suites += it.next 719 } 720 else if (s.startsWith("-j")) { 721 722 junits += s 723 if (it.hasNext) 724 junits += it.next 725 } 726 else if (s.startsWith("-m")) { 727 728 membersOnly += s 729 if (it.hasNext) 730 membersOnly += it.next 731 } 732 else if (s.startsWith("-w")) { 733 734 wildcard += s 735 if (it.hasNext) 736 wildcard += it.next 737 } 738 else if (s.startsWith("-c")) { 739 740 concurrent += s 741 } 742 else if (s.startsWith("-t")) { 743 744 testNGXMLFiles += s 745 if (it.hasNext) 746 testNGXMLFiles += it.next 747 } 748 else { 749 throw new IllegalArgumentException("Unrecognized argument: " + s) 750 } 751 } 752 753 ( 754 runpath.toList, 755 reporters.toList, 756 suites.toList, 757 junits.toList, 758 props.toList, 759 includes.toList, 760 excludes.toList, 761 concurrent.toList, 762 membersOnly.toList, 763 wildcard.toList, 764 testNGXMLFiles.toList 765 ) 766 } 767 768 /** 769 * Returns a possibly empty ConfigSet containing configuration 770 * objects specified in the passed reporterArg. Configuration 771 * options are specified immediately following 772 * the reporter option, as in: 773 * 774 * -oFA 775 * 776 * If no configuration options are specified, this method returns an 777 * empty ConfigSet. This method never returns null. 778 */ 779 private def parseConfigSet(reporterArg: String): Set[ReporterConfigParam] = { 780 781 if (reporterArg == null) 782 throw new NullPointerException("reporterArg was null") 783 784 if (reporterArg.length < 2) 785 throw new IllegalArgumentException("reporterArg < 2") 786 787 // The reporterArg passed includes the initial -, as in "-oFI", 788 // so the first config param will be at index 2 789 val configString = reporterArg.substring(2) 790 val it = configString.iterator 791 var set = Set[ReporterConfigParam]() 792 while (it.hasNext) 793 it.next match { 794 case 'Y' => throw new IllegalArgumentException("Use of Y was deprecated in ScalaTest 1.0 and removed in 1.5. Please check the Scaladoc documentation of org.scalatest.Runner for information on valid Reporter config parameters.") 795 case 'Z' => throw new IllegalArgumentException("Use of Z was deprecated in ScalaTest 1.0 and removed in 1.5. Please check the Scaladoc documentation of org.scalatest.Runner for information on valid Reporter config parameters.") 796 case 'T' => // Use for Dots 797 case 'U' => throw new IllegalArgumentException("Use of U was deprecated in ScalaTest 1.0 and removed in 1.5. Please check the Scaladoc documentation of org.scalatest.Runner for information on valid Reporter config parameters.") 798 case 'P' =>throw new IllegalArgumentException("Use of P was deprecated in ScalaTest 1.0 and removed in 1.5. Please check the Scaladoc documentation of org.scalatest.Runner for information on valid Reporter config parameters.") 799 case 'B' =>throw new IllegalArgumentException("Use of B was deprecated in ScalaTest 1.0 and removed in 1.5. Please check the Scaladoc documentation of org.scalatest.Runner for information on valid Reporter config parameters.") 800 case 'I' =>throw new IllegalArgumentException("Use of I was deprecated in ScalaTest 1.0 and removed in 1.5. Please check the Scaladoc documentation of org.scalatest.Runner for information on valid Reporter config parameters.") 801 // case 'S' => // Use for Short Stack Traces 802 case 'A' =>throw new IllegalArgumentException("Use of A was deprecated in ScalaTest 1.0 and removed in 1.5. Please check the Scaladoc documentation of org.scalatest.Runner for information on valid Reporter config parameters.") 803 case 'R' =>throw new IllegalArgumentException("Use of R was deprecated in ScalaTest 1.0 and removed in 1.5. Please check the Scaladoc documentation of org.scalatest.Runner for information on valid Reporter config parameters.") 804 case 'G' =>throw new IllegalArgumentException("Use of G was deprecated in ScalaTest 1.0 and removed in 1.5. Please check the Scaladoc documentation of org.scalatest.Runner for information on valid Reporter config parameters.") 805 case 'N' => set += FilterTestStarting 806 case 'C' => set += FilterTestSucceeded 807 case 'X' => set += FilterTestIgnored 808 case 'E' => set += FilterTestPending 809 case 'H' => set += FilterSuiteStarting 810 case 'L' => set += FilterSuiteCompleted 811 case 'O' => set += FilterInfoProvided 812 case 'W' => set += PresentWithoutColor 813 case 'F' => set += PresentFullStackTraces 814 case 'S' => set += PresentShortStackTraces 815 case 'D' => set += PresentAllDurations 816 case c: Char => { 817 818 // this should be moved to the checker, and just throw an exception here with a debug message. Or allow a MatchError. 819 val msg1 = Resources("invalidConfigOption", String.valueOf(c)) + '\n' 820 val msg2 = Resources("probarg", reporterArg) + '\n' 821 822 throw new IllegalArgumentException(msg1 + msg2) 823 } 824 } 825 set 826 } 827 828 private[scalatest] def parseReporterArgsIntoConfigurations(args: List[String]) = { 829 // 830 // Checks to see if any args are smaller than two characters in length. 831 // Allows a one-character arg if it's a directory-name parameter, to 832 // permit use of "." for example. 833 // 834 def argTooShort(args: List[String]): Boolean = { 835 args match { 836 case Nil => false 837 838 case "-u" :: directory :: list => argTooShort(list) 839 840 case x :: list => 841 if (x.length < 2) true 842 else argTooShort(list) 843 } 844 } 845 846 if (args == null) 847 throw new NullPointerException("args was null") 848 849 if (args.exists(_ == null)) 850 throw new NullPointerException("an arg String was null") 851 852 if (argTooShort(args)) // TODO: check and print out a user friendly message for this 853 throw new IllegalArgumentException("an arg String was less than 2 in length: " + args) 854 855 for (dashX <- List("-g", "-o", "-e")) { 856 if (args.toList.count(_.startsWith(dashX)) > 1) // TODO: also check and print a user friendly message for this 857 throw new IllegalArgumentException("Only one " + dashX + " allowed") 858 } 859 860 // TODO: also check and print a user friendly message for this 861 // again here, i had to skip some things, so I had to use an iterator. 862 val it = args.iterator 863 while (it.hasNext) 864 it.next.take(2).toString match { 865 case "-g" => 866 case "-o" => 867 case "-e" => 868 case "-f" => 869 if (it.hasNext) 870 it.next // scroll past the filename 871 else 872 throw new IllegalArgumentException("-f needs to be followed by a file name arg: ") 873 case "-u" => 874 if (it.hasNext) { 875 val directory = it.next 876 if (!(new File(directory).isDirectory)) 877 throw new IllegalArgumentException( 878 "arg for -u option is not a directory [" + directory + "]") 879 else {} 880 } 881 else { 882 throw new IllegalArgumentException("-u needs to be followed by a directory name arg: ") 883 } 884 case "-h" => 885 if (it.hasNext) 886 it.next // scroll past the filename 887 else 888 throw new IllegalArgumentException("-h needs to be followed by a file name arg: ") 889 case "-r" => 890 if (it.hasNext) 891 it.next // scroll past the reporter class 892 else 893 throw new IllegalArgumentException("-r needs to be followed by a reporter class name arg: ") 894 case arg: String => 895 throw new IllegalArgumentException("An arg started with an invalid character string: " + arg) 896 } 897 898 val graphicReporterConfigurationOption = 899 args.find(arg => arg.startsWith("-g")) match { 900 case Some(dashGString) => 901 val configSet = parseConfigSet(dashGString) 902 if (configSet.contains(PresentShortStackTraces)) 903 throw new IllegalArgumentException("Cannot specify an S (present short stack traces) configuration parameter for the graphic reporter (because it shows them anyway): " + dashGString) 904 if (configSet.contains(PresentFullStackTraces)) 905 throw new IllegalArgumentException("Cannot specify an F (present full stack traces) configuration parameter for the graphic reporter (because it shows them anyway): " + dashGString) 906 if (configSet.contains(PresentWithoutColor)) 907 throw new IllegalArgumentException("Cannot specify a W (present without color) configuration parameter for the graphic reporter: " + dashGString) 908 if (configSet.contains(PresentAllDurations)) 909 throw new IllegalArgumentException("Cannot specify a D (present all durations) configuration parameter for the graphic reporter (because it shows them all anyway): " + dashGString) 910 Some(new GraphicReporterConfiguration(configSet)) 911 case None => None 912 } 913 914 def buildFileReporterConfigurationList(args: List[String]) = { 915 val it = args.iterator 916 val lb = new ListBuffer[FileReporterConfiguration] 917 while (it.hasNext) { 918 val arg = it.next 919 if (arg.startsWith("-f")) 920 lb += new FileReporterConfiguration(parseConfigSet(arg), it.next) 921 } 922 lb.toList 923 } 924 val fileReporterConfigurationList = buildFileReporterConfigurationList(args) 925 926 def buildXmlReporterConfigurationList(args: List[String]) = { 927 val it = args.iterator 928 val lb = new ListBuffer[XmlReporterConfiguration] 929 while (it.hasNext) { 930 val arg = it.next 931 if (arg.startsWith("-u")) 932 lb += new XmlReporterConfiguration(Set[ReporterConfigParam](), 933 it.next) 934 } 935 lb.toList 936 } 937 val xmlReporterConfigurationList = buildXmlReporterConfigurationList(args) 938 939 def buildHtmlReporterConfigurationList(args: List[String]) = { 940 val it = args.iterator 941 val lb = new ListBuffer[HtmlReporterConfiguration] 942 while (it.hasNext) { 943 val arg = it.next 944 if (arg.startsWith("-h")) 945 lb += new HtmlReporterConfiguration(parseConfigSet(arg), it.next) 946 } 947 lb.toList 948 } 949 val htmlReporterConfigurationList = buildHtmlReporterConfigurationList(args) 950 951 val standardOutReporterConfigurationOption = 952 args.find(arg => arg.startsWith("-o")) match { 953 case Some(dashOString) => Some(new StandardOutReporterConfiguration(parseConfigSet(dashOString))) 954 case None => None 955 } 956 957 val standardErrReporterConfigurationOption = 958 args.find(arg => arg.startsWith("-e")) match { 959 case Some(dashEString) => Some(new StandardErrReporterConfiguration(parseConfigSet(dashEString))) 960 case None => None 961 } 962 963 def buildCustomReporterConfigurationList(args: List[String]) = { 964 val it = args.iterator 965 val lb = new ListBuffer[CustomReporterConfiguration] 966 while (it.hasNext) { 967 val arg = it.next 968 if (arg.startsWith("-r")) { 969 val dashRString = arg 970 val customReporterClassName = it.next 971 val configSet = parseConfigSet(dashRString) 972 if (configSet.contains(PresentShortStackTraces)) 973 throw new IllegalArgumentException("Cannot specify an S (present short stack traces) configuration parameter for a custom reporter: " + dashRString + " " + customReporterClassName) 974 if (configSet.contains(PresentFullStackTraces)) 975 throw new IllegalArgumentException("Cannot specify an F (present full stack traces) configuration parameter for a custom reporter: " + dashRString + " " + customReporterClassName) 976 if (configSet.contains(PresentWithoutColor)) 977 throw new IllegalArgumentException("Cannot specify a W (without color) configuration parameter for a custom reporter: " + dashRString + " " + customReporterClassName) 978 if (configSet.contains(PresentAllDurations)) 979 throw new IllegalArgumentException("Cannot specify a D (present all durations) configuration parameter for a custom reporter: " + dashRString + " " + customReporterClassName) 980 lb += new CustomReporterConfiguration(configSet, customReporterClassName) 981 } 982 } 983 lb.toList 984 } 985 val customReporterConfigurationList = buildCustomReporterConfigurationList(args) 986 987 // Here instead of one loop, i go through the loop several times. 988 new ReporterConfigurations( 989 graphicReporterConfigurationOption, 990 fileReporterConfigurationList, 991 xmlReporterConfigurationList, 992 standardOutReporterConfigurationOption, 993 standardErrReporterConfigurationOption, 994 htmlReporterConfigurationList, 995 customReporterConfigurationList 996 ) 997 } 998 999 // Used to parse -s, -j, -m, and -w args, one of which will be passed as a String as dashArg 1000 private[scalatest] def parseSuiteArgsIntoNameStrings(args: List[String], dashArg: String) = { 1001 1002 if (args == null) 1003 throw new NullPointerException("args was null") 1004 1005 if (args.exists(_ == null)) 1006 throw new NullPointerException("an arg String was null") 1007 1008 if (dashArg != "-j" && dashArg != "-s" && dashArg != "-w" && dashArg != "-m" && dashArg != "-t") 1009 throw new NullPointerException("dashArg invalid: " + dashArg) 1010 1011 val lb = new ListBuffer[String] 1012 val it = args.iterator 1013 while (it.hasNext) { 1014 val dashS = it.next 1015 if (dashS != dashArg) 1016 throw new IllegalArgumentException("Every other element, starting with the first, must be -s") 1017 if (it.hasNext) { 1018 val suiteName = it.next 1019 if (!suiteName.startsWith("-")) 1020 lb += suiteName 1021 else 1022 throw new IllegalArgumentException("Expecting a Suite class name to follow -s, but got: " + suiteName) 1023 } 1024 else 1025 throw new IllegalArgumentException("Last element must be a Suite class name, not a -s.") 1026 } 1027 lb.toList 1028 } 1029 1030 private[scalatest] def parseCompoundArgIntoSet(args: List[String], expectedDashArg: String): Set[String] = 1031 Set() ++ parseCompoundArgIntoList(args, expectedDashArg) 1032 1033 private[scalatest] def parseRunpathArgIntoList(args: List[String]): List[String] = parseCompoundArgIntoList(args, "-p") 1034 1035 private[scalatest] def parseCompoundArgIntoList(args: List[String], expectedDashArg: String): List[String] = { 1036 1037 if (args == null) 1038 throw new NullPointerException("args was null") 1039 1040 if (args.exists(_ == null)) 1041 throw new NullPointerException("an arg String was null") 1042 1043 if (args.length == 0) { 1044 List() 1045 } 1046 else if (args.length == 2) { 1047 val dashArg = args(0) 1048 val runpathArg = args(1) 1049 1050 if (dashArg != expectedDashArg) 1051 throw new IllegalArgumentException("First arg must be " + expectedDashArg + ", but was: " + dashArg) 1052 1053 if (runpathArg.trim.isEmpty) 1054 throw new IllegalArgumentException("The runpath string must actually include some non-whitespace characters.") 1055 1056 splitPath(runpathArg) 1057 } 1058 else { 1059 throw new IllegalArgumentException("Runpath must be either zero or two args: " + args) 1060 } 1061 } 1062 1063 // 1064 // Splits a space-delimited path into its component parts. 1065 // 1066 // Spaces within path elements may be escaped with backslashes, e.g. 1067 // "c:\Documents\ And\ Settings c:\Program\ Files" 1068 // 1069 // See comments for isCompleteToken() below for exceptions. 1070 // 1071 private val START_TOKEN_PATTERN = Pattern.compile("""^\s*(.*?)(\s|$)""") 1072 private val FULL_TOKEN_PATTERN = Pattern.compile("""^\s*(.+?)(((?<=[^\\])\s)|$)""") 1073 private def splitPath(pathArg: String): List[String] = { 1074 val path = pathArg.trim 1075 1076 if (path.isEmpty) Nil 1077 else { 1078 val startMatcher = START_TOKEN_PATTERN.matcher(path) 1079 1080 if (!startMatcher.find()) 1081 throw new RuntimeException("unexpected startMatcher path [" + 1082 path + "]") 1083 val token = startMatcher.group(1) 1084 1085 if (isCompleteToken(token)) { 1086 token :: splitPath(path.substring(startMatcher.end)) 1087 } 1088 else { 1089 val fullMatcher = FULL_TOKEN_PATTERN.matcher(path) 1090 1091 if (!fullMatcher.find()) 1092 throw new RuntimeException("unexpected fullMatcher path [" + 1093 path + "]") 1094 val fullToken = fullMatcher.group(1).replaceAll("""\\(\s)""", "$1") 1095 1096 fullToken :: splitPath(path.substring(fullMatcher.end)) 1097 } 1098 } 1099 } 1100 1101 // 1102 // Determines whether specified token is complete or partial. 1103 // 1104 // Tokens are considered partial if they end with a backslash, since 1105 // backslash is used to escape spaces that would otherwise be 1106 // treated as delimiters within the path string. 1107 // 1108 // Exceptions are cases where the token ends in a backslash 1109 // but is still considered a complete token because it constitutes 1110 // a valid representation of a root directory on a windows system, 1111 // e.g. "c:\" or just "\". 1112 // 1113 private val ROOT_DIR_PATTERN = Pattern.compile("""(?i)\\|[a-z]:\\""") 1114 private def isCompleteToken(token: String): Boolean = { 1115 val matcher = ROOT_DIR_PATTERN.matcher(token) 1116 1117 matcher.matches() || (token(token.length - 1) != '\\') 1118 } 1119 1120 private[scalatest] def parsePropertiesArgsIntoMap(args: List[String]) = { 1121 1122 if (args == null) 1123 throw new NullPointerException("args was null") 1124 1125 if (args.exists(_ == null)) 1126 throw new NullPointerException("an arg String was null") 1127 1128 if (args.exists(_.indexOf('=') == -1)) 1129 throw new IllegalArgumentException("A -D arg does not contain an equals sign.") 1130 1131 if (args.exists(!_.startsWith("-D"))) 1132 throw new IllegalArgumentException("A spice arg does not start with -D.") 1133 1134 if (args.exists(_.indexOf('=') == 2)) 1135 throw new IllegalArgumentException("A spice arg does not have a key to the left of the equals sign.") 1136 1137 if (args.exists(arg => arg.indexOf('=') == arg.length - 1)) 1138 throw new IllegalArgumentException("A spice arg does not have a value to the right of the equals sign.") 1139 1140 val tuples = for (arg <- args) yield { 1141 val keyValue = arg.substring(2) // Cut off the -D at the beginning 1142 val equalsPos = keyValue.indexOf('=') 1143 val key = keyValue.substring(0, equalsPos) 1144 val value = keyValue.substring(equalsPos + 1) 1145 (key, value) 1146 } 1147 1148 scala.collection.immutable.Map() ++ tuples 1149 } 1150 1151 // For debugging. 1152/* 1153 private[scalatest] def printOpts(opt: EventToPresent.Set32) { 1154 if (opt.contains(EventToPresent.PresentRunStarting)) 1155 println("PresentRunStarting") 1156 if (opt.contains(EventToPresent.PresentTestStarting)) 1157 println("PresentTestStarting") 1158 if (opt.contains(EventToPresent.PresentTestSucceeded)) 1159 println("PresentTestSucceeded") 1160 if (opt.contains(EventToPresent.PresentTestFailed)) 1161 println("PresentTestFailed") 1162 if (opt.contains(EventToPresent.PresentTestIgnored)) 1163 println("PresentTestIgnored") 1164 if (opt.contains(EventToPresent.PresentSuiteStarting)) 1165 println("PresentSuiteStarting") 1166 if (opt.contains(EventToPresent.PresentSuiteCompleted)) 1167 println("PresentSuiteCompleted") 1168 if (opt.contains(EventToPresent.PresentSuiteAborted)) 1169 println("PresentSuiteAborted") 1170 if (opt.contains(EventToPresent.PresentInfoProvided)) 1171 println("PresentInfoProvided") 1172 if (opt.contains(EventToPresent.PresentRunStopped)) 1173 println("PresentRunStopped") 1174 if (opt.contains(EventToPresent.PresentRunCompleted)) 1175 println("PresentRunCompleted") 1176 if (opt.contains(EventToPresent.PresentRunAborted)) 1177 println("PresentRunAborted") 1178 } 1179*/ 1180 1181 private def configSetMinusNonFilterParams(configSet: Set[ReporterConfigParam]) = 1182 (((configSet - PresentShortStackTraces) - PresentFullStackTraces) - PresentWithoutColor) - PresentAllDurations 1183 1184 private[scalatest] def getDispatchReporter(reporterSpecs: ReporterConfigurations, graphicReporter: Option[Reporter], passFailReporter: Option[Reporter], loader: ClassLoader) = { 1185 1186 def getReporterFromConfiguration(configuration: ReporterConfiguration): Reporter = 1187 1188 configuration match { 1189 case StandardOutReporterConfiguration(configSet) => 1190 if (configSetMinusNonFilterParams(configSet).isEmpty) 1191 new StandardOutReporter( 1192 configSet.contains(PresentAllDurations), 1193 !configSet.contains(PresentWithoutColor), 1194 configSet.contains(PresentShortStackTraces) || configSet.contains(PresentFullStackTraces), 1195 configSet.contains(PresentFullStackTraces) // If they say both S and F, F overrules 1196 ) 1197 else 1198 new FilterReporter( 1199 new StandardOutReporter( 1200 configSet.contains(PresentAllDurations), 1201 !configSet.contains(PresentWithoutColor), 1202 configSet.contains(PresentShortStackTraces) || configSet.contains(PresentFullStackTraces), 1203 configSet.contains(PresentFullStackTraces) // If they say both S and F, F overrules 1204 ), 1205 configSet 1206 ) 1207 1208 case StandardErrReporterConfiguration(configSet) => 1209 if (configSetMinusNonFilterParams(configSet).isEmpty) 1210 new StandardErrReporter( 1211 configSet.contains(PresentAllDurations), 1212 !configSet.contains(PresentWithoutColor), 1213 configSet.contains(PresentShortStackTraces) || configSet.contains(PresentFullStackTraces), 1214 configSet.contains(PresentFullStackTraces) // If they say both S and F, F overrules 1215 ) 1216 else 1217 new FilterReporter( 1218 new StandardErrReporter( 1219 configSet.contains(PresentAllDurations), 1220 !configSet.contains(PresentWithoutColor), 1221 configSet.contains(PresentShortStackTraces) || configSet.contains(PresentFullStackTraces), 1222 configSet.contains(PresentFullStackTraces) // If they say both S and F, F overrules 1223 ), 1224 configSet 1225 ) 1226 1227 case FileReporterConfiguration(configSet, fileName) => 1228 if (configSetMinusNonFilterParams(configSet).isEmpty) 1229 new FileReporter( 1230 fileName, 1231 configSet.contains(PresentAllDurations), 1232 !configSet.contains(PresentWithoutColor), 1233 configSet.contains(PresentShortStackTraces) || configSet.contains(PresentFullStackTraces), 1234 configSet.contains(PresentFullStackTraces) // If they say both S and F, F overrules 1235 ) 1236 else 1237 new FilterReporter( 1238 new FileReporter( 1239 fileName, 1240 configSet.contains(PresentAllDurations), 1241 !configSet.contains(PresentWithoutColor), 1242 configSet.contains(PresentShortStackTraces) || configSet.contains(PresentFullStackTraces), 1243 configSet.contains(PresentFullStackTraces) // If they say both S and F, F overrules 1244 ), 1245 configSet 1246 ) 1247 1248 case XmlReporterConfiguration(configSet, directory) => 1249 new XmlReporter(directory) 1250 1251 case HtmlReporterConfiguration(configSet, fileName) => 1252 if (configSetMinusNonFilterParams(configSet).isEmpty) 1253 new HtmlReporter( 1254 fileName, 1255 configSet.contains(PresentAllDurations), 1256 !configSet.contains(PresentWithoutColor), 1257 configSet.contains(PresentShortStackTraces) || configSet.contains(PresentFullStackTraces), 1258 configSet.contains(PresentFullStackTraces) // If they say both S and F, F overrules 1259 ) 1260 else 1261 new FilterReporter( 1262 new HtmlReporter( 1263 fileName, 1264 configSet.contains(PresentAllDurations), 1265 !configSet.contains(PresentWithoutColor), 1266 configSet.contains(PresentShortStackTraces) || configSet.contains(PresentFullStackTraces), 1267 configSet.contains(PresentFullStackTraces) // If they say both S and F, F overrules 1268 ), 1269 configSet 1270 ) 1271 1272 case CustomReporterConfiguration(configSet, reporterClassName) => { 1273 val customReporter = getCustomReporter(reporterClassName, loader, "-r... " + reporterClassName) 1274 if (configSet.isEmpty) 1275 customReporter 1276 else 1277 new FilterReporter(customReporter, configSet) 1278 } 1279 case GraphicReporterConfiguration(configSet) => throw new RuntimeException("Should never happen.") 1280 } 1281 1282 val reporterSeq = 1283 (for (spec <- reporterSpecs) 1284 yield getReporterFromConfiguration(spec)) 1285 1286 val almostFullReporterList: List[Reporter] = 1287 graphicReporter match { 1288 case None => reporterSeq.toList 1289 case Some(gRep) => gRep :: reporterSeq.toList 1290 } 1291 1292 val fullReporterList: List[Reporter] = 1293 passFailReporter match { 1294 case Some(pfr) => pfr :: almostFullReporterList 1295 case None => almostFullReporterList 1296 } 1297 1298 new DispatchReporter(fullReporterList) 1299 } 1300 1301 private def getCustomReporter(reporterClassName: String, loader: ClassLoader, argString: String): Reporter = { 1302 try { 1303 val reporterClass: java.lang.Class[_] = loader.loadClass(reporterClassName) 1304 reporterClass.newInstance.asInstanceOf[Reporter] 1305 } // Could probably catch ClassCastException too 1306 catch { 1307 case e: ClassNotFoundException => { 1308 1309 val msg1 = Resources("cantLoadReporterClass", reporterClassName) 1310 val msg2 = Resources("probarg", argString) 1311 val msg = msg1 + "\n" + msg2 1312 1313 val iae = new IllegalArgumentException(msg) 1314 iae.initCause(e) 1315 throw iae 1316 } 1317 case e: InstantiationException => { 1318 1319 val msg1 = Resources("cantInstantiateReporter", reporterClassName) 1320 val msg2 = Resources("probarg", argString) 1321 val msg = msg1 + "\n" + msg2 1322 1323 val iae = new IllegalArgumentException(msg) 1324 iae.initCause(e) 1325 throw iae 1326 } 1327 case e: IllegalAccessException => { 1328 1329 val msg1 = Resources("cantInstantiateReporter", reporterClassName) 1330 val msg2 = Resources("probarg", argString) 1331 val msg = msg1 + "\n" + msg2 1332 1333 val iae = new IllegalArgumentException(msg) 1334 iae.initCause(e) 1335 throw iae 1336 } 1337 } 1338 } 1339 1340 private[scalatest] def doRunRunRunDaDoRunRun( 1341 dispatch: DispatchReporter, 1342 suitesList: List[String], 1343 junitsList: List[String], 1344 stopRequested: Stopper, 1345 filter: Filter, 1346 configMap: Map[String, String], 1347 concurrent: Boolean, 1348 membersOnlyList: List[String], 1349 wildcardList: List[String], 1350 testNGList: List[String], 1351 runpath: List[String], 1352 loader: ClassLoader, 1353 doneListener: RunDoneListener, 1354 runStamp: Int, 1355 numThreads: Int 1356 ) = { 1357 1358 // TODO: add more, and to RunnerThread too 1359 if (dispatch == null) 1360 throw new NullPointerException 1361 if (suitesList == null) 1362 throw new NullPointerException 1363 if (junitsList == null) 1364 throw new NullPointerException 1365 if (stopRequested == null) 1366 throw new NullPointerException 1367 if (filter == null) 1368 throw new NullPointerException 1369 if (configMap == null) 1370 throw new NullPointerException 1371 if (membersOnlyList == null) 1372 throw new NullPointerException 1373 if (wildcardList == null) 1374 throw new NullPointerException 1375 if (runpath == null) 1376 throw new NullPointerException 1377 if (loader == null) 1378 throw new NullPointerException 1379 if (doneListener == null) 1380 throw new NullPointerException 1381 1382 val tagsToInclude = 1383 filter.tagsToInclude match { 1384 case None => Set[String]() 1385 case Some(tti) => tti 1386 } 1387 val tagsToExclude = filter.tagsToExclude 1388 1389 var tracker = new Tracker(new Ordinal(runStamp)) 1390 1391 val runStartTime = System.currentTimeMillis 1392 1393 try { 1394 val loadProblemsExist = 1395 try { 1396 val unassignableList = suitesList.filter(className => !classOf[Suite].isAssignableFrom(loader.loadClass(className))) 1397 if (!unassignableList.isEmpty) { 1398 val names = for (className <- unassignableList) yield " " + className 1399 dispatch(RunAborted(tracker.nextOrdinal(), Resources("nonSuite") + names, None)) 1400 true 1401 } 1402 else { 1403 false 1404 } 1405 } 1406 catch { 1407 case e: ClassNotFoundException => { 1408 dispatch(RunAborted(tracker.nextOrdinal(), Resources("cannotLoadSuite", e.getMessage), Some(e))) 1409 true 1410 } 1411 } 1412 1413 if (!loadProblemsExist) { 1414 try { 1415 val namedSuiteInstances: List[Suite] = 1416 for (suiteClassName <- suitesList) 1417 yield { 1418 val clazz = loader.loadClass(suiteClassName) 1419 clazz.newInstance.asInstanceOf[Suite] 1420 } 1421 1422 val junitSuiteInstances: List[Suite] = 1423 for (junitClassName <- junitsList) 1424 yield new JUnitWrapperSuite(junitClassName, loader) 1425 1426 val testNGWrapperSuiteList: List[TestNGWrapperSuite] = 1427 if (!testNGList.isEmpty) 1428 List(new TestNGWrapperSuite(testNGList)) 1429 else 1430 Nil 1431 1432 val (membersOnlySuiteInstances, wildcardSuiteInstances) = { 1433 1434 val membersOnlyAndBeginsWithListsAreEmpty = membersOnlyList.isEmpty && wildcardList.isEmpty // They didn't specify any -m's or -w's on the command line 1435 1436 1437 // TODO: rename the 'BeginsWith' variables to 'Wildcard' to match the terminology that 1438 // we ended up with on the outside 1439 // TODO: Should SuiteDiscoverHelper be a singleton object? 1440 if (membersOnlyAndBeginsWithListsAreEmpty && (!suitesList.isEmpty || !junitsList.isEmpty)) { 1441 (Nil, Nil) // No DiscoverySuites in this case. Just run Suites named with -s or -j 1442 } 1443 else { 1444 val accessibleSuites = (new SuiteDiscoveryHelper).discoverSuiteNames(runpath, loader) 1445 1446 if (membersOnlyAndBeginsWithListsAreEmpty && suitesList.isEmpty && junitsList.isEmpty) { 1447 // In this case, they didn't specify any -w, -m, -s, or -j on the command line, so the default 1448 // is to run any accessible Suites discovered on the runpath 1449 (Nil, List(new DiscoverySuite("", accessibleSuites, true, loader))) 1450 } 1451 else { 1452 val membersOnlyInstances = 1453 for (membersOnlyName <- membersOnlyList) 1454 yield new DiscoverySuite(membersOnlyName, accessibleSuites, false, loader) 1455 1456 val wildcardInstances = 1457 for (wildcardName <- wildcardList) 1458 yield new DiscoverySuite(wildcardName, accessibleSuites, true, loader) 1459 1460 (membersOnlyInstances, wildcardInstances) 1461 } 1462 } 1463 } 1464 1465 val suiteInstances: List[Suite] = namedSuiteInstances ::: junitSuiteInstances ::: membersOnlySuiteInstances ::: wildcardSuiteInstances ::: testNGWrapperSuiteList 1466 1467 val testCountList = 1468 for (suite <- suiteInstances) 1469 yield suite.expectedTestCount(filter) 1470 1471 def sumInts(list: List[Int]): Int = 1472 list match { 1473 case Nil => 0 1474 case x :: xs => x + sumInts(xs) 1475 } 1476 1477 val expectedTestCount = sumInts(testCountList) 1478 1479 dispatch(RunStarting(tracker.nextOrdinal(), expectedTestCount, configMap)) 1480 1481 if (concurrent) { 1482 1483 // Because some tests may do IO, will create a pool of 2 times the number of processors reported 1484 // by the Runtime's availableProcessors method. 1485 val poolSize = 1486 if (numThreads > 0) numThreads 1487 else Runtime.getRuntime.availableProcessors * 2 1488 1489 val execSvc: ExecutorService = Executors.newFixedThreadPool(poolSize) 1490 try { 1491 1492 if (System.getProperty("org.scalatest.tools.Runner.forever", "false") == "true") { 1493 val distributor = new ConcurrentDistributor(dispatch, stopRequested, filter, configMap, execSvc) 1494 while (true) { 1495 for (suite <- suiteInstances) { 1496 distributor.apply(suite, tracker.nextTracker()) 1497 } 1498 distributor.waitUntilDone() 1499 } 1500 } 1501 else { 1502 val distributor = new ConcurrentDistributor(dispatch, stopRequested, filter, configMap, execSvc) 1503 for (suite <- suiteInstances) { 1504 distributor.apply(suite, tracker.nextTracker()) 1505 } 1506 distributor.waitUntilDone() 1507 } 1508 } 1509 finally { 1510 execSvc.shutdown() 1511 } 1512 } 1513 else { 1514 for (suite <- suiteInstances) { 1515 val suiteRunner = new SuiteRunner(suite, dispatch, stopRequested, filter, 1516 configMap, None, tracker) 1517 suiteRunner.run() 1518 } 1519 } 1520 1521 val duration = System.currentTimeMillis - runStartTime 1522 if (stopRequested()) { 1523 dispatch(RunStopped(tracker.nextOrdinal(), Some(duration))) 1524 } 1525 else { 1526 dispatch(RunCompleted(tracker.nextOrdinal(), Some(duration))) 1527 } 1528 } 1529 catch { 1530 case e: InstantiationException => 1531 dispatch(RunAborted(tracker.nextOrdinal(), Resources("cannotInstantiateSuite", e.getMessage), Some(e), Some(System.currentTimeMillis - runStartTime))) 1532 case e: IllegalAccessException => 1533 dispatch(RunAborted(tracker.nextOrdinal(), Resources("cannotInstantiateSuite", e.getMessage), Some(e), Some(System.currentTimeMillis - runStartTime))) 1534 case e: NoClassDefFoundError => 1535 dispatch(RunAborted(tracker.nextOrdinal(), Resources("cannotLoadClass", e.getMessage), Some(e), Some(System.currentTimeMillis - runStartTime))) 1536 case e: Throwable => 1537 dispatch(RunAborted(tracker.nextOrdinal(), Resources.bigProblems(e), Some(e), Some(System.currentTimeMillis - runStartTime))) 1538 } 1539 } 1540 } 1541 finally { 1542 dispatch.dispatchDisposeAndWaitUntilDone() 1543 doneListener.done() 1544 } 1545 } 1546 1547 private[scalatest] def excludesWithIgnore(excludes: Set[String]) = excludes + "org.scalatest.Ignore" 1548 1549 private[scalatest] def withClassLoaderAndDispatchReporter(runpathList: List[String], reporterSpecs: ReporterConfigurations, 1550 graphicReporter: Option[Reporter], passFailReporter: Option[Reporter])(f: (ClassLoader, DispatchReporter) => Unit): Unit = { 1551 1552 val loader: ClassLoader = getRunpathClassLoader(runpathList) 1553 try { 1554 Thread.currentThread.setContextClassLoader(loader) 1555 try { 1556 val dispatchReporter = getDispatchReporter(reporterSpecs, graphicReporter, passFailReporter, loader) 1557 try { 1558 f(loader, dispatchReporter) 1559 } 1560 finally { 1561 dispatchReporter.dispatchDisposeAndWaitUntilDone() 1562 } 1563 } 1564 catch { 1565 // getDispatchReporter may complete abruptly with an exception, if there is an problem trying to load 1566 // or instantiate a custom reporter class. 1567 case ex: Throwable => { 1568 System.err.println(Resources("bigProblemsMaybeCustomReporter")) 1569 ex.printStackTrace(System.err) 1570 } 1571 } 1572 } 1573 finally { 1574 // eventually call close on the RunpathClassLoader 1575 } 1576 } 1577 1578 private[scalatest] def getRunpathClassLoader(runpathList: List[String]): ClassLoader = { 1579 1580 if (runpathList == null) 1581 throw new NullPointerException 1582 if (runpathList.isEmpty) { 1583 classOf[Suite].getClassLoader // Could this be null technically? 1584 } 1585 else { 1586 val urlsList: List[URL] = 1587 for (raw <- runpathList) yield { 1588 try { 1589 new URL(raw) 1590 } 1591 catch { 1592 case murle: MalformedURLException => { 1593 1594 // Assume they tried to just pass in a file name 1595 val file: File = new File(raw) 1596 1597 // file.toURL may throw MalformedURLException too, but for now 1598 // just let that propagate up. 1599 file.toURI.toURL // If a dir, comes back terminated by a slash 1600 } 1601 } 1602 } 1603 1604 // Here is where the Jini preferred class loader stuff went. 1605 1606 // Tell the URLConnections to not use caching, so that repeated runs and reruns actually work 1607 // on the latest binaries. 1608 for (url <- urlsList) { 1609 try { 1610 url.openConnection.setDefaultUseCaches(false) 1611 } 1612 catch { 1613 case e: IOException => // just ignore these 1614 } 1615 } 1616 1617 new URLClassLoader(urlsList.toArray, classOf[Suite].getClassLoader) 1618 } 1619 } 1620 1621 private[scalatest] def usingEventDispatchThread(f: => Unit): Unit = { 1622 SwingUtilities.invokeLater( 1623 new Runnable() { 1624 def run() { 1625 f 1626 } 1627 } 1628 ) 1629 } 1630} 1631