* Integrate Josh's test arguments patch

* Use ScalaTest arguments example to test test-interface
This commit is contained in:
Mark Harrah 2010-01-20 23:06:10 -05:00
parent 328d862f70
commit d06df9a2fe
9 changed files with 149 additions and 67 deletions

View File

@ -11,7 +11,7 @@ final class SbtHandler(directory: File, log: Logger, server: IPC.Server) extends
def initialState = newRemote
def apply(command: String, arguments: List[String], p: Process): Process =
{
send((command :: arguments).mkString(" "))
send((command :: arguments.map(escape)).mkString(" "))
receive(command + " failed")
p
}
@ -38,5 +38,9 @@ final class SbtHandler(directory: File, log: Logger, server: IPC.Server) extends
catch { case e: java.net.SocketException => error("Remote sbt initialization failed") }
p
}
// if the argument contains spaces, enclose it in quotes, quoting backslashes and quotes
def escape(argument: String) =
if(argument.contains(" ")) "\"" + argument.replaceAll(q("""\"""), """\\""").replaceAll(q("\""), "\\\"") + "\"" else argument
def q(s: String) = java.util.regex.Pattern.quote(s)
// Process("java" :: "-classpath" :: classpath.map(_.getAbsolutePath).mkString(File.pathSeparator) :: "xsbt.boot.Boot" :: ( "<" + server.port) :: Nil) run log
}

View File

@ -267,16 +267,16 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec
protected def docAction = scaladocTask(mainLabel, mainSources, mainDocPath, docClasspath, documentOptions).dependsOn(compile) describedAs DocDescription
protected def docTestAction = scaladocTask(testLabel, testSources, testDocPath, docClasspath, documentOptions).dependsOn(testCompile) describedAs TestDocDescription
protected def testAction = defaultTestTask(Nil, testOptions)
protected def testOnlyAction = testQuickMethod(testCompileConditional.analysis, testOptions)((testArgs, options) => {
defaultTestTask(testArgs, options)
}) describedAs(TestOnlyDescription)
protected def testQuickAction = defaultTestQuickMethod(false) describedAs(TestQuickDescription)
protected def testFailedAction = defaultTestQuickMethod(true) describedAs(TestFailedDescription)
protected def defaultTestQuickMethod(failedOnly: Boolean) =
testQuickMethod(testCompileConditional.analysis, testOptions)((args, options) => defaultTestTask(args, quickOptions(failedOnly) ::: options.toList))
protected def defaultTestTask(testArgs: Seq[String], testOptions: => Seq[TestOption]) =
testTask(testFrameworks, testClasspath, testCompileConditional.analysis, testArgs, testOptions).dependsOn(testCompile, copyResources, copyTestResources) describedAs TestDescription
protected def testAction = defaultTestTask(testOptions)
protected def testOnlyAction = testQuickMethod(testCompileConditional.analysis, testOptions)((options) => {
defaultTestTask(options)
}) describedAs (TestOnlyDescription)
protected def testQuickAction = defaultTestQuickMethod(false) describedAs (TestQuickDescription)
protected def testFailedAction = defaultTestQuickMethod(true) describedAs (TestFailedDescription)
protected def defaultTestQuickMethod(failedOnly: Boolean) =
testQuickMethod(testCompileConditional.analysis, testOptions)(options => defaultTestTask(quickOptions(failedOnly) ::: options.toList))
protected def defaultTestTask(testOptions: => Seq[TestOption]) =
testTask(testFrameworks, testClasspath, testCompileConditional.analysis, testOptions).dependsOn(testCompile, copyResources, copyTestResources) describedAs TestDescription
override def packageToPublishActions: Seq[ManagedTask] = `package` :: Nil

View File

@ -15,8 +15,8 @@ trait IntegrationTesting extends NotNull
trait ScalaIntegrationTesting extends IntegrationTesting
{ self: ScalaProject =>
protected def integrationTestTask(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, testArgs: Seq[String], options: => Seq[TestOption]) =
testTask(frameworks, classpath, analysis, testArgs, options)
protected def integrationTestTask(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: => Seq[TestOption]) =
testTask(frameworks, classpath, analysis, options)
}
trait BasicScalaIntegrationTesting extends BasicIntegrationTesting with MavenStyleIntegrationTestPaths
@ -34,7 +34,7 @@ trait BasicIntegrationTesting extends ScalaIntegrationTesting with IntegrationTe
val integrationTestCompileConditional = new CompileConditional(integrationTestCompileConfiguration, buildCompiler)
protected def integrationTestAction = integrationTestTask(integrationTestFrameworks, integrationTestClasspath, integrationTestCompileConditional.analysis, Nil, integrationTestOptions) dependsOn integrationTestCompile describedAs IntegrationTestCompileDescription
protected def integrationTestAction = integrationTestTask(integrationTestFrameworks, integrationTestClasspath, integrationTestCompileConditional.analysis, integrationTestOptions) dependsOn integrationTestCompile describedAs IntegrationTestCompileDescription
protected def integrationTestCompileAction = integrationTestCompileTask() dependsOn compile describedAs IntegrationTestDescription
protected def integrationTestCompileTask() = task{ integrationTestCompileConditional.run }

View File

@ -67,7 +67,14 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
case class ExcludeTests(tests: Iterable[String]) extends TestOption
case class TestListeners(listeners: Iterable[TestReportListener]) extends TestOption
case class TestFilter(filterTest: String => Boolean) extends TestOption
case class TestFrameworkArgument(arg: String) extends TestOption
// args for all frameworks
def TestArgument(args: String*): TestArgument = TestArgument(None, args.toList)
// args for a particular test framework
def TestArgument(tf: TestFramework, args: String*): TestArgument = TestArgument(Some(tf), args.toList)
// None means apply to all, Some(tf) means apply to a particular framework only.
case class TestArgument(framework: Option[TestFramework], args: List[String]) extends TestOption
case class JarManifest(m: Manifest) extends PackageOption
{
@ -150,14 +157,13 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
def copyTask(sources: PathFinder, destinationDirectory: Path): Task =
task { FileUtilities.copy(sources.get, destinationDirectory, log).left.toOption }
def testTask(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, testArgs: Seq[String], options: TestOption*): Task =
testTask(frameworks, classpath, analysis, testArgs, options)
def testTask(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis,
testArgs: Seq[String], options: => Seq[TestOption]): Task =
def testTask(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: TestOption*): Task =
testTask(frameworks, classpath, analysis, options)
def testTask(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: => Seq[TestOption]): Task =
{
def work =
{
val (begin, work, end) = testTasks(frameworks, classpath, analysis, testArgs, options)
val (begin, work, end) = testTasks(frameworks, classpath, analysis, options)
val beginTasks = begin.map(toTask).toSeq // test setup tasks
val workTasks = work.map(w => toTask(w) dependsOn(beginTasks : _*)) // the actual tests
val endTasks = end.map(toTask).toSeq // tasks that perform test cleanup and are run regardless of success of tests
@ -251,43 +257,58 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
}
}
protected def incrementImpl(v: BasicVersion): Version = v.incrementMicro
protected def testTasks(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, testArgs: Seq[String],
options: => Seq[TestOption]) = {
protected def testTasks(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: => Seq[TestOption]) =
{
import scala.collection.mutable.HashSet
import scala.collection.mutable.Map
val testFilters = new ListBuffer[String => Boolean]
val excludeTestsSet = new HashSet[String]
val setup, cleanup = new ListBuffer[() => Option[String]]
val testListeners = new ListBuffer[TestReportListener]
val testFilters = new ListBuffer[String => Boolean]
val excludeTestsSet = new HashSet[String]
val setup, cleanup = new ListBuffer[() => Option[String]]
val testListeners = new ListBuffer[TestReportListener]
val testArgsByFramework = Map[TestFramework, ListBuffer[String]]()
def frameworkArgs(framework: TestFramework): ListBuffer[String] =
testArgsByFramework.getOrElseUpdate(framework, new ListBuffer[String])
for(option <- options)
for(option <- options)
{
option match
{
option match
{
case TestFilter(include) => testFilters += include
case ExcludeTests(exclude) => excludeTestsSet ++= exclude
case TestListeners(listeners) => testListeners ++= listeners
case TestSetup(setupFunction) => setup += setupFunction
case TestCleanup(cleanupFunction) => cleanup += cleanupFunction
}
case TestFilter(include) => testFilters += include
case ExcludeTests(exclude) => excludeTestsSet ++= exclude
case TestListeners(listeners) => testListeners ++= listeners
case TestSetup(setupFunction) => setup += setupFunction
case TestCleanup(cleanupFunction) => cleanup += cleanupFunction
/**
* There are two cases here.
* The first handles TestArguments in the project file, which
* might have a TestFramework specified.
* The second handles arguments to be applied to all test frameworks.
* -- arguments from the project file that didnt have a framework specified
* -- command line arguments (ex: test-only someClass -- someArg)
* (currently, command line args must be passed to all frameworks)
*/
case TestArgument(Some(framework), args) => frameworkArgs(framework) ++= args
case TestArgument(None, args) => frameworks.foreach { framework => frameworkArgs(framework) ++= args.toList }
}
}
if(excludeTestsSet.size > 0 && log.atLevel(Level.Debug))
{
log.debug("Excluding tests: ")
excludeTestsSet.foreach(test => log.debug("\t" + test))
}
def includeTest(test: TestDefinition) = !excludeTestsSet.contains(test.testClassName) && testFilters.forall(filter => filter(test.testClassName))
val tests = HashSet.empty[TestDefinition] ++ analysis.allTests.filter(includeTest)
TestFramework.testTasks(frameworks, classpath.get, buildScalaInstance.loader, tests.toSeq, log, testListeners.readOnly, false, setup.readOnly, cleanup.readOnly, testArgs)
if(excludeTestsSet.size > 0 && log.atLevel(Level.Debug))
{
log.debug("Excluding tests: ")
excludeTestsSet.foreach(test => log.debug("\t" + test))
}
def includeTest(test: TestDefinition) = !excludeTestsSet.contains(test.testClassName) && testFilters.forall(filter => filter(test.testClassName))
val tests = HashSet.empty[TestDefinition] ++ analysis.allTests.filter(includeTest)
TestFramework.testTasks(frameworks, classpath.get, buildScalaInstance.loader, tests.toSeq, log,
testListeners.readOnly, false, setup.readOnly, cleanup.readOnly, testArgsByFramework)
}
private def flatten[T](i: Iterable[Iterable[T]]) = i.flatMap(x => x)
protected def testQuickMethod(testAnalysis: CompileAnalysis, options: => Seq[TestOption])(toRun: (Seq[String],Seq[TestOption]) => Task) = {
protected def testQuickMethod(testAnalysis: CompileAnalysis, options: => Seq[TestOption])(toRun: (Seq[TestOption]) => Task) = {
val analysis = testAnalysis.allTests.map(_.testClassName).toList
multiTask(analysis) { (args,includeFunction) => {
toRun(args, TestFilter(includeFunction) :: options.toList)
}
multiTask(analysis) { (args, includeFunction) =>
toRun(TestArgument(args:_*) :: TestFilter(includeFunction) :: options.toList)
}
}
@ -351,7 +372,7 @@ object ScalaProject
}
trait MultiTaskProject extends Project
{
def multiTask(allTests: => List[String])(run: (List[String], (String => Boolean)) => Task): MethodTask = {
def multiTask(allTests: => List[String])(run: (Seq[String], String => Boolean) => Task): MethodTask = {
task { tests =>

View File

@ -34,6 +34,7 @@ final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq
private[this] val delegate = framework.testRunner(loader, listeners.flatMap(_.contentLogger).toArray)
final def run(testDefinition: TestDefinition, args: Seq[String]): Result.Value =
{
log.debug("Running " + testDefinition + " with arguments " + args.mkString(", "))
val testClass = testDefinition.testClassName
def runTest() =
{
@ -87,28 +88,34 @@ object TestFramework
private[sbt] def safeForeach[T](it: Iterable[T], log: Logger)(f: T => Unit): Unit =
it.foreach(i => Control.trapAndLog(log){ f(i) } )
import scala.collection.{Map, Set}
import scala.collection.{immutable, Map, Set}
def testTasks(frameworks: Seq[TestFramework],
classpath: Iterable[Path],
scalaLoader: ClassLoader,
tests: Seq[TestDefinition],
log: Logger,
listeners: Seq[TestReportListener],
endErrorsEnabled: Boolean,
setup: Iterable[() => Option[String]],
cleanup: Iterable[() => Option[String]],
testArgs: Seq[String]): (Iterable[NamedTestTask], Iterable[NamedTestTask], Iterable[NamedTestTask]) =
classpath: Iterable[Path],
scalaLoader: ClassLoader,
tests: Seq[TestDefinition],
log: Logger,
listeners: Seq[TestReportListener],
endErrorsEnabled: Boolean,
setup: Iterable[() => Option[String]],
cleanup: Iterable[() => Option[String]],
testArgsByFramework: Map[TestFramework, Seq[String]]):
(Iterable[NamedTestTask], Iterable[NamedTestTask], Iterable[NamedTestTask]) =
{
val loader = createTestLoader(classpath, scalaLoader)
val rawFrameworks = frameworks.flatMap(_.create(loader, log))
val mappedTests = testMap(rawFrameworks, tests)
val arguments = immutable.Map() ++
( for(framework <- frameworks; created <- framework.create(loader, log)) yield
(created, testArgsByFramework.getOrElse(framework, Nil)) )
val mappedTests = testMap(arguments.keys.toList, tests, arguments)
if(mappedTests.isEmpty)
(new NamedTestTask(TestStartName, None) :: Nil, Nil, new NamedTestTask(TestFinishName, { log.info("No tests to run."); None }) :: Nil )
else
createTestTasks(loader, mappedTests, log, listeners, endErrorsEnabled, setup, cleanup, testArgs)
createTestTasks(loader, mappedTests, log, listeners, endErrorsEnabled, setup, cleanup)
}
private def testMap(frameworks: Seq[Framework], tests: Seq[TestDefinition]): Map[Framework, Set[TestDefinition]] =
private def testMap(frameworks: Seq[Framework], tests: Seq[TestDefinition], args: Map[Framework, Seq[String]]):
immutable.Map[Framework, (Set[TestDefinition], Seq[String])] =
{
import scala.collection.mutable.{HashMap, HashSet, Set}
val map = new HashMap[Framework, Set[TestDefinition]]
@ -127,14 +134,14 @@ object TestFramework
}
if(!frameworks.isEmpty)
assignTests()
wrap.Wrappers.readOnly(map)
(immutable.Map() ++ map) transform { (framework, tests) => (tests, args(framework)) }
}
private def createTasks(work: Iterable[() => Option[String]], baseName: String) =
work.toList.zipWithIndex.map{ case (work, index) => new NamedTestTask(baseName + " " + (index+1), work()) }
private def createTestTasks(loader: ClassLoader, tests: Map[Framework, Set[TestDefinition]], log: Logger,
private def createTestTasks(loader: ClassLoader, tests: Map[Framework, (Set[TestDefinition], Seq[String])], log: Logger,
listeners: Seq[TestReportListener], endErrorsEnabled: Boolean, setup: Iterable[() => Option[String]],
cleanup: Iterable[() => Option[String]], testArgs: Seq[String]) =
cleanup: Iterable[() => Option[String]]) =
{
val testsListeners = listeners.filter(_.isInstanceOf[TestsListener]).map(_.asInstanceOf[TestsListener])
def foreachListenerSafe(f: TestsListener => Unit): Unit = safeForeach(testsListeners, log)(f)
@ -148,7 +155,7 @@ object TestFramework
}
val startTask = new NamedTestTask(TestStartName, {foreachListenerSafe(_.doInit); None}) :: createTasks(setup, "Test setup")
val testTasks =
tests flatMap { case (framework, testDefinitions) =>
tests flatMap { case (framework, (testDefinitions, testArgs)) =>
val runner = new TestRunner(framework, loader, listeners, log)
for(testDefinition <- testDefinitions) yield

View File

@ -0,0 +1,2 @@
project.name=Test Arguments Test
project.version=1.0

View File

@ -0,0 +1,7 @@
import sbt._
class ArgumentTest(info: ProjectInfo) extends DefaultProject(info)
{
val snap = ScalaToolsSnapshots
val st = "org.scalatest" % "scalatest" % "1.0.1-for-scala-2.8.0.Beta1-RC7-with-test-interfaces-0.3-SNAPSHOT"
}

View File

@ -0,0 +1,14 @@
import org.scalatest.fixture.FixtureFunSuite
import org.scalatest.Tag
class ArgumentTest extends FixtureFunSuite{
type FixtureParam = Map[String,Any]
override def withFixture(test: OneArgTest) {
test(test.configMap)
}
test("1", Tag("test1")){ conf => error("error #1") }
test("2", Tag("test2")){ conf => () }
test("3", Tag("test3")){ conf => () }
test("4", Tag("test4")){ conf => error("error #4") }
}

View File

@ -0,0 +1,27 @@
> ++2.8.0.Beta1-RC7
> update
# should fail because it should run all tests, some of which are expected to fail (1 and 4)
-> test-only ArgumentTest
# should fail because it should run the test tagged 'test1', which should fail
-> test-only ArgumentTest -- -n test1
# should succeed because it should only run the test tagged 'test2', which should succeed
> test-only ArgumentTest -- -n test2
# should succeed because it should only run the test tagged 'test3', which should succeed
> test-only ArgumentTest -- -n test3
# should fail because it should run the test tagged 'test4', which should fail
-> test-only ArgumentTest -- -n test4
# should succeed because it should only run the tests tagged 'test2' or 'test3', both of which should succeed
> test-only ArgumentTest -- -n "test2 test3"
# these should fail because they run at least one failed test
-> test-only ArgumentTest -- -n "test2 test4"
-> test-only ArgumentTest -- -n "test1 test2 test3"
-> test-only ArgumentTest -- -n "test2 test3 test4"
-> test-only ArgumentTest -- -n "test1 test2 test3 test4"
-> test-only ArgumentTest -- -n "test1 test3"