diff options
author | Micro Dotta <mirco.dotta@gmail.com> | 2011-08-17 13:32:25 +0000 |
---|---|---|
committer | Micro Dotta <mirco.dotta@gmail.com> | 2011-08-17 13:32:25 +0000 |
commit | 6ba1b9f3c974351e826485ca9c41df732c6de15a (patch) | |
tree | 25c08330ac9c176353cc98f6a3f6cbd0c541d07d /src | |
parent | 044099d4f1677425719ea8cad8c946dab8b5c2d9 (diff) | |
download | scala-6ba1b9f3c974351e826485ca9c41df732c6de15a.tar.gz scala-6ba1b9f3c974351e826485ca9c41df732c6de15a.tar.bz2 scala-6ba1b9f3c974351e826485ca9c41df732c6de15a.zip |
Major rewrite of the testing infrastructure for...
Major rewrite of the testing infrastructure for the presentation
compiler. Added several new tests that will be part of the nightly
build. Once the move to SBT is completed I will look into how to extract
the test infrastructure (as it should really not be living in the
compiler codebase). Review by dragos
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 |