diff options
author | Lukas Rytz <lukas.rytz@gmail.com> | 2014-08-27 14:00:36 +0200 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@gmail.com> | 2014-09-10 00:05:11 +0200 |
commit | 35c53af7e3bbe19d50845e698c02a49d0a022409 (patch) | |
tree | 2d9dce67ed018e48b8342a1bd65773af301870bc | |
parent | 44b5c261a8e585c5747380895aa06c84f9d63f6c (diff) | |
download | scala-35c53af7e3bbe19d50845e698c02a49d0a022409.tar.gz scala-35c53af7e3bbe19d50845e698c02a49d0a022409.tar.bz2 scala-35c53af7e3bbe19d50845e698c02a49d0a022409.zip |
Tools to run the compiler in JUnit tests
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) + )) + } +} |