Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SI-8941 Deterministic tests for pres. compiler idempotency
A retrospective test case which covers typechecking idempptency which was introduced in 0b78a01 / 148736c. 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.
- Loading branch information
Showing
3 changed files
with
130 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
} | ||
} | ||
|