summaryrefslogtreecommitdiff
path: root/src/partest-extras
diff options
context:
space:
mode:
Diffstat (limited to 'src/partest-extras')
-rw-r--r--src/partest-extras/scala/tools/partest/ASMConverters.scala71
-rw-r--r--src/partest-extras/scala/tools/partest/AsmNode.scala61
-rw-r--r--src/partest-extras/scala/tools/partest/BytecodeTest.scala167
-rw-r--r--src/partest-extras/scala/tools/partest/JavapTest.scala26
-rw-r--r--src/partest-extras/scala/tools/partest/ReplTest.scala43
-rw-r--r--src/partest-extras/scala/tools/partest/SigTest.scala52
-rw-r--r--src/partest-extras/scala/tools/partest/Util.scala52
-rw-r--r--src/partest-extras/scala/tools/partest/instrumented/Instrumentation.scala93
-rw-r--r--src/partest-extras/scala/tools/partest/instrumented/Profiler.java82
9 files changed, 647 insertions, 0 deletions
diff --git a/src/partest-extras/scala/tools/partest/ASMConverters.scala b/src/partest-extras/scala/tools/partest/ASMConverters.scala
new file mode 100644
index 0000000000..d618e086f4
--- /dev/null
+++ b/src/partest-extras/scala/tools/partest/ASMConverters.scala
@@ -0,0 +1,71 @@
+package scala.tools.partest
+
+import scala.collection.JavaConverters._
+import scala.tools.asm
+import asm.tree.{ClassNode, MethodNode, InsnList}
+
+/** Makes using ASM from ByteCodeTests more convenient.
+ *
+ * Wraps ASM instructions in case classes so that equals and toString work
+ * for the purpose of bytecode diffing and pretty printing.
+ */
+trait ASMConverters {
+ // wrap ASM's instructions so we get case class-style `equals` and `toString`
+ object instructions {
+ def fromMethod(meth: MethodNode): List[Instruction] = {
+ val insns = meth.instructions
+ val asmToScala = new AsmToScala{ def labelIndex(l: asm.tree.AbstractInsnNode) = insns.indexOf(l) }
+
+ asmToScala.mapOver(insns.iterator.asScala.toList).asInstanceOf[List[Instruction]]
+ }
+
+ sealed abstract class Instruction { def opcode: String }
+ case class Field (opcode: String, desc: String, name: String, owner: String) extends Instruction
+ case class Incr (opcode: String, incr: Int, `var`: Int) extends Instruction
+ case class Op (opcode: String) extends Instruction
+ case class IntOp (opcode: String, operand: Int) extends Instruction
+ case class Jump (opcode: String, label: Label) extends Instruction
+ case class Ldc (opcode: String, cst: Any) extends Instruction
+ case class LookupSwitch (opcode: String, dflt: Label, keys: List[Integer], labels: List[Label]) extends Instruction
+ case class TableSwitch (opcode: String, dflt: Label, max: Int, min: Int, labels: List[Label]) extends Instruction
+ case class Method (opcode: String, desc: String, name: String, owner: String) extends Instruction
+ case class NewArray (opcode: String, desc: String, dims: Int) extends Instruction
+ case class TypeOp (opcode: String, desc: String) extends Instruction
+ case class VarOp (opcode: String, `var`: Int) extends Instruction
+ case class Label (offset: Int) extends Instruction { def opcode: String = "" }
+ case class FrameEntry (local: List[Any], stack: List[Any]) extends Instruction { def opcode: String = "" }
+ case class LineNumber (line: Int, start: Label) extends Instruction { def opcode: String = "" }
+ }
+
+ abstract class AsmToScala {
+ import instructions._
+
+ def labelIndex(l: asm.tree.AbstractInsnNode): Int
+
+ def mapOver(is: List[Any]): List[Any] = is map {
+ case i: asm.tree.AbstractInsnNode => apply(i)
+ case x => x
+ }
+
+ def op(i: asm.tree.AbstractInsnNode) = if (asm.util.Printer.OPCODES.isDefinedAt(i.getOpcode)) asm.util.Printer.OPCODES(i.getOpcode) else "?"
+ def lst[T](xs: java.util.List[T]): List[T] = if (xs == null) Nil else xs.asScala.toList
+ def apply(l: asm.tree.LabelNode): Label = this(l: asm.tree.AbstractInsnNode).asInstanceOf[Label]
+ def apply(x: asm.tree.AbstractInsnNode): Instruction = x match {
+ case i: asm.tree.FieldInsnNode => Field (op(i), i.desc: String, i.name: String, i.owner: String)
+ case i: asm.tree.IincInsnNode => Incr (op(i), i.incr: Int, i.`var`: Int)
+ case i: asm.tree.InsnNode => Op (op(i))
+ case i: asm.tree.IntInsnNode => IntOp (op(i), i.operand: Int)
+ case i: asm.tree.JumpInsnNode => Jump (op(i), this(i.label))
+ case i: asm.tree.LdcInsnNode => Ldc (op(i), i.cst: Any)
+ case i: asm.tree.LookupSwitchInsnNode => LookupSwitch (op(i), this(i.dflt), lst(i.keys), mapOver(lst(i.labels)).asInstanceOf[List[Label]])
+ case i: asm.tree.TableSwitchInsnNode => TableSwitch (op(i), this(i.dflt), i.max: Int, i.min: Int, mapOver(lst(i.labels)).asInstanceOf[List[Label]])
+ case i: asm.tree.MethodInsnNode => Method (op(i), i.desc: String, i.name: String, i.owner: String)
+ case i: asm.tree.MultiANewArrayInsnNode => NewArray (op(i), i.desc: String, i.dims: Int)
+ case i: asm.tree.TypeInsnNode => TypeOp (op(i), i.desc: String)
+ case i: asm.tree.VarInsnNode => VarOp (op(i), i.`var`: Int)
+ case i: asm.tree.LabelNode => Label (labelIndex(x))
+ case i: asm.tree.FrameNode => FrameEntry (mapOver(lst(i.local)), mapOver(lst(i.stack)))
+ case i: asm.tree.LineNumberNode => LineNumber (i.line: Int, this(i.start): Label)
+ }
+ }
+} \ No newline at end of file
diff --git a/src/partest-extras/scala/tools/partest/AsmNode.scala b/src/partest-extras/scala/tools/partest/AsmNode.scala
new file mode 100644
index 0000000000..e6a91498d1
--- /dev/null
+++ b/src/partest-extras/scala/tools/partest/AsmNode.scala
@@ -0,0 +1,61 @@
+package scala.tools.partest
+
+import scala.collection.JavaConverters._
+import scala.tools.asm
+import asm._
+import asm.tree._
+import java.lang.reflect.Modifier
+
+sealed trait AsmNode[+T] {
+ def node: T
+ def access: Int
+ def desc: String
+ def name: String
+ def signature: String
+ def attrs: List[Attribute]
+ def visibleAnnotations: List[AnnotationNode]
+ def invisibleAnnotations: List[AnnotationNode]
+ def characteristics = f"$name%15s $desc%-30s$accessString$sigString"
+ def erasedCharacteristics = f"$name%15s $desc%-30s$accessString"
+
+ private def accessString = if (access == 0) "" else " " + Modifier.toString(access)
+ private def sigString = if (signature == null) "" else " " + signature
+ override def toString = characteristics
+}
+
+object AsmNode {
+ type AsmMethod = AsmNode[MethodNode]
+ type AsmField = AsmNode[FieldNode]
+ type AsmMember = AsmNode[_]
+
+ implicit class ClassNodeOps(val node: ClassNode) {
+ def fieldsAndMethods: List[AsmMember] = {
+ val xs: List[AsmMember] = (
+ node.methods.asScala.toList.map(x => (x: AsmMethod))
+ ++ node.fields.asScala.toList.map(x => (x: AsmField))
+ )
+ xs sortBy (_.characteristics)
+ }
+ }
+ implicit class AsmMethodNode(val node: MethodNode) extends AsmNode[MethodNode] {
+ def access: Int = node.access
+ def desc: String = node.desc
+ def name: String = node.name
+ def signature: String = node.signature
+ def attrs: List[Attribute] = node.attrs.asScala.toList
+ def visibleAnnotations: List[AnnotationNode] = node.visibleAnnotations.asScala.toList
+ def invisibleAnnotations: List[AnnotationNode] = node.invisibleAnnotations.asScala.toList
+ }
+ implicit class AsmFieldNode(val node: FieldNode) extends AsmNode[FieldNode] {
+ def access: Int = node.access
+ def desc: String = node.desc
+ def name: String = node.name
+ def signature: String = node.signature
+ def attrs: List[Attribute] = node.attrs.asScala.toList
+ def visibleAnnotations: List[AnnotationNode] = node.visibleAnnotations.asScala.toList
+ def invisibleAnnotations: List[AnnotationNode] = node.invisibleAnnotations.asScala.toList
+ }
+
+ def apply(node: MethodNode): AsmMethodNode = new AsmMethodNode(node)
+ def apply(node: FieldNode): AsmFieldNode = new AsmFieldNode(node)
+}
diff --git a/src/partest-extras/scala/tools/partest/BytecodeTest.scala b/src/partest-extras/scala/tools/partest/BytecodeTest.scala
new file mode 100644
index 0000000000..7650a892fd
--- /dev/null
+++ b/src/partest-extras/scala/tools/partest/BytecodeTest.scala
@@ -0,0 +1,167 @@
+package scala.tools.partest
+
+import scala.tools.nsc.util.JavaClassPath
+import scala.collection.JavaConverters._
+import scala.tools.asm.{ClassWriter, ClassReader}
+import scala.tools.asm.tree.{ClassNode, MethodNode, InsnList}
+import java.io.{FileOutputStream, FileInputStream, File => JFile, InputStream}
+import AsmNode._
+
+/**
+ * Provides utilities for inspecting bytecode using ASM library.
+ *
+ * HOW TO USE
+ * 1. Create subdirectory in test/files/jvm for your test. Let's name it $TESTDIR.
+ * 2. Create $TESTDIR/BytecodeSrc_1.scala that contains Scala source file that you
+ * want to inspect the bytecode for. The '_1' suffix signals to partest that it
+ * should compile this file first.
+ * 3. Create $TESTDIR/Test.scala:
+ * import scala.tools.partest.BytecodeTest
+ * object Test extends BytecodeTest {
+ * def show {
+ * // your code that inspect ASM trees and prints values
+ * }
+ * }
+ * 4. Create corresponding check file.
+ *
+ * EXAMPLE
+ * See test/files/jvm/bytecode-test-example for an example of bytecode test.
+ *
+ */
+abstract class BytecodeTest extends ASMConverters {
+ import instructions._
+
+ /** produce the output to be compared against a checkfile */
+ protected def show(): Unit
+
+ def main(args: Array[String]): Unit = show
+
+ // asserts
+ def sameBytecode(methA: MethodNode, methB: MethodNode) = {
+ val isa = instructions.fromMethod(methA)
+ val isb = instructions.fromMethod(methB)
+ if (isa == isb) println("bytecode identical")
+ else diffInstructions(isa, isb)
+ }
+
+ // Do these classes have all the same methods, with the same names, access,
+ // descriptors and generic signatures? Method bodies are not considered, and
+ // the names of the classes containing the methods are substituted so they do
+ // not appear as differences.
+ def sameMethodAndFieldSignatures(clazzA: ClassNode, clazzB: ClassNode) =
+ sameCharacteristics(clazzA, clazzB)(_.characteristics)
+
+ // Same as sameMethodAndFieldSignatures, but ignoring generic signatures.
+ // This allows for methods which receive the same descriptor but differing
+ // generic signatures. In particular, this happens with value classes,
+ // which get a generic signature where a method written in terms of the
+ // underlying values does not.
+ def sameMethodAndFieldDescriptors(clazzA: ClassNode, clazzB: ClassNode) =
+ sameCharacteristics(clazzA, clazzB)(_.erasedCharacteristics)
+
+ private def sameCharacteristics(clazzA: ClassNode, clazzB: ClassNode)(f: AsmNode[_] => String): Boolean = {
+ val ms1 = clazzA.fieldsAndMethods.toIndexedSeq
+ val ms2 = clazzB.fieldsAndMethods.toIndexedSeq
+ val name1 = clazzA.name
+ val name2 = clazzB.name
+
+ if (ms1.length != ms2.length) {
+ println(s"Different member counts in $name1 and $name2")
+ false
+ }
+ else (ms1, ms2).zipped forall { (m1, m2) =>
+ val c1 = f(m1)
+ val c2 = f(m2).replaceAllLiterally(name2, name1)
+ if (c1 == c2)
+ println(s"[ok] $m1")
+ else
+ println(s"[fail]\n in $name1: $c1\n in $name2: $c2")
+
+ c1 == c2
+ }
+ }
+
+ // bytecode is equal modulo local variable numbering
+ def equalsModuloVar(a: Instruction, b: Instruction) = (a, b) match {
+ case _ if a == b => true
+ case (VarOp(op1, _), VarOp(op2, _)) if op1 == op2 => true
+ case _ => false
+ }
+
+ def similarBytecode(methA: MethodNode, methB: MethodNode, similar: (Instruction, Instruction) => Boolean) = {
+ val isa = fromMethod(methA)
+ val isb = fromMethod(methB)
+ if (isa == isb) println("bytecode identical")
+ else if ((isa, isb).zipped.forall { case (a, b) => similar(a, b) }) println("bytecode similar")
+ else diffInstructions(isa, isb)
+ }
+
+ def diffInstructions(isa: List[Instruction], isb: List[Instruction]) = {
+ val len = Math.max(isa.length, isb.length)
+ if (len > 0 ) {
+ val width = isa.map(_.toString.length).max
+ val lineWidth = len.toString.length
+ (1 to len) foreach { line =>
+ val isaPadded = isa.map(_.toString) orElse Stream.continually("")
+ val isbPadded = isb.map(_.toString) orElse Stream.continually("")
+ val a = isaPadded(line-1)
+ val b = isbPadded(line-1)
+
+ println(s"""$line${" " * (lineWidth-line.toString.length)} ${if (a==b) "==" else "<>"} $a${" " * (width-a.length)} | $b""")
+ }
+ }
+ }
+
+// loading
+ protected def getMethod(classNode: ClassNode, name: String): MethodNode =
+ classNode.methods.asScala.find(_.name == name) getOrElse
+ sys.error(s"Didn't find method '$name' in class '${classNode.name}'")
+
+ protected def loadClassNode(name: String, skipDebugInfo: Boolean = true): ClassNode = {
+ val classBytes: InputStream = (for {
+ classRep <- classpath.findClass(name)
+ binary <- classRep.binary
+ } yield binary.input) getOrElse sys.error(s"failed to load class '$name'; classpath = $classpath")
+
+ val cr = new ClassReader(classBytes)
+ val cn = new ClassNode()
+ cr.accept(cn, if (skipDebugInfo) ClassReader.SKIP_DEBUG else 0)
+ cn
+ }
+
+ protected lazy val classpath: JavaClassPath = {
+ import scala.tools.nsc.util.ClassPath.DefaultJavaContext
+ import scala.tools.util.PathResolver.Defaults
+ // logic inspired by scala.tools.util.PathResolver implementation
+ val containers = DefaultJavaContext.classesInExpandedPath(Defaults.javaUserClassPath)
+ new JavaClassPath(containers, DefaultJavaContext)
+ }
+}
+
+object BytecodeTest {
+ /** Parse `file` as a class file, transforms the ASM representation with `f`,
+ * and overwrites the orginal file.
+ */
+ def modifyClassFile(file: JFile)(f: ClassNode => ClassNode) {
+ val rfile = new reflect.io.File(file)
+ def readClass: ClassNode = {
+ val cr = new ClassReader(rfile.toByteArray())
+ val cn = new ClassNode()
+ cr.accept(cn, 0)
+ cn
+ }
+
+ def writeClass(cn: ClassNode) {
+ val writer = new ClassWriter(0)
+ cn.accept(writer)
+ val os = rfile.bufferedOutput()
+ try {
+ os.write(writer.toByteArray)
+ } finally {
+ os.close()
+ }
+ }
+
+ writeClass(f(readClass))
+ }
+}
diff --git a/src/partest-extras/scala/tools/partest/JavapTest.scala b/src/partest-extras/scala/tools/partest/JavapTest.scala
new file mode 100644
index 0000000000..3cb3dc6ca8
--- /dev/null
+++ b/src/partest-extras/scala/tools/partest/JavapTest.scala
@@ -0,0 +1,26 @@
+
+package scala.tools.partest
+
+import scala.util.{Try,Success,Failure}
+import java.lang.System.{out => sysout}
+
+/** A trait for testing repl's javap command
+ * or possibly examining its output.
+ */
+abstract class JavapTest extends ReplTest {
+
+ /** Your Assertion Here, whatever you want to bejahen.
+ * Assertions must be satisfied by all flavors of javap
+ * and should not be fragile with respect to compiler output.
+ */
+ def yah(res: Seq[String]): Boolean
+
+ def baddies = List(":javap unavailable", ":javap not yet working")
+
+ // give it a pass if javap is broken
+ override def show() = try {
+ val res = eval().toSeq
+ val unsupported = res exists (s => baddies exists (s contains _))
+ assert ((unsupported || yah(res)), res.mkString("","\n","\n"))
+ } catch { case ae: AssertionError => ae.printStackTrace(sysout) }
+}
diff --git a/src/partest-extras/scala/tools/partest/ReplTest.scala b/src/partest-extras/scala/tools/partest/ReplTest.scala
new file mode 100644
index 0000000000..7cc2dd39a9
--- /dev/null
+++ b/src/partest-extras/scala/tools/partest/ReplTest.scala
@@ -0,0 +1,43 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2013 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.partest
+
+import scala.tools.nsc.Settings
+import scala.tools.nsc.interpreter.ILoop
+import java.lang.reflect.{ Method => JMethod, Field => JField }
+
+/** A trait for testing repl code. It drops the first line
+ * of output because the real repl prints a version number.
+ */
+abstract class ReplTest extends DirectTest {
+ // override to transform Settings object immediately before the finish
+ def transformSettings(s: Settings): Settings = s
+ // final because we need to enforce the existence of a couple settings.
+ final override def settings: Settings = {
+ val s = super.settings
+ // s.Yreplsync.value = true
+ s.Xnojline.value = true
+ transformSettings(s)
+ }
+ def eval() = {
+ val s = settings
+ log("eval(): settings = " + s)
+ ILoop.runForTranscript(code, s).lines drop 1
+ }
+ def show() = eval() foreach println
+}
+
+abstract class SessionTest extends ReplTest {
+ def session: String
+ override final def code = expected filter (_.startsWith(prompt)) map (_.drop(prompt.length)) mkString "\n"
+ def expected = session.stripMargin.lines.toList
+ final def prompt = "scala> "
+ override def show() = {
+ val out = eval().toList
+ if (out.size != expected.size) Console println s"Expected ${expected.size} lines, got ${out.size}"
+ if (out != expected) Console print nest.FileManager.compareContents(expected, out, "expected", "actual")
+ }
+}
diff --git a/src/partest-extras/scala/tools/partest/SigTest.scala b/src/partest-extras/scala/tools/partest/SigTest.scala
new file mode 100644
index 0000000000..fe233a4fb5
--- /dev/null
+++ b/src/partest-extras/scala/tools/partest/SigTest.scala
@@ -0,0 +1,52 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2013 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.partest
+
+import scala.tools.nsc.Settings
+import scala.tools.nsc.interpreter.ILoop
+import java.lang.reflect.{ Method => JMethod, Field => JField }
+import scala.reflect.{ClassTag, classTag}
+
+/** Support code for testing signatures.
+ */
+trait SigTest {
+ def mstr(m: JMethod) = " (m) %s%s".format(
+ m.toGenericString,
+ if (m.isBridge) " (bridge)" else ""
+ )
+ def fstr(f: JField) = " (f) %s".format(f.toGenericString)
+
+ def isObjectMethodName(name: String) = classOf[Object].getMethods exists (_.getName == name)
+
+ def fields[T: ClassTag](p: JField => Boolean) = {
+ val cl = classTag[T].runtimeClass
+ val fs = (cl.getFields ++ cl.getDeclaredFields).distinct sortBy (_.getName)
+
+ fs filter p
+ }
+ def methods[T: ClassTag](p: JMethod => Boolean) = {
+ val cl = classTag[T].runtimeClass
+ val ms = (cl.getMethods ++ cl.getDeclaredMethods).distinct sortBy (x => (x.getName, x.isBridge))
+
+ ms filter p
+ }
+ def allFields[T: ClassTag]() = fields[T](_ => true)
+ def allMethods[T: ClassTag]() = methods[T](m => !isObjectMethodName(m.getName))
+ def fieldsNamed[T: ClassTag](name: String) = fields[T](_.getName == name)
+ def methodsNamed[T: ClassTag](name: String) = methods[T](_.getName == name)
+
+ def allGenericStrings[T: ClassTag]() =
+ (allMethods[T]() map mstr) ++ (allFields[T]() map fstr)
+
+ def genericStrings[T: ClassTag](name: String) =
+ (methodsNamed[T](name) map mstr) ++ (fieldsNamed[T](name) map fstr)
+
+ def show[T: ClassTag](name: String = "") = {
+ println(classTag[T].runtimeClass.getName)
+ if (name == "") allGenericStrings[T]() foreach println
+ else genericStrings[T](name) foreach println
+ }
+}
diff --git a/src/partest-extras/scala/tools/partest/Util.scala b/src/partest-extras/scala/tools/partest/Util.scala
new file mode 100644
index 0000000000..114658b0cd
--- /dev/null
+++ b/src/partest-extras/scala/tools/partest/Util.scala
@@ -0,0 +1,52 @@
+package scala.tools.partest
+
+import scala.language.experimental.macros
+
+object Util {
+ /**
+ * `trace("".isEmpty)` will return `true` and as a side effect print the following to standard out.
+ * {{{
+ * trace> "".isEmpty
+ * res: Boolean = true
+ *
+ * }}}
+ *
+ * An alternative to [[scala.tools.partest.ReplTest]] that avoids the inconvenience of embedding
+ * test code in a string.
+ */
+ def trace[A](a: A) = macro traceImpl[A]
+
+ import scala.reflect.macros.Context
+ def traceImpl[A: c.WeakTypeTag](c: Context)(a: c.Expr[A]): c.Expr[A] = {
+ import c.universe._
+ import definitions._
+
+ // xeno.by: reify shouldn't be used explicitly before the final release of 2.10.0,
+ // because this impairs reflection refactorings
+ //
+ // val exprCode = c.literal(show(a.tree))
+ // val exprType = c.literal(show(a.actualType))
+ // reify {
+ // println(s"trace> ${exprCode.splice}\nres: ${exprType.splice} = ${a.splice}\n")
+ // a.splice
+ // }
+
+ c.Expr(Block(
+ List(Apply(
+ Select(Ident(PredefModule), TermName("println")),
+ List(Apply(
+ Select(Apply(
+ Select(Ident(ScalaPackage), TermName("StringContext")),
+ List(
+ Literal(Constant("trace> ")),
+ Literal(Constant("\\nres: ")),
+ Literal(Constant(" = ")),
+ Literal(Constant("\\n")))),
+ TermName("s")),
+ List(
+ Literal(Constant(show(a.tree))),
+ Literal(Constant(show(a.actualType))),
+ a.tree))))),
+ a.tree))
+ }
+} \ No newline at end of file
diff --git a/src/partest-extras/scala/tools/partest/instrumented/Instrumentation.scala b/src/partest-extras/scala/tools/partest/instrumented/Instrumentation.scala
new file mode 100644
index 0000000000..18dd740208
--- /dev/null
+++ b/src/partest-extras/scala/tools/partest/instrumented/Instrumentation.scala
@@ -0,0 +1,93 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2013 LAMP/EPFL
+ * @author Grzegorz Kossakowski
+ */
+
+package scala.tools.partest.instrumented
+
+import scala.collection.JavaConverters._
+
+case class MethodCallTrace(className: String, methodName: String, methodDescriptor: String) {
+ override def toString(): String = className + "." + methodName + methodDescriptor
+}
+object MethodCallTrace {
+ implicit val ordering: Ordering[MethodCallTrace] = Ordering.by(x => (x.className, x.methodName, x.methodDescriptor))
+}
+
+/**
+ * An object that controls profiling of instrumented byte-code. The instrumentation is achieved
+ * by using `java.lang.instrument` package. The instrumentation agent can be found in
+ * `scala.tools.partest.javaagent` package.
+ *
+ * At the moment the following classes are being instrumented:
+ * * all classes with empty package
+ * * all classes from scala package (except for classes responsible for instrumentation)
+ *
+ * The canonical way of using instrumentation is have a test-case in `files/instrumented` directory.
+ * The following code in main:
+ *
+ * {{{
+ * import scala.tools.partest.instrumented.Instrumentation._
+ * def main(args: Array[String]): Unit = {
+ * startProfiling()
+ * // should box the boolean
+ println(true)
+ stopProfiling()
+ printStatistics()
+ * }
+ * }}}
+ *
+ *
+ * should print:
+ *
+ * {{{
+ * true
+ * Method call statistics:
+ * scala/Predef$.println(Ljava/lang/Object;)V: 1
+ * scala/runtime/BoxesRunTime.boxToBoolean(Z)Ljava/lang/Boolean;: 1
+ * }}}
+ */
+object Instrumentation {
+
+ type Statistics = Map[MethodCallTrace, Int]
+
+ def startProfiling(): Unit = Profiler.startProfiling()
+ def stopProfiling(): Unit = Profiler.stopProfiling()
+ def resetProfiling(): Unit = Profiler.resetProfiling()
+ def isProfiling(): Boolean = Profiler.isProfiling()
+
+ def getStatistics: Statistics = {
+ val isProfiling = Profiler.isProfiling()
+ if (isProfiling) {
+ Profiler.stopProfiling()
+ }
+ val stats = Profiler.getStatistics().asScala.toSeq.map {
+ case (trace, count) => MethodCallTrace(trace.className, trace.methodName, trace.methodDescriptor) -> count.intValue
+ }
+ val res = Map(stats: _*)
+ if (isProfiling) {
+ Profiler.startProfiling()
+ }
+ res
+ }
+
+ val standardFilter: MethodCallTrace => Boolean = t => {
+ // ignore all calls to Console trigger by printing
+ t.className != "scala/Console$" &&
+ // console accesses DynamicVariable, let's discard it too
+ !t.className.startsWith("scala/util/DynamicVariable")
+ }
+
+ // Used in tests.
+ def printStatistics(stats: Statistics = getStatistics, filter: MethodCallTrace => Boolean = standardFilter): Unit = {
+ val stats = getStatistics
+ println("Method call statistics:")
+ val toBePrinted = stats.toSeq.filter(p => filter(p._1)).sortBy(_._1)
+ // <count> <trace>
+ val format = "%5d %s\n"
+ toBePrinted foreach {
+ case (trace, count) => printf(format, count, trace)
+ }
+ }
+
+}
diff --git a/src/partest-extras/scala/tools/partest/instrumented/Profiler.java b/src/partest-extras/scala/tools/partest/instrumented/Profiler.java
new file mode 100644
index 0000000000..d6b62e1d9e
--- /dev/null
+++ b/src/partest-extras/scala/tools/partest/instrumented/Profiler.java
@@ -0,0 +1,82 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2013 LAMP/EPFL
+ * @author Grzegorz Kossakowski
+ */
+
+package scala.tools.partest.instrumented;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A simple profiler class that counts method invocations. It is being used in byte-code instrumentation by inserting
+ * call to {@link Profiler#methodCalled(String, String, String)} at the beginning of every instrumented class.
+ *
+ * WARANING: This class is INTERNAL implementation detail and should never be used directly. It's made public only
+ * because it must be universally accessible for instrumentation needs. If you want to profile your test use
+ * {@link Instrumentation} instead.
+ */
+public class Profiler {
+
+ private static boolean isProfiling = false;
+ private static Map<MethodCallTrace, Integer> counts = new HashMap<MethodCallTrace, Integer>();
+
+ static public class MethodCallTrace {
+ final String className;
+ final String methodName;
+ final String methodDescriptor;
+
+ public MethodCallTrace(final String className, final String methodName, final String methodDescriptor) {
+ this.className = className;
+ this.methodName = methodName;
+ this.methodDescriptor = methodDescriptor;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof MethodCallTrace)) {
+ return false;
+ } else {
+ MethodCallTrace that = (MethodCallTrace) obj;
+ return that.className.equals(className) && that.methodName.equals(methodName) && that.methodDescriptor.equals(methodDescriptor);
+ }
+ }
+ @Override
+ public int hashCode() {
+ return className.hashCode() ^ methodName.hashCode() ^ methodDescriptor.hashCode();
+ }
+ }
+
+ public static void startProfiling() {
+ isProfiling = true;
+ }
+
+ public static void stopProfiling() {
+ isProfiling = false;
+ }
+
+ public static boolean isProfiling() {
+ return isProfiling;
+ }
+
+ public static void resetProfiling() {
+ counts = new HashMap<MethodCallTrace, Integer>();
+ }
+
+ public static void methodCalled(final String className, final String methodName, final String methodDescriptor) {
+ if (isProfiling) {
+ MethodCallTrace trace = new MethodCallTrace(className, methodName, methodDescriptor);
+ Integer counter = counts.get(trace);
+ if (counter == null) {
+ counts.put(trace, 1);
+ } else {
+ counts.put(trace, counter+1);
+ }
+ }
+ }
+
+ public static Map<MethodCallTrace, Integer> getStatistics() {
+ return new HashMap<MethodCallTrace, Integer>(counts);
+ }
+
+}