summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2014-10-28 19:12:29 +1000
committerJason Zaugg <jzaugg@gmail.com>2014-10-28 19:23:50 +1000
commit8f9e9c893893084129ea423ef7ff5d3e79074622 (patch)
treed5cd3fb7dd0ed963f468ce1e8bbfaaf1016b79e0
parent7664e25eed8ae4547f1cfd774b62a7b6a4baf8b5 (diff)
downloadscala-8f9e9c893893084129ea423ef7ff5d3e79074622.tar.gz
scala-8f9e9c893893084129ea423ef7ff5d3e79074622.tar.bz2
scala-8f9e9c893893084129ea423ef7ff5d3e79074622.zip
SI-8941 Deterministic tests for pres. compiler idempotency
A retrospective test case which covers typechecking idempotency which was introduced in 0b78a0196 / 148736c3df. It also tests the implicit class handling, which was fixed in the previous commit. It is difficult to test this using existing presentation compiler testing infrastructure, as one can't control at which point during the first typechecking the subesquent work item will be noticed. Instead, I've created a test with a custom subclass of `interactive.Global` that allows precise, deterministic control of when this happens. It overrides `signalDone`, which is called after each tree is typechecked, and watches for a defintion with a well known name. At that point, it triggers a targetted typecheck of the tree marked with a special comment. It is likely that this approach can be generalized to a reusable base class down the track. In particular, I expect that some of the nasty interactive ScalaDoc bugs could use this single-threaded approach to testing the presentation compiler.
-rw-r--r--src/interactive/scala/tools/nsc/interactive/Global.scala6
-rw-r--r--test/files/presentation/t8941b/IdempotencyTest.scala73
-rw-r--r--test/files/presentation/t8941b/Test.scala53
3 files changed, 130 insertions, 2 deletions
diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala
index e308bc3bf0..7df809b6ff 100644
--- a/src/interactive/scala/tools/nsc/interactive/Global.scala
+++ b/src/interactive/scala/tools/nsc/interactive/Global.scala
@@ -64,6 +64,8 @@ trait InteractiveAnalyzer extends Analyzer {
// that case the definitions that were already attributed as
// well as any default parameters of such methods need to be
// re-entered in the current scope.
+ //
+ // Tested in test/files/presentation/t8941b
override def enterExistingSym(sym: Symbol, tree: Tree): Context = {
if (sym != null && sym.owner.isTerm) {
enterIfNotThere(sym)
@@ -737,7 +739,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "")
}
}
- private def reloadSource(source: SourceFile) {
+ private[interactive] def reloadSource(source: SourceFile) {
val unit = new RichCompilationUnit(source)
unitOfFile(source.file) = unit
toBeRemoved -= source.file
@@ -786,7 +788,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "")
}
/** A fully attributed tree located at position `pos` */
- private def typedTreeAt(pos: Position): Tree = getUnit(pos.source) match {
+ private[interactive] def typedTreeAt(pos: Position): Tree = getUnit(pos.source) match {
case None =>
reloadSources(List(pos.source))
try typedTreeAt(pos)
diff --git a/test/files/presentation/t8941b/IdempotencyTest.scala b/test/files/presentation/t8941b/IdempotencyTest.scala
new file mode 100644
index 0000000000..af01b36898
--- /dev/null
+++ b/test/files/presentation/t8941b/IdempotencyTest.scala
@@ -0,0 +1,73 @@
+package scala.tools.nsc
+package interactive
+package tests.core
+
+import reporters.{Reporter => CompilerReporter}
+import scala.tools.nsc.interactive.InteractiveReporter
+import scala.reflect.internal.util.SourceFile
+
+/** Determistically interrupts typechecking of `code` when a defintion named
+ * `MagicInterruptionMarker` is typechecked, and then performs a targetted
+ * typecheck of the tree at the specal comment marker marker
+ */
+abstract class IdempotencyTest { self =>
+ private val settings = new Settings
+ settings.usejavacp.value = true
+
+ private object Break extends scala.util.control.ControlThrowable
+
+ private val compilerReporter: CompilerReporter = new InteractiveReporter {
+ override def compiler = self.compiler
+ }
+
+ object compiler extends Global(settings, compilerReporter) {
+ override def checkForMoreWork(pos: Position) {
+ }
+ override def signalDone(context: Context, old: Tree, result: Tree) {
+ // println("signalDone: " + old.toString.take(50).replaceAll("\n", "\\n"))
+ if (!interrupted && analyzer.lockedCount == 0 && interruptsEnabled && shouldInterrupt(result)) {
+ interrupted = true
+ val typed = typedTreeAt(markerPosition)
+ checkTypedTree(typed)
+ throw Break
+ }
+ super.signalDone(context, old, result)
+ }
+
+ // we're driving manually using our own thread, disable the check here.
+ override def assertCorrectThread() {}
+ }
+
+ import compiler._
+
+ private var interrupted = false
+
+ // Extension points
+ protected def code: String
+ protected def shouldInterrupt(tree: Tree): Boolean = {
+ tree.symbol != null && tree.symbol.name.toString == "MagicInterruptionMarker"
+ }
+ protected def checkTypedTree(tree: Tree): Unit = {}
+
+
+ private val source: SourceFile = newSourceFile(code)
+ private def markerPosition: Position = source.position(code.indexOf("/*?*/"))
+
+ def assertNoProblems() {
+ val problems = getUnit(source).get.problems
+ assert(problems.isEmpty, problems.mkString("\n"))
+ }
+
+ def show() {
+ reloadSource(source)
+ try {
+ typedTree(source, true)
+ assert(false, "Expected to break out of typechecking.")
+ } catch {
+ case Break => // expected
+ }
+ assertNoProblems()
+ }
+
+ def main(args: Array[String]) { show() }
+}
diff --git a/test/files/presentation/t8941b/Test.scala b/test/files/presentation/t8941b/Test.scala
new file mode 100644
index 0000000000..7269a14286
--- /dev/null
+++ b/test/files/presentation/t8941b/Test.scala
@@ -0,0 +1,53 @@
+import scala.tools.nsc.interactive.tests.core.IdempotencyTest
+
+// At the time of writing this test, removing any part of `enterExistingSym`
+// leads to a failure.
+object Test {
+ def main(args: Array[String]) {
+ test("""
+ object Foo {
+ def term {
+ def foo(c: String = "") = c
+ class MagicInterruptionMarker
+ foo()/*?*/
+ }
+ }
+ """)
+
+ test("""
+ object Foo {
+ def term {
+ def foo = 42
+ class MagicInterruptionMarker
+ foo/*?*/
+ }
+ }
+ """)
+
+ test("""
+ object Foo {
+ def term {
+ lazy val foo = 42
+ class MagicInterruptionMarker
+ foo/*?*/
+ }
+ }
+ """)
+
+ test("""
+ object Foo {
+ implicit class C(val a: String) extends AnyVal
+ class MagicInterruptionMarker
+ ""/*?*/
+ }
+ """)
+ }
+
+ def test(code0: String) {
+ val t = new IdempotencyTest {
+ def code = code0
+ }
+ t.show()
+ }
+}
+