org.scalatest.tools.ScalaTestAntTask.scala Maven / Gradle / Ivy
/*
* 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 scala.collection.mutable.ListBuffer
import org.apache.tools.ant.BuildException
import org.apache.tools.ant.Task
import org.apache.tools.ant.types.Path
import org.apache.tools.ant.AntClassLoader
import org.apache.tools.ant.taskdefs.Java
/**
*
* An ant task to run ScalaTest. Instructions on how to specify various
* options are below. See the main documentation for object Runner
class for a description
* of what each of the options does.
*
*
*
* To use the ScalaTest ant task, you must first define it in your ant file using taskdef
.
* Here's an example:
*
*
*
* <path id="scalatest.classpath">
* <pathelement location="${lib}/scalatest.jar"/>
* <pathelement location="${lib}/scala-library.jar"/>
* <-- scala-actors.jar needed only for ScalaTest <= 1.9.1 on Scala >= 2.10.0 -->
* <pathelement location="${lib}/scala-actors.jar"/>
* </path>
*
* <target name="main" depends="dist">
* <taskdef name="scalatest" classname="org.scalatest.tools.ScalaTestAntTask">
* <classpath refid="scalatest.classpath"/>
* </taskdef>
*
* <scalatest ...
* </target>
*
*
*
* Note that you only need the scala-actors.jar
if you are using ScalaTest version 1.9.1 or earlier
* with Scala 2.10 or later.
* Once defined, you use the task by specifying information in a scalatest
element:
*
*
*
* <scalatest ...>
* ...
* </scalatest>
*
*
*
* You can place key value pairs into the config map using nested <config>
elements,
* like this:
*
*
*
* <scalatest>
* <config name="dbname" value="testdb"/>
* <config name="server" value="192.168.1.188"/>
*
*
*
* You can specify a runpath using either a runpath
attribute and/or nested
* <runpath>
elements, using standard ant path notation:
*
*
*
* <scalatest runpath="serviceuitest-1.1beta4.jar:myjini">
*
*
*
* or
*
*
*
* <scalatest>
* <runpath>
* <pathelement location="serviceuitest-1.1beta4.jar"/>
* <pathelement location="myjini"/>
* </runpath>
*
*
*
* To add a URL to your runpath, use a <runpathurl>
element
* (since ant paths don't support URLs):
*
*
*
* <scalatest>
* <runpathurl url="http://foo.com/bar.jar"/>
*
*
*
* You can specify reporters using nested <reporter>
elements, where the type
* attribute must be one of the following:
*
*
*
* -
graphic
* -
file
* -
memory
* -
junitxml
* -
html
* -
stdout
* -
stderr
* -
reporterclass
*
*
*
* Each may include a config
attribute to specify the reporter configuration.
* Types file
, memory
, junitxml
, html
, and reporterclass
* require additional attributes (the css attribute is optional for the html reporter):
*
*
*
* <scalatest>
* <reporter type="stdout" config="FD"/>
* <reporter type="file" filename="test.out"/>
* <reporter type="memory" filename="target/memory.out"/>
* <reporter type="junitxml" directory="target"/>
* <reporter type="html" directory="target" css="src/main/html/mystylesheet.css"/>
* <reporter type="reporterclass" classname="my.ReporterClass"/>
*
*
*
* Specify tags to include and/or exclude using <tagsToInclude>
and
* <tagsToExclude>
elements, like this:
*
*
*
* <scalatest>
* <tagsToInclude>
* CheckinTests
* FunctionalTests
* </tagsToInclude>
*
* <tagsToExclude>
* SlowTests
* NetworkTests
* </tagsToExclude>
*
*
*
* Tags to include or exclude can also be specified using attributes
* tagsToInclude and tagsToExclude, with arguments specified as whitespace-
* delimited lists.
*
*
*
* To specify suites to run, use either a suite
attribute or nested
* <suite>
elements:
*
*
*
* <scalatest suite="com.artima.serviceuitest.ServiceUITestkit">
*
*
*
* or
*
*
*
* <scalatest>
* <suite classname="com.artima.serviceuitest.ServiceUITestkit"/>
*
*
*
* To specify tests to run, use nested <test>
elements with
* either a 'name' or 'substring' attribute:
*
*
*
* <scalatest>
* <test name="hello test"/>
* <test substring="hello"/>
*
*
*
* To specify suites using members-only or wildcard package names, use
* either the membersonly
or wildcard
attributes, or nested
* <membersonly>
or <wildcard>
elements:
*
*
*
* <scalatest membersonly="com.artima.serviceuitest">
*
*
*
* or
*
*
*
* <scalatest wildcard="com.artima.joker">
*
*
*
* or
*
*
*
* <scalatest>
* <membersonly package="com.artima.serviceuitest"/>
* <wildcard package="com.artima.joker"/>
*
*
*
* Use attribute suffixes="[pipe-delimited list of suffixes]"
* to specify that only classes whose names end in one of the specified suffixes
* should be included in discovery searches for Suites to test. This can
* be used to improve discovery time or to limit the scope of a test. E.g.:
*
*
*
* <scalatest suffixes="Spec|Suite">
*
*
*
* Use attribute testsfile="[file name]"
or nested
* elements to specify files containing a list of
* tests to be run. This is used to rerun failed/canceled tests
* listed in files written by the memory reporter. E.g.:
*
*
*
* <scalatest testsfile="target/memory.out">
*
*
*
* or
*
*
*
* <scalatest>
* <testsfile filename="target/memory.out"/>
*
*
*
* Use attribute parallel="true"
to specify parallel execution of suites.
* (If the parallel
attribute is left out or set to false, suites will be executed sequentially by one thread.)
* When parallel
is true, you can include an optional sortSuites
attribute to request that events be sorted on-the-fly so that
* events for the same suite are reported together, with a timeout, (e.g., sortSuites="true"
),
* and an optional numthreads
attribute to specify the number
* of threads to be created in thread pool (e.g., numthreads="10"
).
*
*
*
* Use attribute haltonfailure="true"
to cause ant to fail the
* build if there's a test failure.
*
*
*
* Use attribute fork="true"
to cause ant to run the tests in
* a separate process.
*
*
*
* When fork
is true
, attribute maxmemory
may be used to specify
* the maximum memory size that will be passed to the forked jvm. For example, the following setting
* will cause "-Xmx1280M"
to be passed to the java command used to
* run the tests.
*
*
*
* <scalatest maxmemory="1280M">
*
*
*
* When fork
is true, nested <jvmarg>
elements may be used
* to pass additional arguments to the forked jvm.
* For example, if you are running into 'PermGen space' memory errors,
* you could add the following jvmarg
to bump up the JVM's MaxPermSize
value:
*
*
*
* <jvmarg value="-XX:MaxPermSize=128m"/>
*
*
* @author George Berger
*/
class ScalaTestAntTask extends Task {
private var includes: String = ""
private var excludes: String = ""
private var maxMemory: String = null
private var suffixes: String = null
private var parallel = false
private var sortSuites = false
private var haltonfailure = false
private var fork = false
private var spanScaleFactor = 1.0
private var numthreads = 0
private val runpath = new ListBuffer[String]
private val jvmArgs = new ListBuffer[String]
private val suites = new ListBuffer[SuiteElement]
private val tests = new ListBuffer[TestElement]
private val membersonlys = new ListBuffer[String]
private val wildcards = new ListBuffer[String]
private val testNGSuites = new ListBuffer[String]
private val chosenStyles = new ListBuffer[String]
private val testsfiles = new ListBuffer[String]
private val reporters = new ListBuffer[ReporterElement]
private val properties = new ListBuffer[NameValuePair]
/**
* Executes the task.
*/
override def execute: Unit = {
val argsList = buildArgsList
val success = if (fork) javaTaskRunner(argsList)
else Runner.run(argsList.toArray)
if (!success && haltonfailure)
throw new BuildException("ScalaTest run failed.")
}
def buildArgsList: List[String] = {
val args = new ListBuffer[String]
addSuiteArgs(args)
addTestArgs(args)
addReporterArgs(args)
addPropertyArgs(args)
addIncludesArgs(args)
addExcludesArgs(args)
addRunpathArgs(args)
addTestNGSuiteArgs(args)
addParallelArg(args)
addSuffixesArg(args)
addTestsfileArgs(args)
addChosenStyles(args)
addSpanScaleFactorArg(args)
args.toList
}
private def javaTaskRunner(args: List[String]): Boolean = {
val java = new Java
java.bindToOwner(this)
java.init()
java.setFork(true)
java.setClassname("org.scalatest.tools.Runner")
val classLoader = getClass.getClassLoader.asInstanceOf[AntClassLoader]
java.setClasspath(new Path(getProject, classLoader.getClasspath))
if (maxMemory != null) java.createJvmarg.setValue("-Xmx" + maxMemory)
for (jvmArg <- jvmArgs)
java.createJvmarg.setValue(jvmArg)
for (arg <- args)
java.createArg.setValue(arg)
val result = java.executeJava
return (result == 0)
}
//
// Adds '-P runpath' arg pair to args list if a runpath
// element or attribute was specified for task.
//
private def addRunpathArgs(args: ListBuffer[String]): Unit = {
if (runpath.size > 0) {
args += "-R"
args += getSpacedOutPathStr(runpath.toList)
}
}
private def addTestNGSuiteArgs(args: ListBuffer[String]): Unit = {
if (testNGSuites.size > 0) {
args += "-b"
args += getSpacedOutPathStr(testNGSuites.toList)
}
}
private def addChosenStyles(args: ListBuffer[String]): Unit = {
chosenStyles.foreach { style =>
args += "-y"
args += style
}
}
//
// Adds '-C' arg to args list if 'parallel' attribute was
// specified true for task.
//
private def addParallelArg(args: ListBuffer[String]): Unit = {
if (parallel) {
args += (if (sortSuites) "-PS" else "-P") + (if (numthreads > 0) ("" + numthreads) else "")
}
}
//
// Add -F arg to args list if spanScaleFactor attribute was
// specified for task
//
private def addSpanScaleFactorArg(args: ListBuffer[String]): Unit = {
args += "-F"
args += spanScaleFactor.toString
}
//
// Adds '-q' arg to args list if 'suffixes' attribute was
// specified for task.
//
private def addSuffixesArg(args: ListBuffer[String]): Unit = {
if (suffixes != null) {
args += "-q"
args += suffixes
}
}
//
// Adds '-A' arg to args list if 'testsfile' attribute was
// specified for task.
//
private def addTestsfileArgs(args: ListBuffer[String]): Unit = {
for (testsfile <- testsfiles) {
args += "-A"
args += testsfile
}
}
//
// Adds '-n includes-list' arg pair to args list if a tagsToInclude
// element or attribute was supplied for task.
//
private def addIncludesArgs(args: ListBuffer[String]): Unit = {
if ((includes != null) && (includes.trim != "")) {
args += "-n"
args += singleSpace(includes)
}
}
//
// Adds '-l excludes-list' arg pair to args list if a tagsToExclude
// element or attribute was supplied for task.
//
private def addExcludesArgs(args: ListBuffer[String]): Unit = {
if ((excludes != null) && (excludes.trim != "")) {
args += "-l"
args += singleSpace(excludes)
}
}
//
// Adds '-Dname=value' argument to args list for each nested
// element supplied for task.
//
private def addPropertyArgs(args: ListBuffer[String]): Unit = {
for (pair <- properties)
args += "-D" + pair.getName + "=" + pair.getValue
}
//
// Adds '-s classname' argument to args list for each suite
// specified for task. Adds '-m packagename' for each
// membersonly element specified, and '-w packagename' for
// each wildcard element specified.
//
private def addSuiteArgs(args: ListBuffer[String]): Unit = {
for (suite <- suites) {
if (suite == null)
throw new BuildException(
"missing classname attribute for element")
args += "-s"
args += suite.getClassName
suite.getTestNames.foreach { tn =>
if (tn == null)
throw new BuildException("missing name attribute for element")
args += "-t"
args += tn
}
suite.getNestedSuites.foreach { ns =>
if (ns.getSuiteId == null)
throw new BuildException("missing suiteId attribute for element")
args += "-i"
args += ns.getSuiteId
ns.getTestNames.foreach { tn =>
if (tn == null)
throw new BuildException("missing name attribute for element")
args += "-t"
args += tn
}
}
}
for (packageName <- membersonlys) {
if (packageName == null)
throw new BuildException(
"missing package attribute for element")
args += "-m"
args += packageName
}
for (packageName <- wildcards) {
if (packageName == null)
throw new BuildException(
"missing package attribute for element")
args += "-w"
args += packageName
}
}
//
// Adds '-t testname' or '-z substring' argument to args list for
// each test specified for task.
//
private def addTestArgs(args: ListBuffer[String]): Unit = {
for (test <- tests) {
if (test.getName != null) {
args += "-t"
args += test.getName
}
else if (test.getSubstring != null) {
args += "-z"
args += test.getSubstring
}
else
throw new BuildException(
"missing name or substring attribute for element")
}
}
//
// Adds appropriate reporter options to args list for each
// nested reporter element specified for task. Defaults to
// stdout if no reporter specified.
//
private def addReporterArgs(args: ListBuffer[String]): Unit = {
if (reporters.size == 0)
args += "-o"
for (reporter <- reporters) {
reporter.getType match {
case "stdout" => addReporterOption(args, reporter, "-o")
case "stderr" => addReporterOption(args, reporter, "-e")
case "graphic" => addReporterOption(args, reporter, "-g")
case "file" => addFileReporter(args, reporter)
case "memory" => addMemoryReporter(args, reporter)
case "xml" => addXmlReporter(args, reporter)
case "junitxml" => addJunitXmlReporter(args, reporter)
case "dashboard" => addDashboardReporter(args, reporter)
case "html" => addHtmlReporter(args, reporter)
case "reporterclass" => addReporterClass(args, reporter)
case t =>
throw new BuildException("unexpected reporter type [" + t + "]")
}
}
}
//
// Adds specified option to args for reporter. Appends reporter
// config string to option if specified, e.g. "-eFD".
//
private def addReporterOption(args: ListBuffer[String],
reporter: ReporterElement,
option: String): Unit =
{
val config = reporter.getConfig
if (config == null) args += option
else args += option + config
}
//
// Adds '-f' file reporter option to args. Appends reporter
// config string to option if specified. Adds reporter's
// filename as additional argument, e.g. "-fFD", "filename".
//
private def addFileReporter(args: ListBuffer[String],
reporter: ReporterElement): Unit =
{
addReporterOption(args, reporter, "-f")
if (reporter.getFilename == null)
throw new BuildException(
"reporter type 'file' requires 'filename' attribute")
args += reporter.getFilename
}
//
// Adds '-M' memory reporter option to args. Adds reporter's
// filename as additional argument, e.g. "-M", "filename".
//
private def addMemoryReporter(args: ListBuffer[String],
reporter: ReporterElement): Unit =
{
addReporterOption(args, reporter, "-M")
if (reporter.getFilename == null)
throw new BuildException(
"reporter type 'memory' requires 'filename' attribute")
args += reporter.getFilename
}
//
// Adds '-x' xml reporter option to args. Adds reporter's
// directory as additional argument, e.g. "-x", "directory".
// [disabled for now]
//
private def addXmlReporter(args: ListBuffer[String],
reporter: ReporterElement): Unit =
{
addReporterOption(args, reporter, "-x")
if (reporter.getDirectory == null)
throw new BuildException(
"reporter type 'xml' requires 'directory' attribute")
args += reporter.getDirectory
}
//
// Adds '-u' junit xml reporter option to args. Adds reporter's
// directory as additional argument, e.g. "-u", "directory".
//
private def addJunitXmlReporter(args: ListBuffer[String],
reporter: ReporterElement): Unit =
{
addReporterOption(args, reporter, "-u")
if (reporter.getDirectory == null)
throw new BuildException(
"reporter type 'junitxml' requires 'directory' attribute")
args += reporter.getDirectory
}
//
// Adds '-d' Dashboard reporter option to args. Adds reporter's
// directory as additional argument, e.g. "-d", "directory".
//
private def addDashboardReporter(args: ListBuffer[String],
reporter: ReporterElement): Unit =
{
addReporterOption(args, reporter, "-d")
if (reporter.getDirectory == null)
throw new BuildException(
"reporter type 'dashboard' requires 'directory' attribute")
args += reporter.getDirectory
if (reporter.getNumfiles >= 0) {
args += "-a"
args += reporter.getNumfiles.toString
}
}
//
// Adds '-h' html reporter option to args. Appends reporter
// config string to option if specified. Adds reporter's
// filename as additional argument, e.g. "-hFD", "filename".
//
private def addHtmlReporter(args: ListBuffer[String],
reporter: ReporterElement): Unit =
{
addReporterOption(args, reporter, "-h")
if (reporter.getDirectory == null)
throw new BuildException(
"reporter type 'html' requires 'directory' attribute")
args += reporter.getDirectory
if (reporter.getCss != null) {
args += "-Y"
args += reporter.getCss
}
}
//
// Adds '-C' reporter class option to args. Appends
// reporter config string to option if specified. Adds
// reporter's classname as additional argument, e.g. "-C",
// "my.ReporterClass".
//
private def addReporterClass(args: ListBuffer[String],
reporter: ReporterElement): Unit =
{
addReporterOption(args, reporter, "-C")
if (reporter.getClassName == null)
throw new BuildException(
"reporter type 'reporterclass' requires 'classname' attribute")
args += reporter.getClassName
}
/**
* Sets value of the runpath
attribute.
*/
def setRunpath(runpath: Path): Unit = {
for (element <- runpath.list) {
this.runpath += element
}
}
/**
* Sets value of the tagsToExclude
attribute.
*/
def setTagsToExclude(tagsToExclude: String): Unit = {
this.excludes += " " + tagsToExclude
}
/**
* Sets value of the tagsToInclude
attribute.
*/
def setTagsToInclude(tagsToInclude: String): Unit = {
this.includes += " " + tagsToInclude
}
/**
* Sets value of the haltonfailure
attribute.
*/
def setHaltonfailure(haltonfailure: Boolean): Unit = {
this.haltonfailure = haltonfailure
}
/**
* Sets value of the fork
attribute.
*/
def setFork(fork: Boolean): Unit = {
this.fork = fork
}
/**
* Sets value of the suffixes
attribute.
*/
def setSuffixes(suffixes: String): Unit = {
this.suffixes = suffixes
}
/**
* Sets value of the testsfile
attribute.
*/
def setTestsfile(testsfile: String): Unit = {
this.testsfiles += testsfile
}
/**
* Sets value of the maxmemory
attribute.
*/
def setMaxmemory(max: String): Unit = {
this.maxMemory = max
}
/**
* Sets value of the testngsuites
attribute.
*/
def setTestNGSuites(testNGSuitePath: Path): Unit = {
for (element <- testNGSuitePath.list)
this.testNGSuites += element
}
/**
* Sets value of the numthreads
attribute.
*/
def setNumthreads(numthreads: Int): Unit = {
this.numthreads = numthreads
}
/**
* Sets value of the parallel
attribute.
*/
def setParallel(parallel: Boolean): Unit = {
this.parallel = parallel
}
/**
* Sets value of the sortSuites
attribute.
*/
def setSortSuites(sortSuites: Boolean): Unit = {
this.sortSuites = sortSuites
}
/**
* Sets value of the spanScaleFactor
attribute.
*/
def setSpanScaleFactor(spanScaleFactor: Double): Unit = {
this.spanScaleFactor = spanScaleFactor
}
/**
* Sets value from nested element runpath
.
*/
def addConfiguredRunpath(runpath: Path): Unit = {
for (element <- runpath.list)
this.runpath += element
}
/**
* Sets value from nested element testngsuites
.
*/
def addConfiguredTestNGSuites(testNGSuitePath: Path): Unit = {
for (element <- testNGSuitePath.list)
this.testNGSuites += element
}
/**
* Sets value from nested element runpathurl
.
*/
def addConfiguredRunpathUrl(runpathurl: RunpathUrl): Unit = {
runpath += runpathurl.getUrl
}
/**
* Sets value from nested element jvmarg
.
*/
def addConfiguredJvmArg(arg: JvmArg): Unit = {
jvmArgs += arg.getValue
}
/**
* Sets values from nested element config
.
*/
def addConfiguredConfig(config: NameValuePair): Unit = {
properties += config
}
/**
* Sets value of suite
attribute.
*/
def setSuite(suite: SuiteElement): Unit = {
suites += suite
}
/**
* Sets value of membersonly
attribute.
*/
def setMembersonly(packageName: String): Unit = {
membersonlys += packageName
}
/**
* Sets value of wildcard
attribute.
*/
def setWildcard(packageName: String): Unit = {
wildcards += packageName
}
/**
* Sets value of style
attribute.
*/
def setStyle(style: String): Unit = {
chosenStyles += style
}
/**
* Sets value from nested element suite
.
*/
def addConfiguredSuite(suite: SuiteElement): Unit = {
suites += suite
}
/**
* Sets value from nested element test
.
*/
def addConfiguredTest(test: TestElement): Unit = {
tests += test
}
/**
* Sets value from nested element membersonly
.
*/
def addConfiguredMembersOnly(membersonly: PackageElement): Unit = {
membersonlys += membersonly.getPackage
}
/**
* Sets value from nested element wildcard
.
*/
def addConfiguredWildcard(wildcard: PackageElement): Unit = {
wildcards += wildcard.getPackage
}
/**
* Sets value from nested element reporter
.
*/
def addConfiguredReporter(reporter: ReporterElement): Unit = {
reporters += reporter
}
/**
* Sets value from nested element tagsToInclude
.
*/
def addConfiguredTagsToInclude(tagsToInclude: TextElement): Unit = {
this.includes += " " + tagsToInclude.getText
}
def addConfiguredStyle(style: StyleElement): Unit = {
this.chosenStyles += style.getName
}
/**
* Sets value from nested element testsfile
.
*/
def addConfiguredTestsfile(testsfile: TestsfileElement): Unit = {
this.testsfiles += testsfile.getFilename
}
/**
* Sets value from nested element tagsToExclude
.
*/
def addConfiguredTagsToExclude(tagsToExclude: TextElement): Unit = {
this.excludes += " " + tagsToExclude.getText
}
//
// Translates a list of strings making up a path into a
// single space-delimited string. Uses backslashes to escape
// spaces within individual path elements, since that's what
// Runner's -p option expects.
//
private def getSpacedOutPathStr(path: List[String]): String = {
path.map(_.replaceAll(" ", """\\ """)).mkString("", " ", "")
}
//
// Translates a whitespace-delimited string into a
// whitespace-delimited string, but not the same whitespace. Trims
// off leading and trailing whitespace and converts inter-element
// whitespace to a single space.
//
private def singleSpace(str: String): String = {
str.trim.replaceAll("\\s+", " ")
}
}
//
// Class to hold data from