diff options
Diffstat (limited to 'src')
13 files changed, 581 insertions, 214 deletions
diff --git a/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala b/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala index f78365a7a8..3bad2d0962 100644 --- a/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala +++ b/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala @@ -2,15 +2,20 @@ * Copyright 2009-2011 Scala Solutions and LAMP/EPFL * @author Martin Odersky */ -package scala.tools.nsc.interactive +package scala.tools.nsc +package interactive package tests -import scala.tools.nsc.Settings -import scala.tools.nsc.reporters.StoreReporter -import scala.tools.nsc.util.{BatchSourceFile, SourceFile, Position} -import scala.tools.nsc.io._ +import core._ -import scala.collection.{immutable, mutable} +import java.io.File.pathSeparatorChar +import java.io.File.separatorChar + +import scala.annotation.migration +import scala.tools.nsc.util.Position +import scala.tools.nsc.util.SourceFile + +import collection.mutable.ListBuffer /** A base class for writing interactive compiler tests. * @@ -18,235 +23,102 @@ import scala.collection.{immutable, mutable} * compiler: instantiation source files, reloading, creating positions, instantiating * the presentation compiler, random stress testing. * - * By default, this class loads all classes found under `src/`. They are found in - * `sourceFiles`. Positions can be created using `pos(file, line, col)`. The presentation - * compiler is available through `compiler`. + * By default, this class loads all scala and java classes found under `src/`, going + * recursively into subfolders. Loaded classes are found in `sourceFiles`. trait `TestResources` + * The presentation compiler is available through `compiler`. + * + * It is easy to test member completion, type and hyperlinking at a given position. Source + * files are searched for `TextMarkers`. By default, the completion marker is `/*!*/`, the + * typedAt marker is `/*?*/` and the hyperlinking marker is `/*#*/`. Place these markers in + * your source files, and the test framework will automatically pick them up and test the + * corresponding actions. Sources are reloaded by `askReload(sourceFiles)` (blocking + * call). All ask operations are placed on the work queue without waiting for each one to + * complete before asking the next. After all asks, it waits for each response in turn and + * prints the result. The default timeout is 1 second per operation. + * + * To define a custom operation you have to: * - * It is easy to test member completion and type at a given position. Source - * files are searched for /markers/. By default, the completion marker is `/*!*/` and the - * typedAt marker is `/*?*/`. Place these markers in your source files, and call `completionTests` - * and `typedAtTests` to print the results at all these positions. Sources are reloaded by `reloadSources` - * (blocking call). All ask operations are placed on the work queue without waiting for each one to - * complete before asking the next. After all asks, it waits for each response in turn and prints the result. - * The default timout is 5 seconds per operation. + * (1) Define a new marker by extending `TestMarker` + * (2) Provide an implementation for the operation you want to check by extending `PresentationCompilerTestDef` + * (3) Add the class defined in (1) to the set of executed test actions by calling `++` on `InteractiveTest`. * - * The same mechanism can be used for custom operations. Use `askAllSources(marker)(f)(g)`. Give your custom - * marker, and provide the two functions: one for creating the request, and the second for processing the - * response, if it didn't time out and there was no error. + * Then you can simply use the new defined `marker` in your test sources and the testing + * framework will automatically pick it up. * * @see Check existing tests under test/files/presentation * - * @author Iulian Dragos + * @author Iulian Dragos + * @author Mirco Dotta */ -abstract class InteractiveTest { - - val completionMarker = "/*!*/" - val typedAtMarker = "/*?*/" - val TIMEOUT = 10000 // timeout in milliseconds - - val settings = new Settings - val reporter= new StoreReporter - - /** The root directory for this test suite, usually the test kind ("test/files/presentation"). */ - val outDir = Path(Option(System.getProperty("partest.cwd")).getOrElse(".")) - - /** The base directory for this test, usually a subdirectory of "test/files/presentation/" */ - val baseDir = Option(System.getProperty("partest.testname")).map(outDir / _).getOrElse(Path(".")) - - /** If there's a file ending in .opts, read it and parse it for cmd line arguments. */ - val argsString = { - val optsFile = outDir / "%s.opts".format(System.getProperty("partest.testname")) - val str = try File(optsFile).slurp() catch { - case e: java.io.IOException => "" - } - str.lines.filter(!_.startsWith("#")).mkString(" ") - } - - /** Prepare the settings object. Load the .opts file and adjust all paths from the - * Unix-like syntax to the platform specific syntax. This is necessary so that a - * single .opts file can be used on all platforms. - * - * @note Bootclasspath is treated specially. If there is a -bootclasspath option in - * the file, the 'usejavacp' setting is set to false. This ensures that the - * bootclasspath takes precedence over the scala-library used to run the current - * test. - */ - def prepareSettings() { - import java.io.File._ - def adjustPaths(paths: settings.PathSetting*) { - for (p <- paths if argsString.contains(p.name)) p.value = p.value.map { - case '/' => separatorChar - case ':' => pathSeparatorChar - case c => c - } - } - - // need this so that the classpath comes from what partest - // instead of scala.home - settings.usejavacp.value = !argsString.contains("-bootclasspath") - - // pass any options coming from outside - settings.processArgumentString(argsString) match { - case (false, rest) => - println("error processing arguments (unprocessed: %s)".format(rest)) - case _ => () - } - adjustPaths(settings.bootclasspath, settings.classpath, settings.javabootclasspath, settings.sourcepath) - } - - protected def printClassPath() { - println("\toutDir: %s".format(outDir.path)) - println("\tbaseDir: %s".format(baseDir.path)) - println("\targsString: %s".format(argsString)) - println("\tbootClassPath: %s".format(settings.bootclasspath.value)) - println("\tverbose: %b".format(settings.verbose.value)) - } - - lazy val compiler = { - prepareSettings() - new Global(settings, reporter) - } - - def sources(filename: String*): Seq[SourceFile] = - for (f <- filename) yield - source(if (f.startsWith("/")) Path(f) else baseDir / f) - - def source(file: Path) = new BatchSourceFile(AbstractFile.getFile(file.toFile)) - def source(filename: String): SourceFile = new BatchSourceFile(AbstractFile.getFile(filename)) - - def pos(file: SourceFile, line: Int, col: Int): Position = - file.position(line, col) - - def filesInDir(dir: Path): Iterator[Path] = { - dir.toDirectory.list.filter(_.isFile) - } - - /** Where source files are placed. */ - val sourceDir = "src" - - /** All .scala files below "src" directory. */ - lazy val sourceFiles: Array[SourceFile] = - filesInDir(baseDir / sourceDir).filter(_.extension == "scala").map(source).toArray - - /** All positions of the given string in all source files. */ - def allPositionsOf(sources: Seq[SourceFile] = sourceFiles, str: String): immutable.Map[SourceFile, Seq[Position]] = { - (for (s <- sources; p <- positionsOf(s, str)) yield p).groupBy(_.source) - } - - /** Return all positions of the given str in the given source file. */ - def positionsOf(source: SourceFile, str: String): Seq[Position] = { - val buf = new mutable.ListBuffer[Position] - var pos = source.content.indexOfSlice(str) - while (pos >= 0) { -// buf += compiler.rangePos(source, pos - 1, pos - 1, pos - 1) - buf += source.position(pos - 1) // we need the position before the first character of this marker - pos = source.content.indexOfSlice(str, pos + 1) - } - buf.toList - } - - /** Should askAllSources wait for each ask to finish before issueing the next? */ - val synchronousRequests = true - - /** Perform an operation on all sources at all positions that match the given - * marker string. For instance, askAllSources("/*!*/")(askTypeAt)(println) would - * ask the tyep at all positions marked with /*!*/ and println the result. +abstract class InteractiveTest + extends AskParse + with AskShutdown + with AskReload + with PresentationCompilerInstance + with CoreTestDefs + with InteractiveTestSettings { self => + + protected val runRandomTests = false + + /** Should askAllSources wait for each ask to finish before issuing the next? */ + override protected val synchronousRequests = true + + /** The core set of test actions that are executed during each test run are + * `CompletionAction`, `TypeAction` and `HyperlinkAction`. + * Override this member if you need to change the default set of executed test actions. */ - def askAllSourcesAsync[T](marker: String)(askAt: Position => Response[T])(f: (Position, T) => Unit) { - val positions = allPositionsOf(str = marker).valuesIterator.toList.flatten - val responses = for (pos <- positions) yield askAt(pos) - - for ((pos, r) <- positions zip responses) withResponse(pos, r)(f) + protected lazy val testActions: ListBuffer[PresentationCompilerTestDef] = { + ListBuffer(new CompletionAction(compiler), new TypeAction(compiler), new HyperlinkAction(compiler)) } - /** Synchronous version of askAllSources. Each position is treated in turn, waiting for the - * response before going to the next one. + /** Add new presentation compiler actions to test. Presentation compiler's test + * need to extends trait `PresentationCompilerTestDef`. */ - def askAllSourcesSync[T](marker: String)(askAt: Position => Response[T])(f: (Position, T) => Unit) { - val positions = allPositionsOf(str = marker).valuesIterator.toList.flatten - for (pos <- positions) withResponse(pos, askAt(pos))(f) - } - - def askAllSources[T] = if (synchronousRequests) askAllSourcesSync[T] _ else askAllSourcesAsync[T] _ - - /** Return the filename:line:col version of this position. */ - def showPos(pos: Position): String = - "%s:%d:%d".format(pos.source.file.name, pos.line, pos.column) - - protected def withResponse[T](pos: Position, response: Response[T])(f: (Position, T) => Unit) { - response.get(TIMEOUT) match { - case Some(Left(t)) => - f(pos, t) - case None => - println("TIMEOUT: " + showPos(pos)) - case Some(r) => - println("ERROR: " + r) - } + protected def ++(tests: PresentationCompilerTestDef*) { + testActions ++= tests } - /** Ask completion for all marked positions in all sources. - * A completion position is marked with /*!*/. - */ - def completionTests() { - askAllSources(completionMarker) { pos => - println("askTypeCompletion at " + pos.source.file.name + ((pos.line, pos.column))) - val r = new Response[List[compiler.Member]] - compiler.askTypeCompletion(pos, r) - r - } { (pos, members) => - println("\n" + "=" * 80) - println("[response] aksTypeCompletion at " + (pos.line, pos.column)) - // we skip getClass because it changed signature between 1.5 and 1.6, so there is no - // universal check file that we can provide for this to work - println("retreived %d members".format(members.size)) - compiler ask { () => - println(members.sortBy(_.sym.name.toString).filterNot(_.sym.name.toString == "getClass").mkString("\n")) - } - } - } + /** Test's entry point */ + def main(args: Array[String]) { + loadSources() + runTests() + shutdown() - /** Ask for typedAt for all marker positions in all sources. - */ - def typeAtTests() { - askAllSources(typedAtMarker) { pos => - println("askTypeAt at " + pos.source.file.name + ((pos.line, pos.column))) - val r = new Response[compiler.Tree] - compiler.askTypeAt(pos, r) - r - } { (pos, tree) => - println("[response] askTypeAt at " + (pos.line, pos.column)) - compiler.ask(() => println(tree)) - } + // this is actually needed to force exit on test completion. + // Note: May be a bug on either the testing framework or (less likely) + // the presentation compiler + sys.exit(0) } - /** Reload the given source files and wait for them to be reloaded. */ - def reloadSources(sources: Seq[SourceFile] = sourceFiles) { -// println("basedir: " + baseDir.path) -// println("sourcedir: " + (baseDir / sourceDir).path) - println("reload: " + sourceFiles.mkString("", ", ", "")) - val reload = new Response[Unit] - compiler.askReload(sourceFiles.toList, reload) - reload.get + /** Load all sources before executing the test. */ + private def loadSources() { + // ask the presentation compiler to track all sources. We do + // not wait for the file to be entirely typed because we do want + // to exercise the presentation compiler on scoped type requests. + askReload(sourceFiles) + // make sure all sources are parsed before running the test. This + // is because test may depend on the sources having been parsed at + // least once + askParse(sourceFiles) } - def runTest(): Unit = { - if (runRandomTests) randomTests(20, sourceFiles) - completionTests() - typeAtTests() + /** Run all defined `PresentationCompilerTestDef` */ + protected def runTests() { + //TODO: integrate random tests!, i.e.: if (runRandomTests) randomTests(20, sourceFiles) + testActions.foreach(_.runTest()) } /** Perform n random tests with random changes. */ - def randomTests(n: Int, files: Array[SourceFile]) { - val tester = new Tester(n, files, settings) + private def randomTests(n: Int, files: Array[SourceFile]) { + val tester = new Tester(n, files, settings) { + override val compiler = self.compiler + override val reporter = compilerReporter + } tester.run() } - val runRandomTests = false - - def main(args: Array[String]) { - reloadSources() - runTest - compiler.askShutdown() + /** shutdown the presentation compiler. */ + private def shutdown() { + askShutdown() } -} - +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTestSettings.scala b/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTestSettings.scala new file mode 100644 index 0000000000..ec2ae3a11b --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTestSettings.scala @@ -0,0 +1,67 @@ +package scala.tools.nsc +package interactive +package tests + +import java.io.File.pathSeparatorChar +import java.io.File.separatorChar + +import scala.tools.nsc.interactive.tests.core.PresentationCompilerInstance +import scala.tools.nsc.io.File + +import core.Reporter +import core.TestSettings + +trait InteractiveTestSettings extends TestSettings with PresentationCompilerInstance { + /** Character delimiter for comments in .opts file */ + private final val CommentStartDelimiter = "#" + + private final val TestOptionsFileExtension = "opts" + + /** Prepare the settings object. Load the .opts file and adjust all paths from the + * Unix-like syntax to the platform specific syntax. This is necessary so that a + * single .opts file can be used on all platforms. + * + * @note Bootclasspath is treated specially. If there is a -bootclasspath option in + * the file, the 'usejavacp' setting is set to false. This ensures that the + * bootclasspath takes precedence over the scala-library used to run the current + * test. + */ + override protected def prepareSettings(settings: Settings) { + import java.io.File._ + def adjustPaths(paths: settings.PathSetting*) { + for (p <- paths if argsString.contains(p.name)) p.value = p.value.map { + case '/' => separatorChar + case ':' => pathSeparatorChar + case c => c + } + } + + // need this so that the classpath comes from what partest + // instead of scala.home + settings.usejavacp.value = !argsString.contains("-bootclasspath") + + // pass any options coming from outside + settings.processArgumentString(argsString) match { + case (false, rest) => + println("error processing arguments (unprocessed: %s)".format(rest)) + case _ => () + } + adjustPaths(settings.bootclasspath, settings.classpath, settings.javabootclasspath, settings.sourcepath) + } + + /** If there's a file ending in .opts, read it and parse it for cmd line arguments. */ + protected val argsString = { + val optsFile = outDir / "%s.%s".format(System.getProperty("partest.testname"), TestOptionsFileExtension) + val str = try File(optsFile).slurp() catch { + case e: java.io.IOException => "" + } + str.lines.filter(!_.startsWith(CommentStartDelimiter)).mkString(" ") + } + + override protected def printClassPath(implicit reporter: Reporter) { + reporter.println("\toutDir: %s".format(outDir.path)) + reporter.println("\tbaseDir: %s".format(baseDir.path)) + reporter.println("\targsString: %s".format(argsString)) + super.printClassPath(reporter) + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/AskCommands.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/AskCommands.scala new file mode 100644 index 0000000000..5454c2cc04 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/AskCommands.scala @@ -0,0 +1,113 @@ +package scala.tools.nsc +package interactive +package tests.core + +import scala.tools.nsc.interactive.Response +import scala.tools.nsc.util.Position +import scala.tools.nsc.util.SourceFile + +/** + * A trait for defining commands that can be queried to the + * presentation compiler. + * */ +trait AskCommand { + + /** presentation compiler's instance. */ + protected val compiler: Global + + /** + * Presentation compiler's `askXXX` actions work by doing side-effects + * on a `Response` instance passed as an argument during the `askXXX` + * call. + * The defined method `ask` is meant to encapsulate this behavior. + * */ + protected def ask[T](op: Response[T] => Unit): Response[T] = { + val r = new Response[T] + op(r) + r + } +} + +/** Ask the presentation compiler to shut-down. */ +trait AskShutdown extends AskCommand { + def askShutdown() = compiler.askShutdown() +} + +/** Ask the presentation compiler to parse a sequence of `sources` */ +trait AskParse extends AskCommand { + import compiler.Tree + + /** `sources` need to be entirely parsed before running the test + * (else commands such as `AskCompletionAt` may fail simply because + * the source's AST is not yet loaded). + */ + def askParse(sources: Seq[SourceFile]) { + val responses = sources map (askParse(_)) + responses.foreach(_.get) // force source files parsing + } + + private def askParse(src: SourceFile, keepLoaded: Boolean = true): Response[Tree] = { + ask { + compiler.askParsedEntered(src, keepLoaded, _) + } + } +} + +/** Ask the presentation compiler to reload a sequence of `sources` */ +trait AskReload extends AskCommand { + + /** Reload the given source files and wait for them to be reloaded. */ + def askReload(sources: Seq[SourceFile])(implicit reporter: Reporter): Response[Unit] = { + reporter.println("reload: " + sources.mkString("", ", ", "")) + + ask { + compiler.askReload(sources.toList, _) + } + } +} + +/** Ask the presentation compiler for completion at a given position. */ +trait AskCompletionAt extends AskCommand { + import compiler.Member + + def askCompletionAt(pos: Position)(implicit reporter: Reporter): Response[List[Member]] = { + reporter.println("\naskTypeCompletion at " + pos.source.file.name + ((pos.line, pos.column))) + + ask { + compiler.askTypeCompletion(pos, _) + } + } +} + +/** Ask the presentation compiler for type info at a given position. */ +trait AskTypeAt extends AskCommand { + import compiler.Tree + + def askTypeAt(pos: Position)(implicit reporter: Reporter): Response[Tree] = { + reporter.println("\naskType at " + pos.source.file.name + ((pos.line, pos.column))) + + ask { + compiler.askTypeAt(pos, _) + } + } +} + + +trait AskType extends AskCommand { + import compiler.Tree + + def askType(source: SourceFile, forceReload: Boolean)(implicit reporter: Reporter): Response[Tree] = { + ask { + compiler.askType(source, forceReload, _) + } + } + + def askType(sources: Seq[SourceFile], forceReload: Boolean)(implicit reporter: Reporter): Seq[Response[Tree]] = { + for(source <- sources) yield + askType(source, forceReload) + } +} + + + + diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/CoreTestDefs.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/CoreTestDefs.scala new file mode 100644 index 0000000000..bf4708de7f --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/CoreTestDefs.scala @@ -0,0 +1,99 @@ +package scala.tools.nsc +package interactive +package tests.core + +import scala.tools.nsc.util.Position + +import scala.tools.nsc.interactive.tests.core._ + +/** Set of core test definitions that are executed for each test run. */ +private[tests] trait CoreTestDefs + extends PresentationCompilerRequestsWorkingMode { + + import scala.tools.nsc.interactive.Global + + /** Ask the presentation compiler for completion at all locations + * (in all sources) where the defined `marker` is found. */ + class CompletionAction(override val compiler: Global) + extends PresentationCompilerTestDef + with AskCompletionAt { + + object MemberPrinter { + def apply(member: compiler.Member): String = + "`" + (member.sym.toString() + member.tpe.toString()).trim() + "`" + } + + protected val marker = CompletionMarker + + override def runTest() { + askAllSources(marker) { pos => + askCompletionAt(pos) + } { (pos, members) => + withResponseDelimiter { + reporter.println("[response] aksTypeCompletion at (%s,%s)".format(pos.line, pos.column)) + // we skip getClass because it changed signature between 1.5 and 1.6, so there is no + // universal check file that we can provide for this to work + reporter.println("retrieved %d members".format(members.size)) + compiler ask { () => + val filtered = members.filterNot(member => member.sym.name.toString == "getClass" || member.sym.isConstructor) + reporter.println(filtered.map(MemberPrinter(_)).sortBy(_.toString()).mkString("\n")) + } + } + } + } + } + + /** Ask the presentation compiler for type info at all locations + * (in all sources) where the defined `marker` is found. */ + class TypeAction(override val compiler: Global) + extends PresentationCompilerTestDef + with AskTypeAt { + + protected val marker = TypeMarker + + override def runTest() { + askAllSources(marker) { pos => + askTypeAt(pos) + } { (pos, tree) => + withResponseDelimiter { + reporter.println("[response] askTypeAt at " + (pos.line, pos.column)) + compiler.ask(() => reporter.println(tree)) + } + } + } + } + + /** Ask the presentation compiler for hyperlink at all locations + * (in all sources) where the defined `marker` is found. */ + class HyperlinkAction(override val compiler: Global) + extends PresentationCompilerTestDef + with AskTypeAt + with AskCompletionAt { + + protected val marker = HyperlinkMarker + + override def runTest() { + askAllSources(marker) { pos => + askTypeAt(pos)(NullReporter) + } { (pos, tree) => + reporter.println("\naskHyperlinkPos for `" + tree.symbol.name + "` at " + ((pos.line, pos.column)) + " " + pos.source.file.name) + val r = new Response[Position] + val sourceFile = sourceFiles.find(tree.symbol.sourceFile.path == _.path) match { + case Some(source) => + compiler.askLinkPos(tree.symbol, source, r) + r.get match { + case Left(pos) => + withResponseDelimiter { + reporter.println("[response] found askHyperlinkPos for `" + tree.symbol.name + "` at " + (pos.line, pos.column) + " " + tree.symbol.sourceFile.name) + } + case Right(ex) => + ex.printStackTrace() + } + case None => + reporter.println("[error] could not locate sourcefile `" + tree.symbol.sourceFile.name + "`." + + "Hint: Does the looked up definition come form a binary?") + } + } + } + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/FindOccurrences.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/FindOccurrences.scala new file mode 100644 index 0000000000..e2a5a3e3ee --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/FindOccurrences.scala @@ -0,0 +1,28 @@ +package scala.tools.nsc +package interactive +package tests.core + +import scala.tools.nsc.util.Position +import scala.tools.nsc.util.SourceFile + +/** Find occurrences of `text` in the passed `sources`. */ +private[core] object FindOccurrences { + + def apply(sources: Seq[SourceFile])(text: String): Map[SourceFile, Seq[Position]] = + allPositionsOf(sources, text) + + /** All positions of the given string in all source files. */ + private def allPositionsOf(sources: Seq[SourceFile], str: String): Map[SourceFile, Seq[Position]] = + (for (s <- sources; p <- positionsOf(s, str)) yield p).groupBy(_.source) + + /** Return all positions of the given str in the given source file. */ + private def positionsOf(source: SourceFile, str: String): Seq[Position] = { + val buf = new collection.mutable.ListBuffer[Position] + var pos = source.content.indexOfSlice(str) + while (pos >= 0) { + buf += source.position(pos - 1) // we need the position before the first character of this marker + pos = source.content.indexOfSlice(str, pos + 1) + } + buf.toList + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala new file mode 100644 index 0000000000..8ccb5aa075 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala @@ -0,0 +1,28 @@ +package scala.tools.nsc +package interactive +package tests.core + +import reporters.StoreReporter + +/** Trait encapsulating the creation of a presentation compiler's instance.*/ +private[tests] trait PresentationCompilerInstance { + protected val settings = new Settings + protected val compilerReporter = new StoreReporter + + protected lazy val compiler: Global = { + prepareSettings(settings) + new Global(settings, compilerReporter) + } + + /** + * Called before instantiating the presentation compiler's instance. + * You should provide an implementation of this method if you need + * to customize the `settings` used to instantiate the presentation compiler. + * */ + protected def prepareSettings(settings: Settings) {} + + protected def printClassPath(implicit reporter: Reporter) { + reporter.println("\tbootClassPath: %s".format(settings.bootclasspath.value)) + reporter.println("\tverbose: %b".format(settings.verbose.value)) + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerRequestsWorkingMode.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerRequestsWorkingMode.scala new file mode 100644 index 0000000000..c274e13976 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerRequestsWorkingMode.scala @@ -0,0 +1,50 @@ +package scala.tools.nsc +package interactive +package tests.core + +import scala.tools.nsc.util.Position +import scala.tools.nsc.util.SourceFile + +trait PresentationCompilerRequestsWorkingMode extends TestResources { + + protected def synchronousRequests: Boolean + + protected def askAllSources[T] = if (synchronousRequests) askAllSourcesSync[T] _ else askAllSourcesAsync[T] _ + + /** Perform an operation on all sources at all positions that match the given + * `marker`. For instance, askAllSources(TypeMarker)(askTypeAt)(println) would + * ask the type at all positions marked with `TypeMarker.marker` and println the result. + */ + private def askAllSourcesAsync[T](marker: TestMarker)(askAt: Position => Response[T])(f: (Position, T) => Unit) { + val positions = allPositionsOf(marker.marker).valuesIterator.toList.flatten + val responses = for (pos <- positions) yield askAt(pos) + + for ((pos, r) <- positions zip responses) withResponse(pos, r)(f) + } + + /** Synchronous version of askAllSources. Each position is treated in turn, waiting for the + * response before going to the next one. + */ + private def askAllSourcesSync[T](marker: TestMarker)(askAt: Position => Response[T])(f: (Position, T) => Unit) { + val positions = allPositionsOf(marker.marker).valuesIterator.toList.flatten + for (pos <- positions) withResponse(pos, askAt(pos))(f) + } + + private def allPositionsOf: String => Map[SourceFile, Seq[Position]] = + FindOccurrences(sourceFiles) _ + + private def withResponse[T](pos: Position, response: Response[T])(f: (Position, T) => Unit) { + /** Return the filename:line:col version of this position. */ + def showPos(pos: Position): String = + "%s:%d:%d".format(pos.source.file.name, pos.line, pos.column) + + response.get(TIMEOUT) match { + case Some(Left(t)) => + f(pos, t) + case None => + println("TIMEOUT: " + showPos(pos)) + case Some(r) => + println("ERROR: " + r) + } + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerTestDef.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerTestDef.scala new file mode 100644 index 0000000000..a9fd7444c1 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerTestDef.scala @@ -0,0 +1,15 @@ +package scala.tools.nsc.interactive.tests.core + +trait PresentationCompilerTestDef { + + protected val marker: TestMarker + + private[tests] def runTest(): Unit + + protected def withResponseDelimiter(block: => Unit)(implicit reporter: Reporter) { + def printDelimiter() = reporter.println("=" * 80) + printDelimiter() + block + printDelimiter() + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/Reporter.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/Reporter.scala new file mode 100644 index 0000000000..631504cda5 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/Reporter.scala @@ -0,0 +1,15 @@ +package scala.tools.nsc.interactive.tests.core + +private[tests] trait Reporter { + def println(msg: Any): Unit +} + +/** Reporter that simply prints all messages in the standard output.*/ +private[tests] object ConsoleReporter extends Reporter { + def println(msg: Any) { Console.println(msg) } +} + +/** Reporter that swallows all passed message. */ +private[tests] object NullReporter extends Reporter { + def println(msg: Any) {} +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/SourcesCollector.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/SourcesCollector.scala new file mode 100644 index 0000000000..518cb7bd76 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/SourcesCollector.scala @@ -0,0 +1,22 @@ +package scala.tools.nsc.interactive.tests.core + +import scala.tools.nsc.util.{SourceFile,BatchSourceFile} +import scala.tools.nsc.io.{AbstractFile,Path} + +private[tests] object SourcesCollector { + import Path._ + type SourceFilter = Path => Boolean + + /** + * All files below `base` directory that pass the `filter`. + * With the default `filter` only .scala and .java files are collected. + * */ + def apply(base: Path, filter: SourceFilter): Array[SourceFile] = { + assert(base.isDirectory) + base.walk.filter(filter).map(source).toArray + } + + private def source(file: Path): SourceFile = source(AbstractFile.getFile(file.toFile)) + private def source(filename: String): SourceFile = source(AbstractFile.getFile(filename)) + private def source(file: AbstractFile): SourceFile = new BatchSourceFile(file) +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/TestMarkers.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/TestMarkers.scala new file mode 100644 index 0000000000..b5ea6ab7ce --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/TestMarkers.scala @@ -0,0 +1,27 @@ +package scala.tools.nsc.interactive.tests.core + +case class DuplicateTestMarker(msg: String) extends Exception(msg) + +object TestMarker { + import collection.mutable.Map + private val markers: Map[String, TestMarker] = Map.empty + + private def checkForDuplicate(marker: TestMarker) { + markers.get(marker.marker) match { + case None => markers(marker.marker) = marker + case Some(otherMarker) => + val msg = "Marker `%s` is already used by %s. Please choose a different marker for %s".format(marker.marker, marker, otherMarker) + throw new DuplicateTestMarker(msg) + } + } +} + +abstract case class TestMarker(val marker: String) { + TestMarker.checkForDuplicate(this) +} + +object CompletionMarker extends TestMarker("/*!*/") + +object TypeMarker extends TestMarker("/*?*/") + +object HyperlinkMarker extends TestMarker("/*#*/")
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/TestResources.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/TestResources.scala new file mode 100644 index 0000000000..106d1dacb3 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/TestResources.scala @@ -0,0 +1,12 @@ +package scala.tools.nsc.interactive.tests.core + +import scala.tools.nsc.io.Path +import scala.tools.nsc.util.SourceFile + +/** Resources used by the test. */ +private[tests] trait TestResources extends TestSettings { + /** collected source files that are to be used by the test runner */ + protected lazy val sourceFiles: Array[SourceFile] = SourcesCollector(baseDir / sourceDir, isScalaOrJavaSource) + + private def isScalaOrJavaSource(file: Path): Boolean = file.extension == "scala" | file.extension == "java" +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/TestSettings.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/TestSettings.scala new file mode 100644 index 0000000000..e92634e731 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/TestSettings.scala @@ -0,0 +1,19 @@ +package scala.tools.nsc.interactive.tests.core + +import scala.tools.nsc.io.Path + +/** Common settings for the test. */ +private[tests] trait TestSettings { + protected final val TIMEOUT = 10000 // timeout in milliseconds + + /** The root directory for this test suite, usually the test kind ("test/files/presentation"). */ + protected val outDir = Path(Option(System.getProperty("partest.cwd")).getOrElse(".")) + + /** The base directory for this test, usually a subdirectory of "test/files/presentation/" */ + protected val baseDir = Option(System.getProperty("partest.testname")).map(outDir / _).getOrElse(Path(".")) + + /** Where source files are placed. */ + protected val sourceDir = "src" + + protected implicit val reporter: Reporter = ConsoleReporter +}
\ No newline at end of file |