summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala300
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/InteractiveTestSettings.scala67
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/core/AskCommands.scala113
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/core/CoreTestDefs.scala99
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/core/FindOccurrences.scala28
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala28
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerRequestsWorkingMode.scala50
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerTestDef.scala15
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/core/Reporter.scala15
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/core/SourcesCollector.scala22
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/core/TestMarkers.scala27
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/core/TestResources.scala12
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/core/TestSettings.scala19
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