All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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