summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala')
-rw-r--r--src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala175
1 files changed, 175 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala b/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala
new file mode 100644
index 0000000000..325151b528
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala
@@ -0,0 +1,175 @@
+package scala.tools.nsc.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 scala.collection.{immutable, mutable}
+
+/** A base class for writing interactive compiler tests.
+ *
+ * This class tries to cover common functionality needed when testing the presentation
+ * 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`.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * @see Check existing tests under test/files/presentation
+ *
+ * @author Iulian Dragos
+ */
+abstract class InteractiveTest {
+
+ val completionMarker = "/*!*/"
+ val typedAtMarker = "/*?*/"
+ val TIMEOUT = 5000 // 5 seconds
+
+ val settings = new Settings
+ val reporter= new StoreReporter
+
+ // need this so that the classpath comes from what partest
+ // instead of scala.home
+ settings.usejavacp.value = true
+
+ /** 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("."))
+
+// settings.YpresentationDebug.value = true
+ lazy val compiler = 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
+ }
+
+ /** 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.
+ */
+ def askAllSources[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) r.get(TIMEOUT) match {
+ case Some(Left(members)) =>
+ f(pos, members)
+ case None =>
+ println("TIMEOUT: " + r)
+ case _ =>
+ println("ERROR: " + r)
+ }
+ }
+
+ /** Ask completion for all marked positions in all sources.
+ * A completion position is marked with /*!*/.
+ */
+ def completionTests() {
+ askAllSources(completionMarker) { pos =>
+ println("askTypeCompletion at " + pos)
+ 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))
+ println(members.sortBy(_.sym.name.toString).mkString("\n"))
+ }
+ }
+
+ /** Ask for typedAt for all marker positions in all sources.
+ */
+ def typeAtTests() {
+ askAllSources(typedAtMarker) { pos =>
+ println("askTypeAt at " + pos)
+ val r = new Response[compiler.Tree]
+ compiler.askTypeAt(pos, r)
+ r
+ } { (pos, tree) =>
+ println("[response] askTypeAt at " + (pos.line, pos.column))
+ println(tree)
+ }
+ }
+
+ /** 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
+ }
+
+ def runTest: Unit = {
+ if (runRandomTests) randomTests(20, sourceFiles)
+ completionTests()
+ typeAtTests()
+ }
+
+ /** Perform n random tests with random changes. */
+ def randomTests(n: Int, files: Array[SourceFile]) {
+ val tester = new Tester(n, files, settings)
+ tester.run()
+ }
+
+ val runRandomTests = true
+
+ def main(args: Array[String]) {
+ reloadSources()
+ runTest
+ compiler.askShutdown()
+ }
+}
+