summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2014-08-27 14:00:36 +0200
committerLukas Rytz <lukas.rytz@gmail.com>2014-09-10 00:05:11 +0200
commit35c53af7e3bbe19d50845e698c02a49d0a022409 (patch)
tree2d9dce67ed018e48b8342a1bd65773af301870bc
parent44b5c261a8e585c5747380895aa06c84f9d63f6c (diff)
downloadscala-35c53af7e3bbe19d50845e698c02a49d0a022409.tar.gz
scala-35c53af7e3bbe19d50845e698c02a49d0a022409.tar.bz2
scala-35c53af7e3bbe19d50845e698c02a49d0a022409.zip
Tools to run the compiler in JUnit tests
-rw-r--r--src/partest-extras/scala/tools/partest/ASMConverters.scala23
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala34
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala81
3 files changed, 136 insertions, 2 deletions
diff --git a/src/partest-extras/scala/tools/partest/ASMConverters.scala b/src/partest-extras/scala/tools/partest/ASMConverters.scala
index 50057d058c..f4a90d9acf 100644
--- a/src/partest-extras/scala/tools/partest/ASMConverters.scala
+++ b/src/partest-extras/scala/tools/partest/ASMConverters.scala
@@ -3,6 +3,7 @@ package scala.tools.partest
import scala.collection.JavaConverters._
import scala.tools.asm
import asm.{tree => t}
+import scala.tools.asm.tree.LabelNode
/** Makes using ASM from ByteCodeTests more convenient.
*
@@ -22,8 +23,26 @@ object ASMConverters {
asmToScala.convert(insns.iterator.asScala.toList)
}
- implicit class CompareInstructionLists(val self: List[Instruction]) {
+ implicit class RichInstructionLists(val self: List[Instruction]) extends AnyVal {
def === (other: List[Instruction]) = equivalentBytecode(self, other)
+
+ def dropLinesFrames = self.filterNot(i => i.isInstanceOf[LineNumber] || i.isInstanceOf[FrameEntry])
+
+ private def referencedLabels(instruction: Instruction): Set[Instruction] = instruction match {
+ case Jump(op, label) => Set(label)
+ case LookupSwitch(op, dflt, keys, labels) => (dflt :: labels).toSet
+ case TableSwitch(op, min, max, dflt, labels) => (dflt :: labels).toSet
+ case LineNumber(line, start) => Set(start)
+ case _ => Set.empty
+ }
+
+ def dropStaleLabels = {
+ val definedLabels: Set[Instruction] = self.filter(_.isInstanceOf[Label]).toSet
+ val usedLabels: Set[Instruction] = self.flatMap(referencedLabels)(collection.breakOut)
+ self.filterNot(definedLabels diff usedLabels)
+ }
+
+ def dropNonOp = dropLinesFrames.dropStaleLabels
}
sealed abstract class Instruction extends Product {
@@ -89,7 +108,7 @@ object ASMConverters {
case i: t.LdcInsnNode => Ldc (op(i), i.cst: Any)
case i: t.LookupSwitchInsnNode => LookupSwitch (op(i), applyLabel(i.dflt), lst(i.keys) map (x => x: Int), lst(i.labels) map applyLabel)
case i: t.TableSwitchInsnNode => TableSwitch (op(i), i.min, i.max, applyLabel(i.dflt), lst(i.labels) map applyLabel)
- case i: t.MethodInsnNode => Method (op(i), i.desc, i.name, i.owner, i.itf)
+ case i: t.MethodInsnNode => Method (op(i), i.owner, i.name, i.desc, i.itf)
case i: t.MultiANewArrayInsnNode => NewArray (op(i), i.desc, i.dims)
case i: t.TypeInsnNode => TypeOp (op(i), i.desc)
case i: t.VarInsnNode => VarOp (op(i), i.`var`)
diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
index 256dff85c3..8518d5c832 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
@@ -1,7 +1,11 @@
package scala.tools.nsc.backend.jvm
+import scala.reflect.internal.util.BatchSourceFile
+import scala.reflect.io.VirtualDirectory
import scala.tools.asm.Opcodes
import scala.tools.asm.tree.{AbstractInsnNode, LabelNode, ClassNode, MethodNode}
+import scala.tools.cmd.CommandLineParser
+import scala.tools.nsc.{Settings, Global}
import scala.tools.partest.ASMConverters
import scala.collection.JavaConverters._
@@ -24,4 +28,34 @@ object CodeGenTools {
cls.methods.add(method)
cls
}
+
+ private def resetOutput(compiler: Global): Unit = {
+ compiler.settings.outputDirs.setSingleOutput(new VirtualDirectory("(memory)", None))
+ }
+
+ def newCompiler(defaultArgs: String = "-usejavacp", extraArgs: String = ""): Global = {
+ val settings = new Settings()
+ val args = (CommandLineParser tokenize defaultArgs) ++ (CommandLineParser tokenize extraArgs)
+ settings.processArguments(args, processAll = true)
+ val compiler = new Global(settings)
+ resetOutput(compiler)
+ compiler
+ }
+
+ def compile(compiler: Global = newCompiler())(code: String): List[(String, Array[Byte])] = {
+ compiler.reporter.reset()
+ resetOutput(compiler)
+ val run = new compiler.Run()
+ run.compileSources(List(new BatchSourceFile("unitTestSource.scala", code)))
+ val outDir = compiler.settings.outputDirs.getSingleOutput.get
+ (for (f <- outDir.iterator if !f.isDirectory) yield (f.name, f.toByteArray)).toList
+ }
+
+ def compileClasses(compiler: Global = newCompiler())(code: String): List[ClassNode] = {
+ compile(compiler)(code).map(p => AsmUtils.readClass(p._2)).sortBy(_.name)
+ }
+
+ def compileMethods(compiler: Global = newCompiler())(code: String): List[MethodNode] = {
+ compileClasses(compiler)(s"class C { $code }").head.methods.asScala.toList.filterNot(_.name == "<init>")
+ }
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
new file mode 100644
index 0000000000..640f22fc47
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
@@ -0,0 +1,81 @@
+package scala.tools.nsc.backend.jvm
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Assert._
+import CodeGenTools._
+import scala.tools.asm.Opcodes._
+import scala.tools.partest.ASMConverters._
+
+@RunWith(classOf[JUnit4])
+class DirectCompileTest {
+ val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode")
+
+ @Test
+ def testCompile(): Unit = {
+ val List(("C.class", bytes)) = compile(compiler)(
+ """
+ |class C {
+ | def f = 1
+ |}
+ """.stripMargin)
+ def s(i: Int, n: Int) = (bytes(i) & 0xff) << n
+ assertTrue((s(0, 24) | s(1, 16) | s(2, 8) | s(3, 0)) == 0xcafebabe) // mocha java latte machiatto surpreme dark roasted espresso
+ }
+
+ @Test
+ def testCompileClasses(): Unit = {
+ val List(cClass, cModuleClass) = compileClasses(compiler)(
+ """
+ |class C
+ |object C
+ """.stripMargin)
+
+ assertTrue(cClass.name == "C")
+ assertTrue(cModuleClass.name == "C$")
+
+ val List(dMirror, dModuleClass) = compileClasses(compiler)(
+ """
+ |object D
+ """.stripMargin)
+
+ assertTrue(dMirror.name == "D")
+ assertTrue(dModuleClass.name == "D$")
+ }
+
+ @Test
+ def testCompileMethods(): Unit = {
+ val List(f, g) = compileMethods(compiler)(
+ """
+ |def f = 10
+ |def g = f
+ """.stripMargin)
+ assertTrue(f.name == "f")
+ assertTrue(g.name == "g")
+
+ assertTrue(instructionsFromMethod(f).dropNonOp ===
+ List(IntOp(BIPUSH, 10), Op(IRETURN)))
+
+ assertTrue(instructionsFromMethod(g).dropNonOp ===
+ List(VarOp(ALOAD, 0), Method(INVOKEVIRTUAL, "C", "f", "()I", false), Op(IRETURN)))
+ }
+
+ @Test
+ def testDropNonOpAliveLabels(): Unit = {
+ val List(f) = compileMethods(compiler)("""def f(x: Int) = if (x == 0) "a" else "b"""")
+ assertTrue(instructionsFromMethod(f).dropNonOp === List(
+ VarOp(ILOAD, 1),
+ Op(ICONST_0),
+ Jump(IF_ICMPEQ, Label(6)),
+ Jump(GOTO, Label(10)),
+ Label(6),
+ Ldc(LDC, "a"),
+ Jump(GOTO, Label(13)),
+ Label(10),
+ Ldc(LDC, "b"),
+ Label(13),
+ Op(ARETURN)
+ ))
+ }
+}