org.scalatest.tools.Runner.scala Maven / Gradle / Ivy
Show all versions of scalatest_2.9.1 Show documentation
/* * 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.tools import org.scalatest._ import scala.collection.mutable.ListBuffer import java.lang.reflect.Constructor import java.lang.reflect.Modifier import java.net.URL import java.net.MalformedURLException import java.net.URLClassLoader import java.io.File import java.io.IOException import javax.swing.SwingUtilities import java.util.concurrent.ArrayBlockingQueue import java.util.regex.Pattern import org.scalatest.testng.TestNGWrapperSuite import java.util.concurrent.Semaphore import org.scalatest.events._ import org.scalatest.junit.JUnitWrapperSuite import java.util.concurrent.Executors import java.util.concurrent.ExecutorService import scala.collection.mutable.ArrayBuffer import SuiteDiscoveryHelper._ import org.scalatest.time.Span import org.scalatest.time.Seconds import org.scalatest.time.Millis /* Command line args: a - archive count for dashboard reporter --archive A - select previously failed and/or canceled tests to rerun --again b - run a testNG test (b for Beust) --testng B - c - parallel execution (--parallel) --parallel (deprecated, will be custom reporter) C - custom reporter (temporarily) d - dashboard reporter --dashboard D - config map pair -D e - standard error reporter (--stderr-reporter) --stderr E - f - file reporter --file F - Span scale factor g - graphical reporter --graphical G - h - HTML Reporter --html H - i - this one is used for the Suite ID --suiteId I - j - currently JUnit directly (can drop and use WrapWith) --junit J - k - This could be chosenVerb must, should, or neither --chosenStyles K - l - tags to exclude --exclude L - m - members only path --members M - memory reporter --memory (records failed tests in a file, so they can be rerun with A) n - tags to include --include N - o - standard out reporter O - p - space-separated runpath (currently deprecated, will be parallel execution) P - parallel execution (temporarily) q - Suffix? -q Spec will only look at class files whose name ends in Spec Q - equalivalent to -q Suite -q Spec r - custom reporter (currently deprecated, will be runpath) R - space-separated runpath (temporarily) s - suite class name (to become a glob) S - t - test name T - sorting timeout --sorting-timeout u - JUnit XML reporter U - v - ScalaTest version number (also -version and --version) V - w - wildcard path W - x - save for ScalaTest native XML X - y - sets org.scalatest.chosenstyle -y FunSpec or -y "FunSpec FunSuite" -Y for after -h to set a custom style sheet z - test name wildcard Z - StringReporter configuration params: A B C - drop TestSucceeded events D - show all durations E - drop TestPending events F - show full stack traces *G - reminder with full stack traces H - drop SuiteStarting events *I - Reminder without stack traces J *K - exclude TestCanceled events from reminder L - drop SuiteCompleted events M - drop MarkupProvided events N - drop TestStarting events O - drop InfoProvided events P - drop ScopeOpened events Q - drop ScopeClosed events R - drop ScopePending events S - show short stack traces *T - reminder with short stack traces U - unformatted mode V W - without color X - drop TestIgnored events Z */ private[tools] case class SuiteParam(className: String, testNames: Array[String], wildcardTestNames: Array[String], nestedSuites: Array[NestedSuiteParam]) private[tools] case class NestedSuiteParam(suiteId: String, testNames: Array[String], wildcardTestNames: Array[String]) private[scalatest] case class ConcurrentConfig(numThreads: Int, enableSuiteSortingReporter: Boolean) /** * Application that runs a suite of tests. * *-z. * The* Note: this application offers the full range of ScalaTest features via command line arguments described below. If you just want * to run a suite of tests from the command line and see results on the standard output, you may prefer to use ScalaTest's simple runner. *
* ** The basic form of a
* *Runnerinvocation is: ** scala [-cp scalatest-<version>.jar:...] org.scalatest.tools.Runner [arguments] ** ** The arguments
* *Runneraccepts are described in the following table: **
* ** argument description example * -Dkey=valuedefines a key/value pair for the config map -DmaxConnections=100* -R <runpath elements>the specifies the runpath from which tests classes will be
discovered and loaded (Note: only one-Rallowed)Unix: -R target/classes:target/generated/classes
Windows:-R target\classes;target\generated\classes* -n <tag name>specifies a tag to include (Note: only one tag name allowed per -n)-n UnitTests -n FastTests* -l <tag name>specifies a tag to exclude (Note: only one tag name allowed per -l)-l SlowTests -l PerfTests* -P[S][integer thread count]specifies a parallel run, with optional suite sorting and thread count
(Note: only one-Pallowed)-P,-PS,-PS 8, or-P8* -s <suite class name>specifies a suite class to run -s com.company.project.StackSpec* -m <members-only package>requests that suites that are direct members of the specified package
be discovered and run-m com.company.project* -w <wildcard package>requests that suites that are members of the specified package or its subpackages
be discovered and run-w com.company.project* -q <suffixes>specify suffixes to discover -q Spec -q Suite* -Qdiscover only classes whose names end with SpecorSuite
(or other suffixes specified by-q)-Q* -j <JUnit class name>instantiate and run a JUnit test class -j StackTestClass* -b <TestNG XML file>run TestNG tests using the specified TestNG XML file -b testng.xml* -F <span scale factor>a factor by which to scale time spans
(Note: only one-Fis allowed)-F 10or-F 2.5* -T <sorting timeout>specifies a integer timeout (in seconds) for sorting the events of
parallel runs back into sequential order-T 5* -y <chosen styles>specifies chosen styles for your project -y org.scalatest.FlatSpec* -i <suite ID>specifies a suite to run by ID (Note: must follow -s,
and is intended to be used primarily by tools such as IDEs.)-i com.company.project.FileSpec-file1.txt* -t <test name>select the test with the specified name
(Note: must follow-sor-i)-t "An empty Stack should complain when popped"* -z <test name substring>select tests whose names include the specified substring
(Note: must follow-sor-i)-z "popped"* -g[NCXEHLOPQMD]select the graphical reporter -g* -f[NCXEHLOPQMDWSFU] <filename>select the file reporter -f output.txt* -u <directory name>select the JUnit XML reporter -u target/junitxmldir* -h <directory name> [-Y <css file name>]select the HTML reporter, optionally including the specified CSS file -h target/htmldir -Y src/main/html/customStyles.css* -vprint the ScalaTest version -vor, also-version* -o[NCXEHLOPQMDWSFU]select the standard output reporter -o* -e[NCXEHLOPQMDWSFU]select the standard error reporter -e* -C[NCXEHLOPQMD] <reporter class>select a custom reporter -C com.company.project.BarReporter* The simplest way to start
* *Runneris to specify the directory containing your compiled tests as the sole element of the runpath, for example: *scala -classpath scalatest-<version>.jar org.scalatest.tools.Runner -R compiled_tests* ** Given the previous command,
* * *Runnerwill discover and execute allSuites in thecompiled_testsdirectory and its subdirectories, * and show results in graphical user interface (GUI). *Executing suites
* ** Each
* * *-sargument must be followed by one and only one fully qualified class name. The class must either extendSuiteand * have a public, no-arg constructor, or be annotated by a validWrapWithannotation. *Specifying the config map
* ** A config map contains pairs consisting of a string key and a value that may be of any type. (Keys that start with * "org.scalatest." are reserved for ScalaTest. Configuration values that are themselves strings may be specified on the *
* *Runnercommand line. * Each configuration pair is denoted with a "-D", followed immediately by the key string, an "=", and the value string. * For example: *-Ddbname=testdb -Dserver=192.168.1.188* * *Specifying a runpath
* ** A runpath is the list of filenames, directory paths, and/or URLs that
* *Runner* uses to load classes for the running test. If runpath is specified,Runnercreates * a custom class loader to load classes available on the runpath. * The graphical user interface reloads the test classes anew for each run * by creating and using a new instance of the custom class loader for each run. * The classes that comprise the test may also be made available on * the classpath, in which case no runpath need be specified. ** The runpath is specified with the -R option. The -R must be followed by a space, * a double quote (
* *"), a white-space-separated list of * paths and URLs, and a double quote. If specifying only one element in the runpath, you can leave off * the double quotes, which only serve to combine a white-space separated list of strings into one * command line argument. If you have path elements that themselves have a space in them, you must * place a backslash (\) in front of the space. Here's an example: *-R "serviceuitest-1.1beta4.jar myjini http://myhost:9998/myfile.jar target/class\ files"* * *Specifying reporters
* ** Reporters can be specified on the command line in any of the following * ways: *
* **
* *- *
-g[configs...]- causes display of a graphical user interface that allows * tests to be run and results to be investigated- *
-f[configs...] <filename>- causes test results to be written to * the named file- *
-u <directory>- causes test results to be written to * junit-style xml files in the named directory- *
-h <directory> [-Y <CSS file>]- causes test results to be written to * HTML files in the named directory, optionally included the specified CSS file- *
-a <number of files to archive>- causes specified number of old * summary and durations files to be archived (in summaries/ and durations/ subdirectories) * for dashboard reporter (default is two)- *
-o[configs...]- causes test results to be written to * the standard output- *
-e[configs...]- causes test results to be written to * the standard error- *
-k <host> <port>- causes test results to be written to * socket in the named host and port number, using XML format- *
-K <host> <port>- causes test results to be written to * socket in the named host and port number, using Java object binary format- *
-C[configs...] <reporterclass>- causes test results to be reported to * an instance of the specified fully qualifiedReporterclass name* The
* *[configs...]parameter, which is used to configure reporters, is described in the next section. ** The
* *-Coption causes the reporter specified in *<reporterclass>to be * instantiated. * Each reporter class specified with a -C option must be public, implement *org.scalatest.Reporter, and have a public no-arg constructor. * Reporter classes must be specified with fully qualified names. * The specified reporter classes may be * deployed on the classpath. If a runpath is specified with the *-Roption, the specified reporter classes may also be loaded from the runpath. * All specified reporter classes will be loaded and instantiated via their no-arg constructor. ** For example, to run a suite named
* *MySuitefrom themydirdirectory * using two reporters, the graphical reporter and a file reporter * writing to a file named"test.out", you would type: *java -jar scalatest.jar -R mydir -g -f test.out -s MySuite* ** The
* *-g,-o, or-eoptions can * appear at most once each in any single command line. * Multiple appearances of-fand-Cresult in multiple reporters * unless the specified<filename>or<reporterclass>is * repeated. If any of-g,-o,-e, *<filename>or<reporterclass>are repeated on * the command line, theRunnerwill print an error message and not run the tests. **
* * *Runneradds the reporters specified on the command line to a dispatch reporter, * which will dispatch each method invocation to each contained reporter.Runnerwill pass * the dispatch reporter to executed suites. As a result, every * specified reporter will receive every report generated by the running suite of tests. * If no reporters are specified, a graphical * runner will be displayed that provides a graphical report of * executed suites. *Configuring Reporters
* * ** Each reporter option on the command line can include configuration characters. Configuration characters * are specified immediately following the
* *-g,-o, *-e,-f, or-C. The following configuration * characters, which cause reports to be dropped, are valid for any reporter: **
* *- *
N- dropTestStartingevents- *
C- dropTestSucceededevents- *
X- dropTestIgnoredevents- *
E- dropTestPendingevents- *
H- dropSuiteStartingevents- *
L- dropSuiteCompletedevents- *
O- dropInfoProvidedevents- *
P- dropScopeOpenedevents- *
Q- dropScopeClosedevents- *
R- dropScopePendingevents- *
M- dropMarkupProvidedevents* A dropped event will not be delivered to the reporter at all. So the reporter will not know about it and therefore not * present information about the event in its report. For example, if you specify
* *-oN, the standard output reporter * will never receive anyTestStartingevents and will therefore never report them. The purpose of these * configuration parameters is to allow users to selectively remove events they find add clutter to the report without * providing essential information. ** The following three reporter configuration parameters may additionally be used on standard output (-o), standard error (-e), * and file (-f) reporters: *
* **
* *- *
W- without color- *
D- show all durations- *
S- show short stack traces- *
F- show full stack traces- *
U- unformatted mode- *
I- show reminder of failed and canceled tests without stack traces- *
T- show reminder of failed and canceled tests with short stack traces- *
G- show reminder of failed and canceled tests with full stack traces- *
K- excludeTestCanceledevents from reminder* If you specify a W, D, S, F, U, R, T, G, or K for any reporter other than standard output, standard error, or file reporters,
* *Runner* will complain with an error message and not perform the run. ** Configuring a standard output, error, or file reporter with
* *Dwill cause that reporter to * print a duration for each test and suite. When running in the default mode, a duration will only be printed for * the entire run. ** Configuring a standard output, error, or file reporter with
* *Fwill cause that reporter to print full stack traces for all exceptions, * includingTestFailedExceptions. EveryTestFailedExceptioncontains a stack depth of the * 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, * mode, these reporters will only show full stack traces when other exceptions are thrown, such as an exception thrown * by production code. When aTestFailedExceptionis thrown in default mode, only the source filename and * 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 * trace. ** The 'U' unformatted configuration removes some formatting from the output and adds verbosity. * The purpose of unformatted (or, "ugly") mode is to facilitate debugging of parallel runs. If you have * tests that fail or hang during parallel runs, but succeed when run sequentially, unformatted mode can help. * In unformatted mode, you can see exactly what is happening when it is happening. Rather than attempting to make the output * look as pretty and human-readable as possible, unformatted mode will just print out verbose information about each event * as it arrives, helping you track down the problem * you are trying to debug. *
* ** By default, a standard output, error, or file reporter inserts ansi escape codes into the output printed to change and later reset * terminal colors. Information printed as a result of run starting, completed, and stopped events * is printed in cyan. Information printed as a result of ignored or pending test events is shown in yellow. Information printed * as a result of test failed, suite aborted, or run aborted events is printed in red. All other information is printed in green. * The purpose of these colors is to facilitate speedy reading of the output, especially the finding of failed tests, which can * get lost in a sea of passing tests. Configuring a standard output, error, or file reporter into without-color mode (
* *W) will * turn off this behavior. No ansi codes will be inserted. ** The
* *R,T, andGoptions enable "reminders" of failed and, optionally, canceled tests to be printed * at the end of the summary. This minimizes or eliminates the need to search and scroll backwards to find out what tests failed or were canceled. * For large test suites, the actual failure message could have scrolled off the top of the buffer, making it otherwise impossible * to see what failed. You can configure the detail level of the stack trace for regular reports of failed and canceled tests independently * from that of reminders. To set the detail level for regular reports, useSfor short stack traces,Ffor * full stack traces, or nothing for the default of no stack trace. To set the detail level for reminder reports, useTfor * reminders with short stack traces,Gfor reminders with full stack traces in reminders, orRfor reminders * with no stack traces. If you wish to exclude reminders of canceled tests, i.e., only see reminders of failed tests, specify *Kalong with one ofR,T, orG, as in"-oRK". ** For example, to run a suite using two reporters, the graphical reporter configured to present every reported event * and a standard error reporter configured to present everything but test starting, test succeeded, test ignored, test * pending, suite starting, suite completed, and info provided events, you would type: *
* **
* *scala -classpath scalatest-<version>.jar -R mydir -g -eNDXEHLO -s MySuite** Note that no white space is allowed between the reporter option and the initial configuration * parameters. So
* * *"-e NDXEHLO"will not work, *"-eNDXEHLO"will work. *Specifying tags to include and exclude
* * ** You can specify tag names of tests to include or exclude from a run. To specify tags to include, * use
* *-nfollowed by a white-space-separated list of tag names to include, surrounded by * double quotes. (The double quotes are not needed if specifying just one tag.) Similarly, to specify tags * to exclude, use-lfollowed by a white-space-separated * list of tag names to exclude, surrounded by double quotes. (As before, the double quotes are not needed * if specifying just one tag.) If tags to include is not specified, then all tests * except those mentioned in the tags to exclude (and in theorg.scalatest.Ignoretag), will be executed. * (In other words, the absence of a-noption is like a wildcard, indicating all tests be included.) * If tags to include is specified, then only those tests whose tags are mentioned in the argument following-n* and not mentioned in the tags to exclude, will be executed. For more information on test tags, see * the documentation forSuite. Here are some examples: **
*
* * * *- *
-n CheckinTests- *
-n FunctionalTests -l org.scalatest.tags.Slow- *
-n "CheckinTests FunctionalTests" -l "org.scalatest.tags.Slow org.scalatest.tags.Network"Specifying suffixes to discover
* * ** You can specify suffixes of
* *Suitenames to discover. To specify suffixes to discover, * use-qfollowed by a vertical-bar-separated list of suffixes to discover, surrounded by * double quotes. (The double quotes are not needed if specifying just one suffix.) Or you can specify * them individually using multiple -q's. * If suffixes to discover is not specified, then all suffixes are considered. * If suffixes is specified, then only those Suites whose class names end in one of the specified suffixes * will be considered during discovery. Here are some examples: **
*
* * *- *
-q Spec- *
-q "Spec|Suite"- *
-q Spec -q Suite* Option -Q can be used to specify a default set of suffixes "Spec|Suite". If you specify both -Q and -q, you'll get Spec * and Suite in addition to the other suffix or suffixes you specify with -q. *
* ** Specifying suffixes can speed up the discovery process because class files with names not ending the specified suffixes * can be immediately disqualified, without needing to load and inspect them to see if they either extend
* * *Suite* and declare a public, no-arg constructor, or are annotated withWrapWith. *Executing
* * *Suites in parallel* With the proliferation of multi-core architectures, and the often parallelizable nature of tests, it is useful to be able to run * tests in parallel. If you include
* *-Pon the command line,Runnerwill pass aDistributorto * theSuites you specify with-s.Runnerwill set up a thread pool to execute anySuites * passed to theDistributor'sputmethod in parallel. TraitSuite's implementation of *runNestedSuiteswill place any nestedSuites into thisDistributor. Thus, if you have aSuite* of tests that must be executed sequentially, you should overriderunNestedSuitesas described in the documentation forDistributor. ** The
* * *-Poption may optionally be appended with a number (e.g. * "-P10" -- no intervening space) to specify the number of * threads to be created in the thread pool. If no number (or 0) is * specified, the number of threads will be decided based on the number of * processors available. *Specifying
* * *Suites* Suites are specified on the command line with a -s followed by the fully qualified * name of a
* *Suitesubclass, as in: *-s com.artima.serviceuitest.ServiceUITestkit* ** Each specified suite class must be public, a subclass of *
* *org.scalatest.Suite, and contain a public no-arg constructor. *Suiteclasses must be specified with fully qualified names. * The specifiedSuiteclasses may be * loaded from the classpath. If a runpath is specified with the *-Roption, specifiedSuiteclasses may also be loaded from the runpath. * All specifiedSuiteclasses will be loaded and instantiated via their no-arg constructor. ** The runner will invoke
* *executeon each instantiatedorg.scalatest.Suite, * passing in the dispatch reporter to eachexecutemethod. **
* * *Runneris intended to be used from the command line. It is included inorg.scalatest* package as a convenience for the user. If this package is incorporated into tools, such as IDEs, which take * over the role of runner, objectorg.scalatest.tools.Runnermay be excluded from that implementation of the package. * All other public types declared in packageorg.scalatest.tools.Runnershould be included in any such usage, however, * so client software can count on them being available. *Specifying "members-only" and "wildcard"
* * *Suitepaths* If you specify
* *Suitepath names with-mor-w,Runnerwill automatically * discover and execute accessibleSuites in the runpath that are either a member of (in the case of-m) * or enclosed by (in the case of-w) the specified path. As used in this context, a path is a portion of a fully qualified name. * For example, the fully qualifed namecom.example.webapp.MySuitecontains pathscom,com.example, andcom.example.webapp. * The fully qualifed namecom.example.webapp.MyObject.NestedSuitecontains pathscom,com.example, *com.example.webapp, andcom.example.webapp.MyObject. * An accessibleSuiteis a public class that extendsorg.scalatest.Suite* and defines a public no-arg constructor. Note thatSuites defined inside classes and traits do not have no-arg constructors, * and therefore won't be discovered.Suites defined inside singleton objects, however, do get a no-arg constructor by default, thus * they can be discovered. ** For example, if you specify
* *-m com.example.webapp* on the command line, and you've placedcom.example.webapp.RedSuiteandcom.example.webapp.BlueSuite* on the runpath, thenRunnerwill instantiate and execute both of thoseSuites. The difference * between-mand-wis that for-m, onlySuites that are direct members of the named path * will be discovered. For-w, anySuites whose fully qualified * name begins with the specified path will be discovered. Thus, ifcom.example.webapp.controllers.GreenSuite* exists on the runpath, invokingRunnerwith-w com.example.webappwill causeGreenSuite* to be discovered, because its fully qualifed name begins with"com.example.webapp". But if you invokeRunner* with-m com.example.webapp,GreenSuitewill not be discovered because it is directly * a member ofcom.example.webapp.controllers, notcom.example.webapp. ** If you specify no
* * *-s,-m, or-warguments on the command line toRunner, it will discover and execute all accessibleSuites * in the runpath. *Specifying chosen styles
* * ** You can optionally specify chosen styles for a ScalaTest run. ScalaTest supports different styles of * testing so that different teams can use the style or styles that best suits their situation and culture. But * in any one project, it is recommended you decide on one main style for unit testing, and * consistently use only that style for unit testing throughout the project. If you also have integration * tests in your project, you may wish to pick a different style for them than you are using for unit testing. * You may want to allow certain styles to be used in special testing situations on a project, but in general, * it is best to minimize the styles used in any given project to a few, or one. *
* ** To facilitate the communication and enforcement of a team's style choices for a project, you can * specify the chosen styles in your project build. If chosen styles is defined, ScalaTest style traits that are * not among the chosen list will abort with a message complaining that the style trait is not one of the * chosen styles. The style name for each ScalaTest style trait is its fully qualified name. For example, * to specify that
* *org.scalatest.FunSpecas your chosen style you'd pass this to *Runner: *-y org.scalatest.FunSpec* ** If you wanted
* *org.scalatest.FunSpecas your main unit testing style, but also wanted to * allowPropSpecfor test matrixes andFeatureSpecfor * integration tests, you would write: *-y org.scalatest.FunSpec -y org.scalatest.PropSpec -y org.scalatest.FeatureSpec* ** To select
* *org.scalatest.FlatSpecas your main unit testing style, but allow *org.scalatest.fixture.FlatSpecfor multi-threaded unit tests, you'd write: *-y org.scalatest.FlatSpec -y org.scalatest.fixture.FlatSpec* ** The style name for a suite is obtained by invoking its
* *styleNamemethod. Custom style * traits can override this method so that a custom style can participate in the chosen styles list. ** Because ScalaTest is so customizable, a determined programmer could circumvent * the chosen styles check, but in practice
* * *-yshould be persuasive enough tool * to keep most team members in line. *Selecting suites and tests
* **
Runneraccepts three arguments that facilitate selecting suites and tests:-i,-t, and-ioption enables a suite to be selected by suite ID. This argument is intended to allow tools such as IDEs or build tools to * rerun specific tests or suites from information included in the results of a previous run. A-imust follow a-s* that specifies a class with a public, no-arg constructor. The-iparameter can be used, for example, to rerun a nested suite that * declares no zero-arg constructor, which was created by containing suite that does declare a no-arg constructor. In this case,-swould be * used to specify the class ScalaTest can instantiate directly, the containing suite that has a public, no-arg constructor, and-iwould be * used to select the desired nested suite. One important use case for-iis to enable such a nested suite that aborted during the previous run * to be rerun. * * ** The
* *-targument allows a test to be selected by its (complete) test name. Like-i, the-targument is primarily intented * to be used by tools such as IDEs or build tools, to rerun selected tests based on information obtained from the results of a previous run. * For example,-tcould be used to rerun a test that failed in the previous run. * The-targument can be used directly by users, but because descriptive test names are usually rather long, the-zargument (described next), will * usually be a more practical choice for users. A-tmust follow either-sor-i, which identifies the suite * to which the test belongs. ** The
* * *-zoption allows tests to be selected by a simplified wildcard: any test whose name includes the substring specified after-z* will be selected. For example,-z poppedwould select tests named"An empty stack should complain when popped"and"A non-empty stack * should return the last-pushed value when popped, but not"An empty stack should be empty". In short,-z poppedwould select any * tests whose name includes the substring"popped", and not select any tests whose names don't include"popped". This simplified * approach to test name wildcards, which was suggested by Mathias Doenitz, works around the difficulty of finding an actual wildcard character that will work * reliably on different operating systems. *Specifying a span scale factor
* ** If you specify a integer or floating point span scale factor with
* * *-F, traitScaledTimeSpans* trait will return the specified value from its implementation ofspanScaleFactor. This allows you to tune the "patience" of a run (how long to wait * for asynchronous operations) from the command line. For more information, see the documentation for traitScaledTimeSpans. *Specifying TestNG XML config file paths
* ** If you specify one or more file paths with
* * *-b(b for Beust, the last name of TestNG's creator),Runnerwill create aorg.scalatest.testng.TestNGWrapperSuite, * passing in aListof the specified paths. When executed, theTestNGWrapperSuitewill create oneTestNGinstance * and pass each specified file path to it for running. If you include-barguments, you must include TestNG's jar file on the class path or runpath. * The-bargument will enable you to run existingTestNGtests, including tests written in Java, as part of a ScalaTest run. * You need not use-bto run suites written in Scala that extendTestNGSuite. You can simply run such suites with *-s,-m, or -w parameters. *Specifying JUnit tests
* ** JUnit tests, including ones written in Java, may be run by specifying *
*-j classname, where the classname is a valid JUnit class * such as a TestCase, TestSuite, or a class implementing a static suite() * method returning a TestSuite.* To use this option you must include a JUnit jar file on your classpath. *
* * @author Bill Venners * @author George Berger * @author Josh Cough */ object Runner { private val RUNNER_JFRAME_START_X: Int = 150 private val RUNNER_JFRAME_START_Y: Int = 100 private[scalatest] val SELECTED_TAG = "org.scalatest.Selected" @volatile private[scalatest] var spanScaleFactor: Double = 1.0 private final val DefaultNumFilesToArchive = 2 @volatile private[scalatest] var testSortingReporterTimeout = Span(2, Seconds) // TO // We always include a PassFailReporter on runs in order to determine // whether or not all tests passed. // // The thread that calls Runner.run() will either start a GUI, if a graphic // reporter was requested, or just run the tests itself. If a GUI is started, // an event handler thread will get going, and it will start a RunnerThread, // which will actually do the running. The GUI can repeatedly start RunnerThreads // and RerunnerThreads, until the GUI is closed. If -P is specified, that means // the tests should be run concurrently, which in turn means a Distributor will // be passed to the execute method of the Suites, which will in turn populate // it with its nested suites instead of executing them directly in the same // thread. The Distributor works in conjunction with a pool of threads that // will take suites out of the distributor queue and execute them. The DispatchReporter // will serialize all reports via an actor, which because that actor uses receive // not react, will have its own thread. So the DispatchReporter actor's thread will // be the one that actually invokes TestFailed, RunAborted, etc., on this PassFailReporter. // The thread that invoked Runner.run(), will be the one that calls allTestsPassed. // // The thread that invoked Runner.run() will be the one to instantiate the PassFailReporter // and in its primary constructor acquire the single semaphore permit. This permit will // only be released by the DispatchReporter's actor thread when a runAborted, runStopped, // or runCompleted is invoked. allTestsPassed will block until it can reacquire the lone // semaphore permit. Thus, a PassFailReporter can just be used for one run, then it is // spent. A new PassFailReporter is therefore created each time the Runner.run() method is invoked. // private class PassFailReporter extends Reporter { @volatile private var failedAbortedOrStopped = false private val runDoneSemaphore = new Semaphore(1) runDoneSemaphore.acquire() override def apply(event: Event) { event match { case _: TestFailed => failedAbortedOrStopped = true case _: RunAborted => failedAbortedOrStopped = true runDoneSemaphore.release() case _: SuiteAborted => failedAbortedOrStopped = true case _: RunStopped => failedAbortedOrStopped = true runDoneSemaphore.release() case _: RunCompleted => runDoneSemaphore.release() case _ => } } def allTestsPassed = { runDoneSemaphore.acquire() !failedAbortedOrStopped } } // TODO: I don't think I'm enforcing that properties can't start with "org.scalatest" // TODO: I don't think I'm handling rejecting multiple -f/-C with the same arg. -f fred.txt -f fred.txt should // fail, as should -C MyReporter -C MyReporter. I'm failing on -o -o, -g -g, and -e -e, but the error messages // could indeed be nicer. /** * Runs a suite of tests, with optional GUI. See the main documentation for this singleton object for the details. */ def main(args: Array[String]) { // println("FOR DEBUGGING, THESE ARE THE ARGS PASSED TO main(): " + args.mkString(" ")) val result = if (args.contains("-v") || args.contains("--version")) { val version = Resources("AppVersion") val scalaVersion = Resources("ScalaVersion") println("ScalaTest " + version + " (Built for Scala " + scalaVersion + ")") runOptionallyWithPassFailReporter(args.filter(arg => arg != "-v" && arg != "--version"), true) } else runOptionallyWithPassFailReporter(args, true) if (result) System.exit(0) else System.exit(1) } /** * Runs a suite of tests, with optional GUI. See the main documentation for this singleton object for the details. * The difference between this method andmainis simply that this method will block until the run * has completed, aborted, or been stopped, and returntrueif all tests executed and passed. In other * words, if any test fails, or if any suite aborts, or if the run aborts or is stopped, this method will * returnfalse. This value is used, for example, by the ScalaTest ant task to determine whether * to continue the build ifhaltOnFailureis set totrue. * * @return true if all tests were executed and passed. */ def run(args: Array[String]): Boolean = { // println("FOR DEBUGGING, THESE ARE THE ARGS PASSED TO run(): " + args.mkString(" ")) runOptionallyWithPassFailReporter(args, true) } private[scalatest] def parseFriendlyParams(friendlyArgs:String): Array[String] = { parseFriendlyParams(friendlyArgs.split(" ")) } private[scalatest] def parseFriendlyParams(friendlyArgs:Array[String]): Array[String] = { val (propsList, includesList, excludesList, repoArgsList, concurrentList, memberOnlyList, wildcardList, suiteList, junitList, testngList) = new FriendlyParamsTranslator().parsePropsAndTags(friendlyArgs) val arrayBuffer = new ArrayBuffer[String]() arrayBuffer ++= propsList ::: includesList ::: excludesList ::: repoArgsList ::: concurrentList ::: memberOnlyList ::: wildcardList ::: suiteList ::: junitList ::: testngList arrayBuffer.toArray } private def runOptionallyWithPassFailReporter(args: Array[String], runWithPassFailReporter: Boolean): Boolean = { checkArgsForValidity(args) match { case Some(s) => { println(s) System.exit(1) // TODO: Shouldn't this be returning false? } case None => } val ParsedArgs( runpathArgs, reporterArgs, suiteArgs, junitArgs, propertiesArgs, tagsToIncludeArgs, tagsToExcludeArgs, concurrentArgs, membersOnlyArgs, wildcardArgs, testNGArgs, suffixes, chosenStyles, spanScaleFactors, testSortingReporterTimeouts ) = parseArgs(args) val fullReporterConfigurations: ReporterConfigurations = if (reporterArgs.isEmpty) // If no reporters specified, just give them a graphic reporter new ReporterConfigurations(Some(GraphicReporterConfiguration(Set())), Nil, Nil, Nil, Nil, None, None, Nil, Nil, Nil, Nil) else parseReporterArgsIntoConfigurations(reporterArgs) val suitesList: List[SuiteParam] = parseSuiteArgsIntoSuiteParam(suiteArgs, "-s") val junitsList: List[String] = parseSuiteArgsIntoNameStrings(junitArgs, "-j") val runpathList: List[String] = parseRunpathArgIntoList(runpathArgs) val propertiesMap: ConfigMap = parsePropertiesArgsIntoMap(propertiesArgs) val tagsToInclude: Set[String] = parseCompoundArgIntoSet(tagsToIncludeArgs, "-n") val tagsToExclude: Set[String] = parseCompoundArgIntoSet(tagsToExcludeArgs, "-l") val concurrent: Boolean = !concurrentArgs.isEmpty val concurrentConfig: ConcurrentConfig = parseConcurrentConfig(concurrentArgs) val membersOnlyList: List[String] = parseSuiteArgsIntoNameStrings(membersOnlyArgs, "-m") val wildcardList: List[String] = parseSuiteArgsIntoNameStrings(wildcardArgs, "-w") val testNGList: List[String] = parseSuiteArgsIntoNameStrings(testNGArgs, "-b") val chosenStyleSet: Set[String] = parseChosenStylesIntoChosenStyleSet(chosenStyles, "-y") spanScaleFactor = parseDoubleArgument(spanScaleFactors, "-F", 1.0) testSortingReporterTimeout = Span(parseDoubleArgument(testSortingReporterTimeouts, "-T", 2.0), Seconds) // If there's a graphic reporter, we need to leave it out of // reporterSpecs, because we want to pass all reporterSpecs except // the graphic reporter's to the RunnerJFrame (because RunnerJFrame *is* // the graphic reporter). val reporterConfigs: ReporterConfigurations = fullReporterConfigurations.graphicReporterConfiguration match { case None => fullReporterConfigurations case Some(grs) => { new ReporterConfigurations( None, fullReporterConfigurations.fileReporterConfigurationList, fullReporterConfigurations.junitXmlReporterConfigurationList, fullReporterConfigurations.dashboardReporterConfigurationList, fullReporterConfigurations.xmlReporterConfigurationList, fullReporterConfigurations.standardOutReporterConfiguration, fullReporterConfigurations.standardErrReporterConfiguration, fullReporterConfigurations.htmlReporterConfigurationList, fullReporterConfigurations.customReporterConfigurationList, fullReporterConfigurations.xmlSocketReporterConfigurationList, fullReporterConfigurations.socketReporterConfigurationList ) } } val passFailReporter = if (runWithPassFailReporter) Some(new PassFailReporter) else None if (propertiesMap.isDefinedAt("org.scalatest.ChosenStyles")) throw new IllegalArgumentException("Property name 'org.scalatest.ChosenStyles' is used by ScalaTest, please choose other property name.") val configMap: ConfigMap = if (chosenStyleSet.isEmpty) propertiesMap else propertiesMap + ("org.scalatest.ChosenStyles" -> chosenStyleSet) fullReporterConfigurations.graphicReporterConfiguration match { case Some(GraphicReporterConfiguration(configSet)) => { val graphicEventsToPresent: Set[EventToPresent] = EventToPresent.allEventsToPresent filter (if (configSet.contains(FilterTestStarting)) {_ != PresentTestStarting} else etp => true) filter (if (configSet.contains(FilterTestSucceeded)) {_ != PresentTestSucceeded} else etp => true) filter (if (configSet.contains(FilterTestIgnored)) {_ != PresentTestIgnored} else etp => true) filter (if (configSet.contains(FilterTestPending)) {_ != PresentTestPending} else etp => true) filter (if (configSet.contains(FilterScopeOpened)) {_ != PresentScopeOpened} else etp => true) filter (if (configSet.contains(FilterScopeClosed)) {_ != PresentScopeClosed} else etp => true) filter (if (configSet.contains(FilterScopePending)) {_ != PresentScopePending} else etp => true) filter (if (configSet.contains(FilterSuiteStarting)) {_ != PresentSuiteStarting} else etp => true) filter (if (configSet.contains(FilterSuiteCompleted)) {_ != PresentSuiteCompleted} else etp => true) filter (if (configSet.contains(FilterInfoProvided)) {_ != PresentInfoProvided} else etp => true) filter (if (configSet.contains(FilterMarkupProvided)) {_ != PresentMarkupProvided} else etp => true) val abq = new ArrayBlockingQueue[RunnerJFrame](1) usingEventDispatchThread { val rjf = new RunnerJFrame( graphicEventsToPresent, reporterConfigs, suitesList, junitsList, runpathList, tagsToInclude, tagsToExclude, configMap, concurrent, membersOnlyList, wildcardList, testNGList, passFailReporter, concurrentConfig, suffixes, chosenStyleSet ) rjf.setLocation(RUNNER_JFRAME_START_X, RUNNER_JFRAME_START_Y) rjf.setVisible(true) rjf.prepUIForRunning() rjf.runFromGUI() abq.put(rjf) } // To get the Ant task to work, the main thread needs to block until // The GUI window exits. val rjf = abq.take() rjf.blockUntilWindowClosed() } case None => { // Run the test without a GUI withClassLoaderAndDispatchReporter(runpathList, reporterConfigs, None, passFailReporter) { (loader, dispatchReporter) => { doRunRunRunDaDoRunRun( dispatchReporter, suitesList, junitsList, Stopper.default, tagsToInclude, tagsToExclude, configMap, concurrent, membersOnlyList, wildcardList, testNGList, runpathList, loader, new RunDoneListener {}, 1, concurrentConfig, suffixes, chosenStyleSet ) } } } } passFailReporter match { case Some(pfr) => pfr.allTestsPassed case None => false } } // Returns an Option[String]. Some is an error message. None means no error. private[scalatest] def checkArgsForValidity(args: Array[String]) = { val lb = new ListBuffer[String] val it = args.iterator.buffered while (it.hasNext) { val s = it.next if ( s.startsWith("-p") || s.startsWith("-R") || s.startsWith("-f") || s.startsWith("-u") || s.startsWith("-d") || s.startsWith("-a") || s.startsWith("-r") || s.startsWith("-C") || s.startsWith("-n") || /* s.startsWith("-x") || */ s.startsWith("-l") || s.startsWith("-s") || s.startsWith("-i") || s.startsWith("-j") || s.startsWith("-m") || s.startsWith("-w") || s.startsWith("-b") || s.startsWith("-y") || s.startsWith("-t") || s.startsWith("-z") || s.startsWith("-q") || s.startsWith("-Q") || s.startsWith("-F") || s.startsWith("-T") ) { if (it.hasNext) it.next } else if (s.startsWith("-k")) { if (it.hasNext) it.next if (it.hasNext) it.next } else if (s.startsWith("-K")) { if (it.hasNext) it.next if (it.hasNext) it.next } else if (s.startsWith("-h")) { if (it.hasNext) it.next if (it.hasNext && it.head == "-Y") { it.next it.next } } else if (!s.startsWith("-D") && !s.startsWith("-g") && !s.startsWith("-o") && !s.startsWith("-e") && !s.startsWith("-c") && !s.startsWith("-P")) { lb += s } } val argsList = lb.toList if (argsList.length != 0) Some("Unrecognized argument" + (if (argsList.isEmpty) ": " else "s: ") + argsList.mkString("", ", ", ".")) else None } // // Examines concurrent option arg to see if it contains an optional numeric // value representing the number of threads to use, e.g. -P10 for 10 threads. // // It also examines for the 'S' argument, e.g. -PS or -PS10, which when specified, // will enable the SuiteSortingReporter. // // It's possible for user to specify the -P option multiple times on the // command line, although it isn't particularly useful. This method scans // through multiples until it finds one with a number appended and uses // that. If none have a number it just returns 0. If anyone of the -P comes // with the 'S' option, SuiteSortingReporter will be enabled. // private[scalatest] def parseConcurrentConfig(concurrentList: List[String]): ConcurrentConfig = { val threadOpt = concurrentList.find(s => s.matches("-c\\d+") || s.matches("-cS\\d+")) val numThreads = threadOpt match { case Some(arg) => if (arg.startsWith("-cS")) arg.substring(3).toInt else arg.substring(2).toInt case None => 0 } val enableSuiteSortingReporter = concurrentList.find(_.startsWith("-cS")).isDefined ConcurrentConfig(numThreads, enableSuiteSortingReporter) } // // Generates a Pattern based on suffixes passed in by user. Pattern // matches class names that end with one of the specified suffixes. // private def genSuffixesPattern(suffixesList: List[String]): Option[Pattern] = { if (suffixesList.isEmpty) None else Some(Pattern.compile(".*(" + suffixesList.mkString("|") + ")$")) } private[scalatest] def parseArgs(args: Array[String]): ParsedArgs = { val runpath = new ListBuffer[String]() val reporters = new ListBuffer[String]() val suites = new ListBuffer[String]() val junits = new ListBuffer[String]() val props = new ListBuffer[String]() val includes = new ListBuffer[String]() val excludes = new ListBuffer[String]() val concurrent = new ListBuffer[String]() val membersOnly = new ListBuffer[String]() val wildcard = new ListBuffer[String]() val testNGXMLFiles = new ListBuffer[String]() val suffixes = new ListBuffer[String]() val chosenStyles = new ListBuffer[String]() val spanScaleFactor = new ListBuffer[String]() val testSortingReporterTimeout = new ListBuffer[String]() val it = args.iterator.buffered while (it.hasNext) { val s = it.next if (s.startsWith("-D")) { props += s } else if (s.startsWith("-p")) { println("WARNING: -p has been deprecated and will be reused for a different (but still very cool) purpose in ScalaTest 2.0. Please change all uses of -p to -R.") runpath += s if (it.hasNext) runpath += it.next } else if (s.startsWith("-R")) { runpath += "-p" + s.substring(2) if (it.hasNext) runpath += it.next } else if (s.startsWith("-g")) { reporters += s } else if (s.startsWith("-o")) { reporters += s } else if (s.startsWith("-e")) { reporters += s } else if (s.startsWith("-f")) { reporters += s if (it.hasNext) reporters += it.next } else if (s.startsWith("-u")) { reporters += s if (it.hasNext) reporters += it.next } else if (s.startsWith("-d")) { reporters += s if (it.hasNext) reporters += it.next } else if (s.startsWith("-a")) { reporters += s if (it.hasNext) reporters += it.next } else if (s.startsWith("-x")) { reporters += s if (it.hasNext) reporters += it.next } else if (s.startsWith("-h")) { reporters += s if (it.hasNext) reporters += it.next if (it.hasNext && it.head == "-Y") { reporters += it.next if (it.hasNext) reporters += it.next } } else if (s.startsWith("-n")) { includes += s if (it.hasNext) includes += it.next } else if (s.startsWith("-l")) { excludes += s if (it.hasNext) excludes += it.next } else if (s.startsWith("-r")) { println("WARNING: -r has been deprecated and will be reused for a different (but still very cool) purpose in ScalaTest 2.0. Please change all uses of -r to -C.") reporters += s if (it.hasNext) reporters += it.next } else if (s.startsWith("-C")) { reporters += "-r" + s.substring(2) if (it.hasNext) reporters += it.next } else if (s.startsWith("-s")) { suites += s if (it.hasNext) suites += it.next } else if (s.startsWith("-i")) { suites += s if (it.hasNext) suites += it.next } else if (s.startsWith("-t")) { suites += s if (it.hasNext) suites += it.next } else if (s.startsWith("-z")) { suites += s if (it.hasNext) suites += it.next } else if (s.startsWith("-j")) { junits += s if (it.hasNext) junits += it.next } else if (s.startsWith("-m")) { membersOnly += s if (it.hasNext) membersOnly += it.next } else if (s.startsWith("-w")) { wildcard += s if (it.hasNext) wildcard += it.next } else if (s.startsWith("-c")) { println("WARNING: -c has been deprecated and will be reused for a different (but still very cool) purpose in ScalaTest 2.0. Please change all uses of -c to -P.") concurrent += s } else if (s.startsWith("-P")) { concurrent += "-c" + s.substring(2) } else if (s.startsWith("-b")) { testNGXMLFiles += s if (it.hasNext) testNGXMLFiles += it.next } else if (s.startsWith("-q")) { if (it.hasNext) suffixes += it.next() } else if (s.startsWith("-Q")) { suffixes += "Spec|Suite" } else if (s.startsWith("-k")) { reporters += s if (it.hasNext && !it.head.startsWith("-")) // for host reporters += it.next if (it.hasNext && !it.head.startsWith("-")) // for port reporters += it.next } else if (s.startsWith("-K")) { reporters += s if (it.hasNext && !it.head.startsWith("-")) // for host reporters += it.next if (it.hasNext && !it.head.startsWith("-")) // for port reporters += it.next } else if (s.startsWith("-y")) { chosenStyles += s if (it.hasNext) chosenStyles += it.next() } else if (s.startsWith("-F")) { spanScaleFactor += s if (it.hasNext) spanScaleFactor += it.next() } else if (s.startsWith("-T")) { testSortingReporterTimeout += s if (it.hasNext) testSortingReporterTimeout += it.next } else { throw new IllegalArgumentException("Unrecognized argument: " + s) } } ParsedArgs( runpath.toList, reporters.toList, suites.toList, junits.toList, props.toList, includes.toList, excludes.toList, concurrent.toList, membersOnly.toList, wildcard.toList, testNGXMLFiles.toList, genSuffixesPattern(suffixes.toList), chosenStyles.toList, spanScaleFactor.toList, testSortingReporterTimeout.toList ) } /** * Returns a possibly empty ConfigSet containing configuration * objects specified in the passed reporterArg. Configuration * options are specified immediately following * the reporter option, as in: * * -oFA * * If no configuration options are specified, this method returns an * empty ConfigSet. This method never returns null. */ private def parseConfigSet(reporterArg: String): Set[ReporterConfigParam] = { if (reporterArg == null) throw new NullPointerException("reporterArg was null") if (reporterArg.length < 2) throw new IllegalArgumentException("reporterArg < 2") // The reporterArg passed includes the initial -, as in "-oFI", // so the first config param will be at index 2 val configString = reporterArg.substring(2) val it = configString.iterator var set = Set[ReporterConfigParam]() while (it.hasNext) it.next match { 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.") 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.") //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.") 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.") // case 'S' => // Use for Short Stack Traces 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.") //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.") case 'I' => set += PresentReminderWithoutStackTraces case 'T' => set += PresentReminderWithShortStackTraces case 'G' => set += PresentReminderWithFullStackTraces case 'K' => set += PresentReminderWithoutCanceledTests case 'N' => set += FilterTestStarting case 'C' => set += FilterTestSucceeded case 'X' => set += FilterTestIgnored case 'E' => set += FilterTestPending case 'H' => set += FilterSuiteStarting case 'L' => set += FilterSuiteCompleted case 'O' => set += FilterInfoProvided case 'P' => set += FilterScopeOpened case 'Q' => set += FilterScopeClosed case 'R' => set += FilterScopePending case 'M' => set += FilterMarkupProvided case 'W' => set += PresentWithoutColor case 'F' => set += PresentFullStackTraces case 'S' => set += PresentShortStackTraces case 'D' => set += PresentAllDurations case 'U' => set += PresentUnformatted case c: Char => { // this should be moved to the checker, and just throw an exception here with a debug message. Or allow a MatchError. val msg1 = Resources("invalidConfigOption", String.valueOf(c)) + '\n' val msg2 = Resources("probarg", reporterArg) + '\n' throw new IllegalArgumentException(msg1 + msg2) } } set } private[scalatest] def parseReporterArgsIntoConfigurations(args: List[String]) = { // // Checks to see if any args are smaller than two characters in length. // Allows a one-character arg if it's a directory-name parameter, to // permit use of "." for example. Allows a one-character arg if it's // a number. // def argTooShort(args: List[String]): Boolean = { args match { case Nil => false case "-u" :: directory :: list => argTooShort(list) case "-d" :: directory :: list => argTooShort(list) case "-a" :: number :: list => argTooShort(list) case "-x" :: directory :: list => argTooShort(list) case x :: list => if (x.length < 2) true else argTooShort(list) } } if (args == null) throw new NullPointerException("args was null") if (args.exists(_ == null)) throw new NullPointerException("an arg String was null") if (argTooShort(args)) // TODO: check and print out a user friendly message for this throw new IllegalArgumentException("an arg String was less than 2 in length: " + args) for (dashX <- List("-g", "-o", "-e")) { if (args.toList.count(_.startsWith(dashX)) > 1) // TODO: also check and print a user friendly message for this throw new IllegalArgumentException("Only one " + dashX + " allowed") } // TODO: also check and print a user friendly message for this // again here, i had to skip some things, so I had to use an iterator. val it = args.iterator.buffered while (it.hasNext) it.next.take(2).toString match { case "-g" => case "-o" => case "-e" => case "-f" => if (it.hasNext) it.next // scroll past the filename else throw new IllegalArgumentException("-f needs to be followed by a file name arg: ") case "-u" => if (it.hasNext) { val directoryName = it.next val directory = new File(directoryName) if (!directory.isDirectory) { try { directory.mkdirs() if (!directory.exists) throw new IllegalArgumentException("Unable to create directory: " + directory.getAbsolutePath) } catch { case se: SecurityException => throw new IllegalArgumentException("Unable to create directory: " + directory.getAbsolutePath) } } else if (directory.isFile) throw new IllegalArgumentException(directory.getAbsolutePath + " is a file, directory expected.") } else { throw new IllegalArgumentException("-u needs to be followed by a directory name arg: ") } case "-d" => if (it.hasNext) { val directory = it.next if (!(new File(directory).isDirectory)) throw new IllegalArgumentException( "arg for -d option is not a directory [" + directory + "]") else {} } else { throw new IllegalArgumentException("-d needs to be followed by a directory name arg: ") } case "-a" => if (it.hasNext) { def isValidInt(text: String): Boolean = try { text.toInt; true } catch { case _: Throwable => false } val number = it.next if (!(isValidInt(number))) throw new IllegalArgumentException( "arg for -a option is not a number [" + number + "]") else {} } else { throw new IllegalArgumentException("-a needs to be followed by a number arg: ") } case "-x" => if (it.hasNext) { val directory = it.next if (!(new File(directory).isDirectory)) throw new IllegalArgumentException( "arg for -x option is not a directory [" + directory + "]") else {} } else { throw new IllegalArgumentException("-x needs to be followed by a directory name arg: ") } case "-h" => if (it.hasNext) { it.next // scroll past the filename if (it.hasNext && it.head == "-Y") { it.next // scroll past the -Y if (it.hasNext) it.next // scroll past the css file name else throw new IllegalArgumentException("-Y needs to be followed by a file name arg: ") } } else throw new IllegalArgumentException("-h needs to be followed by a directory name arg: ") case "-r" => if (it.hasNext) it.next // scroll past the reporter class else throw new IllegalArgumentException("-r needs to be followed by a reporter class name arg: ") case "-k" => if (it.hasNext && !it.head.startsWith("-")) { it.next if (it.hasNext && !it.head.startsWith("-")) { try { it.next.toInt } catch { case _: Throwable => throw new IllegalArgumentException("port number must be an integer") } } else throw new IllegalArgumentException("-k needs to be followed by a host name and port number" ) } else throw new IllegalArgumentException("-k needs to be followed by a host name and port number" ) case "-K" => if (it.hasNext && !it.head.startsWith("-")) { it.next if (it.hasNext && !it.head.startsWith("-")) { try { it.next.toInt } catch { case _: Throwable => throw new IllegalArgumentException("port number must be an integer") } } else throw new IllegalArgumentException("-K needs to be followed by a host name and port number" ) } else throw new IllegalArgumentException("-K needs to be followed by a host name and port number" ) case "-C" => if (it.hasNext) it.next // scroll past the reporter class else throw new IllegalArgumentException("-C needs to be followed by a reporter class name arg: ") case arg: String => throw new IllegalArgumentException("An arg started with an invalid character string: " + arg) } val graphicReporterConfigurationOption = args.find(arg => arg.startsWith("-g")) match { case Some(dashGString) => val configSet = parseConfigSet(dashGString) if (configSet.contains(PresentShortStackTraces)) throw new IllegalArgumentException("Cannot specify an S (present short stack traces) configuration parameter for the graphic reporter (because it shows them anyway): " + dashGString) if (configSet.contains(PresentFullStackTraces)) throw new IllegalArgumentException("Cannot specify an F (present full stack traces) configuration parameter for the graphic reporter (because it shows them anyway): " + dashGString) if (configSet.contains(PresentWithoutColor)) throw new IllegalArgumentException("Cannot specify a W (present without color) configuration parameter for the graphic reporter: " + dashGString) if (configSet.contains(PresentAllDurations)) throw new IllegalArgumentException("Cannot specify a D (present all durations) configuration parameter for the graphic reporter (because it shows them all anyway): " + dashGString) if (configSet.contains(PresentUnformatted)) throw new IllegalArgumentException("Cannot specify a U (present unformatted) configuration parameter for the graphic reporter: " + dashGString) Some(new GraphicReporterConfiguration(configSet)) case None => None } def buildFileReporterConfigurationList(args: List[String]) = { val it = args.iterator val lb = new ListBuffer[FileReporterConfiguration] while (it.hasNext) { val arg = it.next if (arg.startsWith("-f")) lb += new FileReporterConfiguration(parseConfigSet(arg), it.next) } lb.toList } val fileReporterConfigurationList = buildFileReporterConfigurationList(args) def buildJunitXmlReporterConfigurationList(args: List[String]) = { val it = args.iterator val lb = new ListBuffer[JunitXmlReporterConfiguration] while (it.hasNext) { val arg = it.next if (arg.startsWith("-u")) lb += new JunitXmlReporterConfiguration(Set[ReporterConfigParam](), it.next) } lb.toList } val junitXmlReporterConfigurationList = buildJunitXmlReporterConfigurationList(args) def buildDashboardReporterConfigurationList(args: List[String]) = { def fetchNumFilesArg: Int = { var numFiles: Option[Int] = None val it = args.iterator while (!numFiles.isDefined && it.hasNext) { val arg = it.next if (arg.startsWith("-a")) numFiles = Some(it.next.toInt) } numFiles.getOrElse(DefaultNumFilesToArchive) } val numFilesToArchive = fetchNumFilesArg val it = args.iterator val lb = new ListBuffer[DashboardReporterConfiguration] while (it.hasNext) { val arg = it.next if (arg.startsWith("-d")) lb += new DashboardReporterConfiguration(Set[ReporterConfigParam](), it.next, numFilesToArchive) } lb.toList } val dashboardReporterConfigurationList = buildDashboardReporterConfigurationList(args) def buildXmlReporterConfigurationList(args: List[String]) = { val it = args.iterator val lb = new ListBuffer[XmlReporterConfiguration] while (it.hasNext) { val arg = it.next if (arg.startsWith("-x")) lb += new XmlReporterConfiguration(Set[ReporterConfigParam](), it.next) } lb.toList } val xmlReporterConfigurationList = buildXmlReporterConfigurationList(args) def buildHtmlReporterConfigurationList(args: List[String]): List[HtmlReporterConfiguration] = { if (args.isEmpty) Nil else if (!args.head.startsWith("-h")) buildHtmlReporterConfigurationList(args.tail) else if (args.tail.isEmpty) throw new IllegalArgumentException( "-h cannot be last, expected HTML output directory name to follow.") else { val (configSet, dir, cssFile, remainingArgs) = parseHtmlArgs(args) new HtmlReporterConfiguration(configSet, dir, cssFile) :: buildHtmlReporterConfigurationList(remainingArgs) } } // // Parses one set of -h arguments: configuration settings, html output // directory, and optional cssFile, from an argument list whose first // argument begins with "-h". Returns parsed values plus list of // remaining arguments. // // -h argument list consists of string "-h" with optional configuration // letters appended (none are currently defined), a directory where // output html is to be written, and optionally a "-Y" argument followed // by the path to a custom CSS file to be used. E.g.: // // -hABC target/htmldir -Y src/resources/my.css // def parseHtmlArgs(args: List[String]): (Set[ReporterConfigParam], String, Option[URL], List[String]) = { val configSet = parseConfigSet(args.head) val directory = args(1) val (cssFile, remainingArgs) = if ((args.size > 2) && args(2) == "-Y") { if (args.size < 4) throw new IllegalArgumentException( "-Y cannot be last, expected CSS file name to follow.") else (Some(new File(args(3)).toURI.toURL), args.drop(4)) } else (None, args.drop(2)) (configSet, directory, cssFile, remainingArgs) } val htmlReporterConfigurationList = buildHtmlReporterConfigurationList(args) val standardOutReporterConfigurationOption = args.find(arg => arg.startsWith("-o")) match { case Some(dashOString) => Some(new StandardOutReporterConfiguration(parseConfigSet(dashOString))) case None => None } val standardErrReporterConfigurationOption = args.find(arg => arg.startsWith("-e")) match { case Some(dashEString) => Some(new StandardErrReporterConfiguration(parseConfigSet(dashEString))) case None => None } def buildCustomReporterConfigurationList(args: List[String]) = { val it = args.iterator val lb = new ListBuffer[CustomReporterConfiguration] while (it.hasNext) { val arg = it.next if (arg.startsWith("-r")) { val dashRString = arg val customReporterClassName = it.next val configSet = parseConfigSet(dashRString) if (configSet.contains(PresentShortStackTraces)) throw new IllegalArgumentException("Cannot specify an S (present short stack traces) configuration parameter for a custom reporter: " + dashRString + " " + customReporterClassName) if (configSet.contains(PresentFullStackTraces)) throw new IllegalArgumentException("Cannot specify an F (present full stack traces) configuration parameter for a custom reporter: " + dashRString + " " + customReporterClassName) if (configSet.contains(PresentWithoutColor)) throw new IllegalArgumentException("Cannot specify a W (without color) configuration parameter for a custom reporter: " + dashRString + " " + customReporterClassName) if (configSet.contains(PresentAllDurations)) throw new IllegalArgumentException("Cannot specify a D (present all durations) configuration parameter for a custom reporter: " + dashRString + " " + customReporterClassName) if (configSet.contains(PresentUnformatted)) throw new IllegalArgumentException("Cannot specify a U (present unformatted) configuration parameter for a custom reporter: " + dashRString + " " + customReporterClassName) lb += new CustomReporterConfiguration(configSet, customReporterClassName) } } lb.toList } val customReporterConfigurationList = buildCustomReporterConfigurationList(args) def buildXmlSocketReporterConfigurationList(args: List[String]) = { val it = args.iterator val lb = new ListBuffer[XmlSocketReporterConfiguration] while (it.hasNext) { val arg = it.next if (arg.startsWith("-k")) { if (!it.hasNext) throw new IllegalArgumentException("-k must be followed by host and port") val host = it.next if (!it.hasNext) throw new IllegalArgumentException("-k must be followed by host and port") val port = it.next.toInt lb += new XmlSocketReporterConfiguration(host, port) } } lb.toList } val xmlSocketReporterConfigurationList = buildXmlSocketReporterConfigurationList(args) def buildSocketReporterConfigurationList(args: List[String]) = { val it = args.iterator val lb = new ListBuffer[SocketReporterConfiguration] while (it.hasNext) { val arg = it.next if (arg.startsWith("-K")) { if (!it.hasNext) throw new IllegalArgumentException("-K must be followed by host and port") val host = it.next if (!it.hasNext) throw new IllegalArgumentException("-K must be followed by host and port") val port = it.next.toInt lb += new SocketReporterConfiguration(host, port) } } lb.toList } val socketReporterConfigurationList = buildSocketReporterConfigurationList(args) // Here instead of one loop, i go through the loop several times. new ReporterConfigurations( graphicReporterConfigurationOption, fileReporterConfigurationList, junitXmlReporterConfigurationList, dashboardReporterConfigurationList, xmlReporterConfigurationList, standardOutReporterConfigurationOption, standardErrReporterConfigurationOption, htmlReporterConfigurationList, customReporterConfigurationList, xmlSocketReporterConfigurationList, socketReporterConfigurationList ) } // Used to parse -s, -j, -m, and -w args, one of which will be passed as a String as dashArg private[scalatest] def parseSuiteArgsIntoNameStrings(args: List[String], dashArg: String) = { if (args == null) throw new NullPointerException("args was null") if (args.exists(_ == null)) throw new NullPointerException("an arg String was null") if (dashArg != "-j" && dashArg != "-s" && dashArg != "-w" && dashArg != "-m" && dashArg != "-b") throw new IllegalArgumentException("dashArg invalid: " + dashArg) /* <<<<<<< .working TODOCS: Is the above the correct way to merge these? if (dashArg != "-j" && dashArg != "-w" && dashArg != "-m" && dashArg != "-b") throw new IllegalArgumentException("dashArg invalid: " + dashArg) ======= if (dashArg != "-j" && dashArg != "-s" && dashArg != "-w" && dashArg != "-m" && dashArg != "-b") throw new NullPointerException("dashArg invalid: " + dashArg) >>>>>>> .merge-right.r3653 */ val lb = new ListBuffer[String] val it = args.iterator while (it.hasNext) { val dashS = it.next if (dashS != dashArg) throw new IllegalArgumentException("Every other element, starting with the first, must be " + dashArg) if (it.hasNext) { val suiteName = it.next if (!suiteName.startsWith("-")) lb += suiteName else throw new IllegalArgumentException("Expecting a Suite class name or package name to follow " + dashArg + ", but got: " + suiteName) } else throw new IllegalArgumentException("Last element must be a Suite class name or package name, not a " + dashArg + ".") } lb.toList } private[scalatest] def parseSuiteArgsIntoSuiteParam(args: List[String], dashArg: String) = { if (args == null) throw new NullPointerException("args was null") if (args.exists(_ == null)) throw new NullPointerException("an arg String was null") if (dashArg != "-s") throw new IllegalArgumentException("dashArg invalid: " + dashArg) val lb = new ListBuffer[SuiteParam] val it = args.iterator.buffered while (it.hasNext) { val dashS = it.next if (dashS != dashArg) throw new IllegalArgumentException("Starting element must be " + dashArg) if (it.hasNext) { val className = it.next if (!className.startsWith("-")) { val (testNames, wildcardTestNames) = if (it.hasNext && (it.head == "-t" || it.head == "-z")) { val testNamesBuffer = new ListBuffer[String]() val wildcardTestNamesBuffer = new ListBuffer[String]() while (it.hasNext && (it.head == "-t" || it.head == "-z")) { val dashTest = it.next if (dashTest == "-t") testNamesBuffer += it.next else wildcardTestNamesBuffer += it.next } (testNamesBuffer.toArray, wildcardTestNamesBuffer.toArray) } else (Array.empty[String], Array.empty[String]) val nestedSuites = if (it.hasNext && it.head == "-i") { val nestedLb = new ListBuffer[NestedSuiteParam]() while (it.hasNext && it.head == "-i") { val dashI = it.next() val suiteId = it.next val suiteIdTestNamesBuffer = new ListBuffer[String]() val suiteIdWildcardTestNamesBuffer = new ListBuffer[String]() while (it.hasNext && (it.head == "-t" || it.head == "-z")) { val dashTest = it.next if (dashTest == "-t") suiteIdTestNamesBuffer += it.next else suiteIdWildcardTestNamesBuffer += it.next } nestedLb += new NestedSuiteParam(suiteId, suiteIdTestNamesBuffer.toArray, suiteIdWildcardTestNamesBuffer.toArray) } nestedLb.toArray } else Array.empty[NestedSuiteParam] lb += SuiteParam(className, testNames, wildcardTestNames, nestedSuites) } else throw new IllegalArgumentException("Expecting a Suite class name to follow " + dashArg + ", but got: " + className) } else throw new IllegalArgumentException("Last element must be a Suite class name, not a " + dashArg + ".") } lb.toList } private[scalatest] def parseCompoundArgIntoSet(args: List[String], expectedDashArg: String): Set[String] = Set() ++ parseCompoundArgIntoList(args, expectedDashArg) private[scalatest] def parseRunpathArgIntoList(args: List[String]): List[String] = parseCompoundArgIntoList(args, "-p") private[scalatest] def parseCompoundArgIntoList(args: List[String], expectedDashArg: String): List[String] = { if (args == null) throw new NullPointerException("args was null") if (args.exists(_ == null)) throw new NullPointerException("an arg String was null") if (args.length == 0) { List() } else if (args.length == 2) { val dashArg = args(0) val runpathArg = args(1) if (dashArg != expectedDashArg) throw new IllegalArgumentException("First arg must be " + expectedDashArg + ", but was: " + dashArg) if (runpathArg.trim.isEmpty) throw new IllegalArgumentException("The runpath string must actually include some non-whitespace characters.") splitPath(runpathArg) } else { throw new IllegalArgumentException("Runpath must be either zero or two args: " + args) } } private[scalatest] def parseChosenStylesIntoChosenStyleSet(args: List[String], dashArg: String) = { val it = args.iterator val lb = new ListBuffer[String]() while (it.hasNext) { val dash = it.next if (dash != dashArg) throw new IllegalArgumentException("Every other element, starting with the first, must be " + dashArg) if (it.hasNext) { lb += it.next } else throw new IllegalArgumentException("Last element must be a style name, not a " + dashArg + ".") } lb.toSet } private[scalatest] def parseDoubleArgument(args: List[String], dashArg: String, defaultValue: Double): Double = { val it = args.iterator val lb = new ListBuffer[Double]() while (it.hasNext) { val dash = it.next if (dash != dashArg) throw new IllegalArgumentException("Every other element, starting with the first, must be " + dashArg) if (it.hasNext) { val spanString = it.next try { lb += spanString.toDouble } catch { case e: NumberFormatException => throw new IllegalArgumentException(dashArg + " must be followed by a number, but '" + spanString + "' is not a number.") } } else throw new IllegalArgumentException("Last element must be a number, not a " + dashArg + ".") } if (lb.size == 0) defaultValue else if (lb.size == 1) lb(0) else throw new IllegalArgumentException("Only one " + dashArg + " can be specified.") } // // Splits a space-delimited path into its component parts. // // Spaces within path elements may be escaped with backslashes, e.g. // "c:\Documents\ And\ Settings c:\Program\ Files" // // See comments for isCompleteToken() below for exceptions. // private val START_TOKEN_PATTERN = Pattern.compile("""^\s*(.*?)(\s|$)""") private val FULL_TOKEN_PATTERN = Pattern.compile("""^\s*(.+?)(((?<=[^\\])\s)|$)""") private def splitPath(pathArg: String): List[String] = { val path = pathArg.trim if (path.isEmpty) Nil else { val startMatcher = START_TOKEN_PATTERN.matcher(path) if (!startMatcher.find()) throw new RuntimeException("unexpected startMatcher path [" + path + "]") val token = startMatcher.group(1) if (isCompleteToken(token)) { token :: splitPath(path.substring(startMatcher.end)) } else { val fullMatcher = FULL_TOKEN_PATTERN.matcher(path) if (!fullMatcher.find()) throw new RuntimeException("unexpected fullMatcher path [" + path + "]") val fullToken = fullMatcher.group(1).replaceAll("""\\(\s)""", "$1") fullToken :: splitPath(path.substring(fullMatcher.end)) } } } // // Determines whether specified token is complete or partial. // // Tokens are considered partial if they end with a backslash, since // backslash is used to escape spaces that would otherwise be // treated as delimiters within the path string. // // Exceptions are cases where the token ends in a backslash // but is still considered a complete token because it constitutes // a valid representation of a root directory on a windows system, // e.g. "c:\" or just "\". // private val ROOT_DIR_PATTERN = Pattern.compile("""(?i)\\|[a-z]:\\""") private def isCompleteToken(token: String): Boolean = { val matcher = ROOT_DIR_PATTERN.matcher(token) matcher.matches() || (token(token.length - 1) != '\\') } private[scalatest] def parsePropertiesArgsIntoMap(args: List[String]): ConfigMap = { if (args == null) throw new NullPointerException("args was null") if (args.exists(_ == null)) throw new NullPointerException("an arg String was null") if (args.exists(_.indexOf('=') == -1)) throw new IllegalArgumentException("A -D arg does not contain an equals sign.") if (args.exists(!_.startsWith("-D"))) throw new IllegalArgumentException("A spice arg does not start with -D.") if (args.exists(_.indexOf('=') == 2)) throw new IllegalArgumentException("A spice arg does not have a key to the left of the equals sign.") if (args.exists(arg => arg.indexOf('=') == arg.length - 1)) throw new IllegalArgumentException("A spice arg does not have a value to the right of the equals sign.") val tuples = for (arg <- args) yield { val keyValue = arg.substring(2) // Cut off the -D at the beginning val equalsPos = keyValue.indexOf('=') val key = keyValue.substring(0, equalsPos) val value = keyValue.substring(equalsPos + 1) (key, value) } new ConfigMap(Map(tuples: _*)) } // For debugging. /* private[scalatest] def printOpts(opt: EventToPresent.Set32) { if (opt.contains(EventToPresent.PresentRunStarting)) println("PresentRunStarting") if (opt.contains(EventToPresent.PresentTestStarting)) println("PresentTestStarting") if (opt.contains(EventToPresent.PresentTestSucceeded)) println("PresentTestSucceeded") if (opt.contains(EventToPresent.PresentTestFailed)) println("PresentTestFailed") if (opt.contains(EventToPresent.PresentTestIgnored)) println("PresentTestIgnored") if (opt.contains(EventToPresent.PresentSuiteStarting)) println("PresentSuiteStarting") if (opt.contains(EventToPresent.PresentSuiteCompleted)) println("PresentSuiteCompleted") if (opt.contains(EventToPresent.PresentSuiteAborted)) println("PresentSuiteAborted") if (opt.contains(EventToPresent.PresentInfoProvided)) println("PresentInfoProvided") if (opt.contains(EventToPresent.PresentRunStopped)) println("PresentRunStopped") if (opt.contains(EventToPresent.PresentRunCompleted)) println("PresentRunCompleted") if (opt.contains(EventToPresent.PresentRunAborted)) println("PresentRunAborted") } */ private[scalatest] def mergeMap[A, B](ms: List[Map[A, B]])(f: (B, B) => B): Map[A, B] = (Map[A, B]() /: (for (m <- ms; kv <- m) yield kv)) { (a, kv) => a + (if (a.contains(kv._1)) kv._1 -> f(a(kv._1), kv._2) else kv) } private[scalatest] def doRunRunRunDaDoRunRun( dispatch: DispatchReporter, suitesList: List[SuiteParam], junitsList: List[String], stopRequested: Stopper, tagsToIncludeSet: Set[String], tagsToExcludeSet: Set[String], configMap: ConfigMap, concurrent: Boolean, membersOnlyList: List[String], wildcardList: List[String], testNGList: List[String], runpath: List[String], loader: ClassLoader, doneListener: RunDoneListener, runStamp: Int, concurrentConfig: ConcurrentConfig, suffixes: Option[Pattern], chosenStyleSet: Set[String] ) = { // TODO, either put a type on here or do procedure style if Unit // TODO: add more, and to RunnerThread too if (dispatch == null) throw new NullPointerException if (suitesList == null) throw new NullPointerException if (junitsList == null) throw new NullPointerException if (stopRequested == null) throw new NullPointerException if (tagsToIncludeSet == null) throw new NullPointerException if (tagsToExcludeSet == null) throw new NullPointerException if (configMap == null) throw new NullPointerException if (membersOnlyList == null) throw new NullPointerException if (wildcardList == null) throw new NullPointerException if (testNGList == null) throw new NullPointerException if (runpath == null) throw new NullPointerException if (loader == null) throw new NullPointerException if (doneListener == null) throw new NullPointerException if (chosenStyleSet == null) throw new NullPointerException var tracker = new Tracker(new Ordinal(runStamp)) val runStartTime = System.currentTimeMillis try { val loadProblemsExist = try { val unrunnableList = suitesList.filter{ suiteParam => val className = suiteParam.className loader.loadClass(className) // Check if the class exist, so if not we get the nice cannot load suite error message. !isAccessibleSuite(className, loader) && !isRunnable(className, loader) } if (!unrunnableList.isEmpty) { val names = for (suiteParam <- unrunnableList) yield " " + suiteParam.className dispatch(RunAborted(tracker.nextOrdinal(), Resources("nonSuite") + names.mkString(", "), None)) true } else { false } } catch { case e: ClassNotFoundException => { dispatch(RunAborted(tracker.nextOrdinal(), Resources("cannotLoadSuite", e.getMessage), Some(e))) true } } if (!loadProblemsExist) { case class SuiteConfig(suite: Suite, dynaTags: DynaTags, requireSelectedTag: Boolean, excludeNestedSuites: Boolean) try { val namedSuiteInstances: List[SuiteConfig] = for (suiteParam <- suitesList) yield { val suiteClassName = suiteParam.className val clazz = loader.loadClass(suiteClassName) val wrapWithAnnotation = clazz.getAnnotation(classOf[WrapWith]) val suiteInstance = if (wrapWithAnnotation == null) clazz.newInstance.asInstanceOf[Suite] else { val suiteClazz = wrapWithAnnotation.value val constructorList = suiteClazz.getDeclaredConstructors() val constructor = constructorList.find { c => val types = c.getParameterTypes types.length == 1 && types(0) == classOf[java.lang.Class[_]] } constructor.get.newInstance(clazz).asInstanceOf[Suite] } if (suiteParam.testNames.length == 0 && suiteParam.wildcardTestNames.length == 0 && suiteParam.nestedSuites.length == 0) SuiteConfig(suiteInstance, new DynaTags(Map.empty, Map.empty), false, false) // -s suiteClass, no dynamic tagging required. else { val nestedSuites = suiteParam.nestedSuites val (selectSuiteList, selectTestList) = nestedSuites.partition(ns => ns.testNames.length == 0 || ns.wildcardTestNames.length == 0) val suiteDynaTags: Map[String, Set[String]] = Map() ++ selectSuiteList.map(ns => (ns.suiteId -> Set(SELECTED_TAG))) val suiteExactTestDynaTags: Map[String, Map[String, Set[String]]] = if (suiteParam.testNames.length > 0) Map(suiteInstance.suiteId -> (Map() ++ suiteParam.testNames.map(tn => (tn -> Set(SELECTED_TAG))))) else Map.empty val suiteWildcardTestDynaTags: Map[String, Map[String, Set[String]]] = if (suiteParam.wildcardTestNames.length > 0) { val wildcardTestNames = suiteParam.wildcardTestNames val allTestNames = suiteInstance.testNames val wildcardTestTags = Map() ++ allTestNames.filter(tn => wildcardTestNames.find(wc => tn.contains(wc)).isDefined) .map(tn => (tn -> Set(SELECTED_TAG))) Map(suiteInstance.suiteId -> wildcardTestTags) } else Map.empty def getNestedSuiteSelectedTestNames(nestedSuite: NestedSuiteParam): Array[String] = { if (nestedSuite.wildcardTestNames.length == 0) nestedSuite.testNames else { val wildcardTestNames = nestedSuite.wildcardTestNames val allTestNames = suiteInstance.testNames nestedSuite.testNames ++ allTestNames.filter(tn => wildcardTestNames.find(wc => tn.contains(wc)).isDefined) } } val nestedSuitesTestDynaTags: Map[String, Map[String, Set[String]]] = Map() ++ selectTestList.map(ns => (ns.suiteId -> (Map() ++ getNestedSuiteSelectedTestNames(ns).map(tn => (tn, Set(SELECTED_TAG)))))) val testDynaTags = mergeMap[String, Map[String, Set[String]]](List(suiteExactTestDynaTags, suiteWildcardTestDynaTags, nestedSuitesTestDynaTags)) { (suiteTestMap1, suiteTestMap2) => mergeMap[String, Set[String]](List(suiteTestMap1, suiteTestMap2)) { (tagSet1, tagSet2) => tagSet1 ++ tagSet2 } } // Only exclude nested suites when using -s XXX -t XXXX, or -s XXX -z XXX val excludeNestedSuites = suiteParam.testNames.length > 0 && nestedSuites.length == 0 SuiteConfig(suiteInstance, new DynaTags(suiteDynaTags, testDynaTags), true, excludeNestedSuites) } } val requireSelectedTag = suitesList.find(suiteParam => suiteParam.testNames.length > 0) val emptyDynaTags = DynaTags(Map.empty[String, Set[String]], Map.empty[String, Map[String, Set[String]]]) val junitSuiteInstances: List[SuiteConfig] = for (junitClassName <- junitsList) yield SuiteConfig(new JUnitWrapperSuite(junitClassName, loader), emptyDynaTags, false, true) // JUnit suite should exclude nested suites val testNGWrapperSuiteList: List[SuiteConfig] = if (!testNGList.isEmpty) List(SuiteConfig(new TestNGWrapperSuite(testNGList), emptyDynaTags, false, true)) // TestNG suite should exclude nested suites else Nil val discoveryStartTime = System.currentTimeMillis dispatch(DiscoveryStarting(tracker.nextOrdinal(), configMap)) val (membersOnlySuiteInstances, wildcardSuiteInstances) = { val membersOnlyAndWildcardListsAreEmpty = membersOnlyList.isEmpty && wildcardList.isEmpty // They didn't specify any -m's or -w's on the command line if (membersOnlyAndWildcardListsAreEmpty && (!suitesList.isEmpty || !junitsList.isEmpty || !testNGList.isEmpty)) { (Nil, Nil) // No DiscoverySuites in this case. Just run Suites named with -s or -j or -b } else { val accessibleSuites = discoverSuiteNames(runpath, loader, suffixes) if (membersOnlyAndWildcardListsAreEmpty && suitesList.isEmpty && junitsList.isEmpty && testNGList.isEmpty) { // In this case, they didn't specify any -w, -m, -s, -j or -b on the command line, so the default // is to run any accessible Suites discovered on the runpath (Nil, List(SuiteConfig(new DiscoverySuite("", accessibleSuites, true, loader), emptyDynaTags, false, false))) } else { val membersOnlyInstances = for (membersOnlyName <- membersOnlyList) yield SuiteConfig(new DiscoverySuite(membersOnlyName, accessibleSuites, false, loader), emptyDynaTags, false, false) val wildcardInstances = for (wildcardName <- wildcardList) yield SuiteConfig(new DiscoverySuite(wildcardName, accessibleSuites, true, loader), emptyDynaTags, false, false) (membersOnlyInstances, wildcardInstances) } } } val suiteInstances: List[SuiteConfig] = namedSuiteInstances ::: junitSuiteInstances ::: membersOnlySuiteInstances ::: wildcardSuiteInstances ::: testNGWrapperSuiteList val testCountList = for (suiteConfig <- suiteInstances) yield { val tagsToInclude = if (suiteConfig.requireSelectedTag) tagsToIncludeSet ++ Set(SELECTED_TAG) else tagsToIncludeSet val filter = Filter(if (tagsToInclude.isEmpty) None else Some(tagsToInclude), tagsToExcludeSet, suiteConfig.excludeNestedSuites, suiteConfig.dynaTags) suiteConfig.suite.expectedTestCount(filter) } def sumInts(list: List[Int]): Int = list match { case Nil => 0 case x :: xs => x + sumInts(xs) } val expectedTestCount = sumInts(testCountList) val discoveryDuration = System.currentTimeMillis - discoveryStartTime dispatch(DiscoveryCompleted(tracker.nextOrdinal(), Some(discoveryDuration))) dispatch(RunStarting(tracker.nextOrdinal(), expectedTestCount, configMap)) if (concurrent) { // Because some tests may do IO, will create a pool of 2 times the number of processors reported // by the Runtime's availableProcessors method. val poolSize = if (concurrentConfig.numThreads > 0) concurrentConfig.numThreads else Runtime.getRuntime.availableProcessors * 2 val distributedSuiteSorter = if (concurrentConfig.enableSuiteSortingReporter) Some(new SuiteSortingReporter(dispatch, Span(testSortingReporterTimeout.millisPart + 1000, Millis), System.err)) else None val concurrentDispatch = distributedSuiteSorter match { case Some(dss) => dss case None => dispatch } val execSvc: ExecutorService = Executors.newFixedThreadPool(poolSize) try { val distributor = new ConcurrentDistributor(Args(dispatch, stopRequested, Filter(if (tagsToIncludeSet.isEmpty) None else Some(tagsToIncludeSet), tagsToExcludeSet), configMap, None, tracker, chosenStyleSet), execSvc) if (System.getProperty("org.scalatest.tools.Runner.forever", "false") == "true") { while (true) { for (suiteConfig <- suiteInstances) { val tagsToInclude = if (suiteConfig.requireSelectedTag) tagsToIncludeSet ++ Set(SELECTED_TAG) else tagsToIncludeSet val filter = Filter(if (tagsToInclude.isEmpty) None else Some(tagsToInclude), tagsToExcludeSet, suiteConfig.excludeNestedSuites, suiteConfig.dynaTags) val runArgs = Args(concurrentDispatch, stopRequested, filter, configMap, Some(distributor), tracker.nextTracker, chosenStyleSet, false, None, distributedSuiteSorter) distributor.apply(suiteConfig.suite, runArgs) } distributor.waitUntilDone() } } else { for (suiteConfig <- suiteInstances) { val tagsToInclude = if (suiteConfig.requireSelectedTag) tagsToIncludeSet ++ Set(SELECTED_TAG) else tagsToIncludeSet val filter = Filter(if (tagsToInclude.isEmpty) None else Some(tagsToInclude), tagsToExcludeSet, suiteConfig.excludeNestedSuites, suiteConfig.dynaTags) val runArgs = Args(concurrentDispatch, stopRequested, filter, configMap, Some(distributor), tracker.nextTracker, chosenStyleSet, false, None, distributedSuiteSorter) distributor.apply(suiteConfig.suite, runArgs) } distributor.waitUntilDone() } } finally { execSvc.shutdown() } } else { for (suiteConfig <- suiteInstances) { val tagsToInclude = if (suiteConfig.requireSelectedTag) tagsToIncludeSet ++ Set(SELECTED_TAG) else tagsToIncludeSet val filter = Filter(if (tagsToInclude.isEmpty) None else Some(tagsToInclude), tagsToExcludeSet, suiteConfig.excludeNestedSuites, suiteConfig.dynaTags) val runArgs = Args(dispatch, stopRequested, filter, configMap, None, tracker, chosenStyleSet) val status = new ScalaTestStatefulStatus() val suiteRunner = new SuiteRunner(suiteConfig.suite, runArgs, status) suiteRunner.run() } } val duration = System.currentTimeMillis - runStartTime if (stopRequested()) { dispatch(RunStopped(tracker.nextOrdinal(), Some(duration))) } else { dispatch(RunCompleted(tracker.nextOrdinal(), Some(duration))) } } catch { case e: InstantiationException => dispatch(RunAborted(tracker.nextOrdinal(), Resources("cannotInstantiateSuite", e.getMessage), Some(e), Some(System.currentTimeMillis - runStartTime))) case e: IllegalAccessException => dispatch(RunAborted(tracker.nextOrdinal(), Resources("cannotInstantiateSuite", e.getMessage), Some(e), Some(System.currentTimeMillis - runStartTime))) case e: NoClassDefFoundError => dispatch(RunAborted(tracker.nextOrdinal(), Resources("cannotLoadClass", e.getMessage), Some(e), Some(System.currentTimeMillis - runStartTime))) case e: Throwable => dispatch(RunAborted(tracker.nextOrdinal(), Resources.bigProblems(e), Some(e), Some(System.currentTimeMillis - runStartTime))) } } } finally { dispatch.dispatchDisposeAndWaitUntilDone() doneListener.done() } } private[scalatest] def excludesWithIgnore(excludes: Set[String]) = excludes + "org.scalatest.Ignore" private[scalatest] def withClassLoaderAndDispatchReporter(runpathList: List[String], reporterSpecs: ReporterConfigurations, graphicReporter: Option[Reporter], passFailReporter: Option[Reporter])(f: (ClassLoader, DispatchReporter) => Unit): Unit = { val loader: ClassLoader = getRunpathClassLoader(runpathList) try { Thread.currentThread.setContextClassLoader(loader) try { val dispatchReporter = ReporterFactory.getDispatchReporter(reporterSpecs, graphicReporter, passFailReporter, loader, None) try { f(loader, dispatchReporter) } finally { dispatchReporter.dispatchDisposeAndWaitUntilDone() } } catch { // getDispatchReporter may complete abruptly with an exception, if there is an problem trying to load // or instantiate a custom reporter class. case ex: Throwable => { System.err.println(Resources("bigProblemsMaybeCustomReporter")) ex.printStackTrace(System.err) } } } finally { // eventually call close on the RunpathClassLoader } } private[scalatest] def getRunpathClassLoader(runpathList: List[String]): ClassLoader = { if (runpathList == null) throw new NullPointerException if (runpathList.isEmpty) { classOf[Suite].getClassLoader // Could this be null technically? } else { val urlsList: List[URL] = for (raw <- runpathList) yield { try { new URL(raw) } catch { case murle: MalformedURLException => { // Assume they tried to just pass in a file name val file: File = new File(raw) // file.toURL may throw MalformedURLException too, but for now // just let that propagate up. file.toURI.toURL // If a dir, comes back terminated by a slash } } } // Here is where the Jini preferred class loader stuff went. // Tell the URLConnections to not use caching, so that repeated runs and reruns actually work // on the latest binaries. for (url <- urlsList) { try { url.openConnection.setDefaultUseCaches(false) } catch { case e: IOException => // just ignore these } } new URLClassLoader(urlsList.toArray, classOf[Suite].getClassLoader) } } private[scalatest] def usingEventDispatchThread(f: => Unit): Unit = { SwingUtilities.invokeLater( new Runnable() { def run() { f } } ) } }