import sbt._ import java.io.File import java.net.URLClassLoader import TestSet.{filter} class TestSet(val SType:TestSetType.Value,val kind:String, val description:String,val files:Array[File]){ /** * @param a list of file that we want to know wheter they are members of the test set or not * @return two lists : the first contains files that are member of the test set, the second contains the files that aren't */ def splitContent(f:List[File]):(List[File],List[File]) = { f.partition((f:File) => files.elements.exists((e:File)=> f==e)) } } object TestSet { def apply(sType:TestSetType.Value,kind:String,description:String,files:PathFinder)= new TestSet(sType,kind,description,filter(files)) def filter(p:PathFinder):Array[File]=( p --- p **(HiddenFileFilter || GlobFilter("*.obj")||GlobFilter("*.log"))).getFiles.toArray } object TestSetType extends Enumeration { val Std,Continuations = Value } class TestConfiguration(val library:Path, val classpath:Iterable[Path], val testRoot:Path, val tests:List[TestSet],val junitReportDir:Option[Path]){ } trait PartestRunner{ self: BasicLayer with Packer => import Partest.runTest import TestSetType._ lazy val testRoot = projectRoot / "test" lazy val testFiles = testRoot / "files" ## lazy val testLibs = testFiles / "lib" lazy val posFilesTest = TestSet(Std,"pos", "Compiling files that are expected to build", testFiles / "pos" * ("*.scala" || DirectoryFilter)) lazy val negFilesTest = TestSet(Std,"neg", "Compiling files that are expected to fail", testFiles / "neg" * ("*.scala" || DirectoryFilter)) lazy val runFilesTest = TestSet(Std,"run", "Compiling and running files", testFiles / "run" * ("*.scala" || DirectoryFilter)) lazy val jvmFilesTest = TestSet(Std,"jvm", "Compiling and running files", testFiles / "jvm" *("*.scala" || DirectoryFilter)) lazy val resFilesTest = TestSet(Std,"res", "Running resident compiler scenarii", testFiles / "res" * ("*.res")) lazy val buildmanagerFilesTest = TestSet(Std,"buildmanager", "Running Build Manager scenarii", testFiles / "buildmanager" * DirectoryFilter) lazy val scalacheckFilesTest = TestSet(Std,"scalacheck", "Running scalacheck tests", testFiles / "scalacheck" * ("*.scala" || DirectoryFilter)) lazy val scriptFilesTest = TestSet(Std,"script", "Running script files", testFiles / "script" * ("*.scala")) lazy val shootoutFilesTest = TestSet(Std,"shootout", "Running shootout tests", testFiles / "shootout" * ("*.scala")) lazy val scalapFilesTest = TestSet(Std,"scalap", "Running scalap tests", testFiles / "scalap" * ("*.scala")) lazy val negContinuationTest = TestSet(Continuations,"neg", "Compiling continuations files that are expected to fail", testFiles / "continuations-neg" * ("*.scala" || DirectoryFilter)) lazy val runContinuationTest = TestSet(Continuations,"run", "Compiling and running continuations files", testFiles / "continuations-run" ** ("*.scala" )) lazy val continuationScalaOpts = "-Xpluginsdir "+continuationPluginConfig.packagingConfig.jarDestination.asFile.getParent+" -Xplugin-require:continuations -P:continuations:enable" lazy val testSuiteFiles:List[TestSet] = List(posFilesTest,negFilesTest,runFilesTest,jvmFilesTest,resFilesTest,buildmanagerFilesTest,scalacheckFilesTest,shootoutFilesTest,scalapFilesTest) lazy val testSuiteContinuation:List[TestSet]=List(negContinuationTest,runContinuationTest) private lazy val filesTestMap:Map[String,TestSet] = Map(testSuiteFiles.map(s=> (s.kind,s) ):_*)+ (("continuations-neg",negContinuationTest),("continuations-run",runContinuationTest)) private lazy val partestOptions = List("-failed") private lazy val partestCompletionList = filesTestMap.keys.toList:::partestOptions:::filesTestMap.values.toList.flatMap{_.files.map(_.toString.substring(testFiles.asFile.toString.length+1))} private def runPartest(tests:List[TestSet],scalacOpts:Option[String], failedOnly:Boolean) = { val config = new TestConfiguration( outputLibraryJar, (outputLibraryJar +++ outputCompilerJar +++ outputPartestJar +++ outputScalapJar +++ antJar +++ jlineJar +++ (testLibs * "*.jar")).get, testRoot, tests, None ) val javaHome = Path.fromFile(new File(System.getProperty("java.home"))) val java = Some(javaHome / "bin" / "java" asFile) val javac = Some(javaHome / "bin" / "javac" asFile) val timeout = Some("2400000") val loader = info.launcher.topLoader val isDebug = info.logger atLevel Level.Debug log.debug("Ready to run tests") if (tests.isEmpty) { log.debug("Empty test list") None } else runTest( loader, config, java, javac, scalacOpts, timeout, true, true, failedOnly, true, isDebug, log ) } lazy val externalPartest = task {args => task { val runner = new ExternalTaskRunner(projectRoot,this.name, "partest " + args.mkString(" "),"Some tests have failed", log) runner.runTask }.dependsOn(pack) }.completeWith(partestCompletionList) lazy val partest = task{ args => var failedOnly = false def setOptions(options:List[String],acc:List[String]):List[String]= options match{ case x::xs => x match{ case "-failed" => {failedOnly= true; log.info("Only tests that failed previously will be run"); setOptions(xs,acc)} case _ => setOptions(xs,x::acc) } case Nil => acc } def resolveSets(l:List[String],rem:List[String],acc:List[TestSet]): (List[String], List[TestSet]) = { def searchSet(arg:String):Option[TestSet] = { filesTestMap.get(arg) } l match{ case x::xs => searchSet(x) match{ case Some(s)=> resolveSets(xs,rem,s::acc) case None => resolveSets(xs,x::rem,acc) } case Nil => (rem,acc) } } def resolveFiles(l:List[String],sets:List[TestSet]):(List[String],List[TestSet]) = { def resolve0(filesToResolve:List[File],setsToSearchIn:List[TestSet],setAcc:List[TestSet]):(List[String],List[TestSet])= { filesToResolve match { case Nil => (Nil,setAcc) // If we have no files left to resolve, we can return the list of the set we have case list => { setsToSearchIn match { case Nil => (list.map(_.toString),setAcc)// If we already had search all sets to find a match, we return the list of the files that where problematic and the set we have case x::xs => { val (found, notFound)=x.splitContent(list) if(!found.isEmpty){ val newSet = new TestSet(x.SType,x.kind,x.description,found.toArray) resolve0(notFound,xs,newSet::setAcc) }else{ resolve0(notFound,xs,setAcc) } } } } } } resolve0(l.map(Path.fromString(testFiles,_).asFile),filesTestMap.values.toList,sets) } val keys = setOptions(args.toList,Nil) if (keys.length == 0) task{runPartest(testSuiteFiles,None,failedOnly) orElse runPartest(testSuiteContinuation,None,failedOnly)} // this is the case where there were only config options, we will run the standard test suite else { val (fileNames, sets) =resolveSets(keys,Nil,Nil) val (notFound,allSets)=resolveFiles(fileNames,sets) if (!notFound.isEmpty) log.info("Don't know what to do with : \n"+notFound.mkString("\n")) val (std, continuations) = allSets.partition(_.SType == TestSetType.Std) task{runPartest(std,None,failedOnly) orElse runPartest(continuations,Some(continuationScalaOpts),failedOnly)} } }.completeWith(partestCompletionList) } object Partest { def runTest(parentLoader:ClassLoader, config:TestConfiguration,javacmd:Option[File],javaccmd:Option[File],scalacOpts:Option[String],timeout:Option[String], showDiff:Boolean,showLog:Boolean,runFailed:Boolean,errorOnFailed:Boolean,debug:Boolean,log:Logger):Option[String] = { if (debug) System.setProperty("partest.debug", "true") if (config.classpath.isEmpty) return Some("The classpath is empty") log.debug("Classpath is "+ config.classpath) val classloader = new URLClassLoader(Array(config.classpath.toSeq.map(_.asURL):_*),ClassLoader.getSystemClassLoader.getParent) val runner: AnyRef = classloader.loadClass("scala.tools.partest.nest.SBTRunner").newInstance().asInstanceOf[AnyRef] val fileManager: AnyRef = runner.getClass.getMethod("fileManager", Array[Class[_]](): _*).invoke(runner, Array[Object](): _*) val runMethod = runner.getClass.getMethod("reflectiveRunTestsForFiles", Array(classOf[Array[File]], classOf[String]): _*) def runTestsForFiles(kindFiles: Array[File], kind: String) = { val result = runMethod.invoke(runner, Array(kindFiles, kind): _*).asInstanceOf[java.util.HashMap[String,Int]] scala.collection.jcl.Conversions.convertMap(result) } def setFileManagerBooleanProperty(name: String, value: Boolean) { log.debug("Setting partest property :"+name+" to :"+value) val setMethod = fileManager.getClass.getMethod(name+"_$eq", Array(classOf[Boolean]): _*) setMethod.invoke(fileManager, Array(java.lang.Boolean.valueOf(value)).asInstanceOf[Array[Object]]: _*) } def setFileManagerStringProperty(name: String, value: String) { log.debug("Setting partest property :"+name+" to :"+value) val setMethod = fileManager.getClass.getMethod(name+"_$eq", Array(classOf[String]): _*) setMethod.invoke(fileManager, Array(value).asInstanceOf[Array[Object]]: _*) } // System.setProperty("partest.srcdir", "files") setFileManagerBooleanProperty("showDiff", showDiff) setFileManagerBooleanProperty("showLog", showLog) setFileManagerBooleanProperty("failed", runFailed) if (!javacmd.isEmpty) setFileManagerStringProperty("JAVACMD", javacmd.get.getAbsolutePath) if (!javaccmd.isEmpty) setFileManagerStringProperty("JAVAC_CMD", "javac") setFileManagerStringProperty("CLASSPATH",(config.classpath.map(_.absolutePath).mkString(File.pathSeparator))) setFileManagerStringProperty("LATEST_LIB", config.library.absolutePath) setFileManagerStringProperty("SCALAC_OPTS", scalacOpts getOrElse "") if (!timeout.isEmpty) setFileManagerStringProperty("timeout", timeout.get) type TFSet = (Array[File], String, String) val testFileSets = config.tests def resultsToStatistics(results: Iterable[(_, Int)]): (Int, Int) = { val (files, failures) = results map (_._2 == 0) partition (_ == true) def count(i:Iterable[_]):Int={ var c = 0 for (elem <-i) yield{ c=c+1 } c } (count(files), count(failures)) } def runSet(set: TestSet): (Int, Int,Iterable[String]) = { val (files, name, msg) = (set.files, set.kind, set.description) log.debug("["+name+"] "+ msg+files.mkString(", files :\n","\n","")) if (files.isEmpty) { log.debug("No files !") (0, 0, List()) } else { log.info(name +" : "+ msg) val results: Iterable[(String, Int)] = runTestsForFiles(files, name) val (succs, fails) = resultsToStatistics(results) val failed: Iterable[String] = results.filter( _._2!=0) map(_ match { case (path, 1) => path + " [FAILED]" case (path, 2) => path + " [TIMOUT]" }) val r=(succs, fails, failed) config.junitReportDir match{ case Some(d)=> { val report = testReport(name, results, succs, fails) scala.xml.XML.save(d/name+".xml", report) } case None => } r } } val _results = testFileSets map runSet val allSuccesses = _results.map (_._1).foldLeft(0)( _ + _ ) val allFailures = _results.map (_._2).foldLeft(0)( _ + _ ) val allFailedPaths = _results flatMap (_._3) def f(msg:String):Option[String] = if (errorOnFailed && allFailures > 0) { Some(msg) } else { log.info(msg) None } def s = if (allFailures > 1) "s" else "" val msg = if (allFailures > 0) "Test suite finished with %d case%s failing.\n".format(allFailures, s)+ allFailedPaths.mkString("\n") else if (allSuccesses == 0) "There were no tests to run." else "Test suite finished with no failures." f(msg) } private def oneResult(res: (String, Int)) = { res._2 match { case 0 => scala.xml.NodeSeq.Empty case 1 => case 2 => } } private def testReport(kind: String, results: Iterable[(String, Int)], succs: Int, fails: Int) = { results.map(oneResult(_)) } }