From b21e47536453f606682e1b24ba2182e5c01995f8 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Wed, 19 Mar 2014 22:21:29 +0100 Subject: [PATCH] Improve unit testing compiler It was not possible to make `ScalaCompilerForUnitTesting` compile several files in different runs, which means that it was not possible to compile and use a macro in a test case, since macros cannot be used in the same compilation run that defines them. This commit allows a test case to provide multiple grouped snippets of code that will be compiled in separate runs. For instance : List(Map(, ), Map()) Here, and will be compiled together, and then will be compiled, and will be able to use symbols defined in or . --- .../xsbt/ScalaCompilerForUnitTesting.scala | 63 ++++++++++++++----- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala b/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala index 5362b1ca6..cb10d1d53 100644 --- a/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala +++ b/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala @@ -53,15 +53,19 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) { * dependencies between snippets. Source code snippets are identified by symbols. Each symbol should * be associated with one snippet only. * + * Snippets can be grouped to be compiled together in the same compiler run. This is + * useful to compile macros, which cannot be used in the same compilation run that + * defines them. + * * Symbols are used to express extracted dependencies between source code snippets. This way we have * file system-independent way of testing dependencies between source code "files". */ - def extractDependenciesFromSrcs(srcs: (Symbol, String)*): ExtractedSourceDependencies = { - val (symbolsForSrcs, rawSrcs) = srcs.unzip - assert(symbolsForSrcs.distinct.size == symbolsForSrcs.size, - s"Duplicate symbols for srcs detected: $symbolsForSrcs") - val (tempSrcFiles, testCallback) = compileSrcs(rawSrcs: _*) - val fileToSymbol = (tempSrcFiles zip symbolsForSrcs).toMap + def extractDependenciesFromSrcs(srcs: List[Map[Symbol, String]]): ExtractedSourceDependencies = { + val rawGroupedSrcs = srcs.map(_.values.toList).toList + val symbols = srcs.map(_.keys).flatten + val (tempSrcFiles, testCallback) = compileSrcs(rawGroupedSrcs) + val fileToSymbol = (tempSrcFiles zip symbols).toMap + val memberRefFileDeps = testCallback.sourceDependencies collect { // false indicates that those dependencies are not introduced by inheritance case (target, src, false) => (src, target) @@ -82,40 +86,64 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) { // convert all collections to immutable variants multiMap.toMap.mapValues(_.toSet).withDefaultValue(Set.empty) } + ExtractedSourceDependencies(pairsToMultiMap(memberRefDeps), pairsToMultiMap(inheritanceDeps)) } + def extractDependenciesFromSrcs(srcs: (Symbol, String)*): ExtractedSourceDependencies = { + val symbols = srcs.map(_._1) + assert(symbols.distinct.size == symbols.size, + s"Duplicate symbols for srcs detected: $symbols") + extractDependenciesFromSrcs(List(srcs.toMap)) + } + /** - * Compiles given source code snippets written to a temporary files. Each snippet is + * Compiles given source code snippets written to temporary files. Each snippet is * written to a separate temporary file. * + * Snippets can be grouped to be compiled together in the same compiler run. This is + * useful to compile macros, which cannot be used in the same compilation run that + * defines them. + * * The sequence of temporary files corresponding to passed snippets and analysis * callback is returned as a result. */ - private def compileSrcs(srcs: String*): (Seq[File], TestCallback) = { + private def compileSrcs(groupedSrcs: List[List[String]]): (Seq[File], TestCallback) = { withTemporaryDirectory { temp => val analysisCallback = new TestCallback(nameHashing) val classesDir = new File(temp, "classes") classesDir.mkdir() - val compiler = prepareCompiler(classesDir, analysisCallback) - val run = new compiler.Run - val srcFiles = srcs.toSeq.zipWithIndex map { case (src, i) => - val fileName = s"Test_$i.scala" - prepareSrcFile(temp, fileName, src) + + val compiler = prepareCompiler(classesDir, analysisCallback, classesDir.toString) + + val files = for((compilationUnit, unitId) <- groupedSrcs.zipWithIndex) yield { + val run = new compiler.Run + val srcFiles = compilationUnit.toSeq.zipWithIndex map { case (src, i) => + val fileName = s"Test-$unitId-$i.scala" + prepareSrcFile(temp, fileName, src) + } + val srcFilePaths = srcFiles.map(srcFile => srcFile.getAbsolutePath).toList + + run.compile(srcFilePaths) + + srcFilePaths.foreach(f => new File(f).delete) + srcFiles } - val srcFilePaths = srcFiles.map(srcFile => srcFile.getAbsolutePath).toList - run.compile(srcFilePaths) - (srcFiles, analysisCallback) + (files.flatten.toSeq, analysisCallback) } } + private def compileSrcs(srcs: String*): (Seq[File], TestCallback) = { + compileSrcs(List(srcs.toList)) + } + private def prepareSrcFile(baseDir: File, fileName: String, src: String): File = { val srcFile = new File(baseDir, fileName) sbt.IO.write(srcFile, src) srcFile } - private def prepareCompiler(outputDir: File, analysisCallback: AnalysisCallback): CachedCompiler0#Compiler = { + private def prepareCompiler(outputDir: File, analysisCallback: AnalysisCallback, classpath: String = "."): CachedCompiler0#Compiler = { val args = Array.empty[String] object output extends SingleOutput { def outputDirectory: File = outputDir @@ -123,6 +151,7 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) { val weakLog = new WeakLog(ConsoleLogger(), ConsoleReporter) val cachedCompiler = new CachedCompiler0(args, output, weakLog, false) val settings = cachedCompiler.settings + settings.classpath.value = classpath settings.usejavacp.value = true val scalaReporter = new ConsoleReporter(settings) val delegatingReporter = DelegatingReporter(settings, ConsoleReporter)