summaryrefslogtreecommitdiff
path: root/test/junit/scala/tools
diff options
context:
space:
mode:
Diffstat (limited to 'test/junit/scala/tools')
-rw-r--r--test/junit/scala/tools/nsc/SampleTest.scala2
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala21
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala50
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala80
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala8
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala99
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala83
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala221
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala60
-rw-r--r--test/junit/scala/tools/nsc/classpath/AggregateFlatClassPathTest.scala208
-rw-r--r--test/junit/scala/tools/nsc/classpath/FlatClassPathResolverTest.scala159
-rw-r--r--test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala18
-rw-r--r--test/junit/scala/tools/nsc/settings/SettingsTest.scala16
-rw-r--r--test/junit/scala/tools/nsc/symtab/CannotHaveAttrsTest.scala12
-rw-r--r--test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala31
-rw-r--r--test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala143
-rw-r--r--test/junit/scala/tools/testing/AssertUtil.scala20
17 files changed, 1157 insertions, 74 deletions
diff --git a/test/junit/scala/tools/nsc/SampleTest.scala b/test/junit/scala/tools/nsc/SampleTest.scala
index 810c88ef9d..60bb09e98f 100644
--- a/test/junit/scala/tools/nsc/SampleTest.scala
+++ b/test/junit/scala/tools/nsc/SampleTest.scala
@@ -11,6 +11,6 @@ import org.junit.runners.JUnit4
class SampleTest {
@Test
def testMath: Unit = {
- assert(2+2 == 4, "you didn't get the math right fellow")
+ assertTrue("you didn't get the math right fellow", 2 + 2 == 4)
}
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
index b892eb36cf..c1c5a71b83 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
@@ -7,6 +7,8 @@ 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.backend.jvm.opt.LocalOpt
+import scala.tools.nsc.settings.{MutableSettings, ScalaSettings}
import scala.tools.nsc.{Settings, Global}
import scala.tools.partest.ASMConverters
import scala.collection.JavaConverters._
@@ -79,4 +81,23 @@ object CodeGenTools {
def getSingleMethod(classNode: ClassNode, name: String): Method =
convertMethod(classNode.methods.asScala.toList.find(_.name == name).get)
+
+ def assertHandlerLabelPostions(h: ExceptionHandler, instructions: List[Instruction], startIndex: Int, endIndex: Int, handlerIndex: Int): Unit = {
+ val insVec = instructions.toVector
+ assertTrue(h.start == insVec(startIndex) && h.end == insVec(endIndex) && h.handler == insVec(handlerIndex))
+ }
+
+ val localOpt = {
+ val settings = new MutableSettings(msg => throw new IllegalArgumentException(msg))
+ settings.processArguments(List("-Yopt:l:method"), processAll = true)
+ new LocalOpt(settings)
+ }
+
+ import scala.language.implicitConversions
+
+ implicit def aliveInstruction(ins: Instruction): (Instruction, Boolean) = (ins, true)
+
+ implicit class MortalInstruction(val ins: Instruction) extends AnyVal {
+ def dead: (Instruction, Boolean) = (ins, false)
+ }
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
index 2fb5bb8052..89900291ca 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
@@ -10,13 +10,12 @@ import scala.tools.partest.ASMConverters._
@RunWith(classOf[JUnit4])
class DirectCompileTest {
- val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode")
+ val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:method")
@Test
def testCompile(): Unit = {
val List(("C.class", bytes)) = compile(compiler)(
- """
- |class C {
+ """class C {
| def f = 1
|}
""".stripMargin)
@@ -26,19 +25,12 @@ class DirectCompileTest {
@Test
def testCompileClasses(): Unit = {
- val List(cClass, cModuleClass) = compileClasses(compiler)(
- """
- |class C
- |object C
- """.stripMargin)
+ val List(cClass, cModuleClass) = compileClasses(compiler)("class C; object C")
assertTrue(cClass.name == "C")
assertTrue(cModuleClass.name == "C$")
- val List(dMirror, dModuleClass) = compileClasses(compiler)(
- """
- |object D
- """.stripMargin)
+ val List(dMirror, dModuleClass) = compileClasses(compiler)("object D")
assertTrue(dMirror.name == "D")
assertTrue(dModuleClass.name == "D$")
@@ -47,35 +39,35 @@ class DirectCompileTest {
@Test
def testCompileMethods(): Unit = {
val List(f, g) = compileMethods(compiler)(
- """
- |def f = 10
+ """def f = 10
|def g = f
""".stripMargin)
assertTrue(f.name == "f")
assertTrue(g.name == "g")
- assertTrue(instructionsFromMethod(f).dropNonOp ===
+ assertSameCode(instructionsFromMethod(f).dropNonOp,
List(IntOp(BIPUSH, 10), Op(IRETURN)))
- assertTrue(instructionsFromMethod(g).dropNonOp ===
- List(VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C", "f", "()I", false), Op(IRETURN)))
+ assertSameCode(instructionsFromMethod(g).dropNonOp,
+ List(VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C", "f", "()I", itf = false), Op(IRETURN)))
}
@Test
def testDropNonOpAliveLabels(): Unit = {
+ // makes sure that dropNoOp doesn't drop labels that are being used
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)
+ assertSameCode(instructionsFromMethod(f).dropLinesFrames, List(
+ Label(0),
+ VarOp(ILOAD, 1),
+ Op(ICONST_0),
+ Jump(IF_ICMPNE,
+ Label(7)),
+ Ldc(LDC, "a"),
+ Op(ARETURN),
+ Label(7),
+ Ldc(LDC, "b"),
+ Op(ARETURN),
+ Label(11)
))
}
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
new file mode 100644
index 0000000000..fc748196d0
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
@@ -0,0 +1,80 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+
+@RunWith(classOf[JUnit4])
+class CompactLocalVariablesTest {
+
+ // recurse-unreachable-jumps is required for eliminating catch blocks, in the first dce round they
+ // are still live.only after eliminating the empty handler the catch blocks become unreachable.
+ val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,recurse-unreachable-jumps,compact-locals")
+ val noCompactVarsCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,recurse-unreachable-jumps")
+
+ @Test
+ def compactUnused(): Unit = {
+ val code =
+ """def f: Double = {
+ | try { }
+ | catch {
+ | case _: Throwable =>
+ | // eliminated by dce
+ | val i = 1
+ | val d = 1d
+ | val f = 1f
+ | val l = 1l
+ | }
+ |
+ | val i = 1 // variable index 1 (it's an instance method, so at index 0 we have `this`)
+ | val d = 1d // 2,3
+ | val f = 1f // 4
+ | val l = 1l // 5,6
+ |
+ | try { }
+ | catch {
+ | case _: Throwable =>
+ | // eliminated by dce
+ | val i = 1
+ | val d = 1d
+ | val f = 1f
+ | val l = 1l
+ | }
+ |
+ | val ii = 1 // 7
+ | val dd = 1d // 8,9
+ | val ff = 1f // 10
+ | val ll = 1l // 11,12
+ |
+ | i + ii + d + dd + f + ff + l + ll
+ |}
+ |""".stripMargin
+
+ val List(noCompact) = compileMethods(noCompactVarsCompiler)(code)
+ val List(withCompact) = compileMethods(methodOptCompiler)(code)
+
+ // code is the same, except for local var indices
+ assertTrue(noCompact.instructions.size == withCompact.instructions.size)
+
+ val varOpSlots = convertMethod(withCompact).instructions collect {
+ case VarOp(_, v) => v
+ }
+ assertTrue(varOpSlots.toString, varOpSlots == List(1, 2, 4, 5, 7, 8, 10, 11, // stores
+ 1, 7, 2, 8, 4, 10, 5, 11)) // loads
+
+ // the local variables descriptor table is cleaned up to remove stale entries after dce,
+ // also when the slots are not compacted
+ assertTrue(noCompact.localVariables.size == withCompact.localVariables.size)
+
+ assertTrue(noCompact.maxLocals == 25)
+ assertTrue(withCompact.maxLocals == 13)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
index 57fa1a7b66..7d83c54b5b 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
@@ -26,7 +26,7 @@ class EmptyExceptionHandlersTest {
Op(RETURN)
)
assertTrue(convertMethod(asmMethod).handlers.length == 1)
- LocalOpt.removeEmptyExceptionHandlers(asmMethod)
+ localOpt.removeEmptyExceptionHandlers(asmMethod)
assertTrue(convertMethod(asmMethod).handlers.isEmpty)
}
@@ -35,12 +35,8 @@ class EmptyExceptionHandlersTest {
val handlers = List(ExceptionHandler(Label(1), Label(2), Label(2), Some(exceptionDescriptor)))
val asmMethod = genMethod(handlers = handlers)(
Label(1), // nops only
- Op(NOP),
- Op(NOP),
Jump(GOTO, Label(3)),
- Op(NOP),
Label(3),
- Op(NOP),
Jump(GOTO, Label(4)),
Label(2), // handler
@@ -51,7 +47,7 @@ class EmptyExceptionHandlersTest {
Op(RETURN)
)
assertTrue(convertMethod(asmMethod).handlers.length == 1)
- LocalOpt.removeEmptyExceptionHandlers(asmMethod)
+ localOpt.removeEmptyExceptionHandlers(asmMethod)
assertTrue(convertMethod(asmMethod).handlers.isEmpty)
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala
new file mode 100644
index 0000000000..8c0168826e
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala
@@ -0,0 +1,99 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+import scala.tools.testing.AssertUtil._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+
+@RunWith(classOf[JUnit4])
+class EmptyLabelsAndLineNumbersTest {
+ @Test
+ def removeEmptyLineNumbers(): Unit = {
+ val ops = List[(Instruction, Boolean)](
+ Label(1),
+ LineNumber(1, Label(1)),
+ Label(2),
+ Label(3),
+ Op(RETURN),
+
+ Label(4),
+ LineNumber(4, Label(4)).dead,
+ LineNumber(5, Label(4)),
+ Op(RETURN),
+
+ Label(5),
+ LineNumber(6, Label(5)).dead,
+ Label(6),
+ Label(7),
+ LineNumber(7, Label(7)),
+ Op(RETURN),
+
+ Label(9),
+ LineNumber(8, Label(9)).dead,
+ Label(10)
+ )
+
+ val method = genMethod()(ops.map(_._1): _*)
+ assertTrue(localOpt.removeEmptyLineNumbers(method))
+ assertSameCode(instructionsFromMethod(method), ops.filter(_._2).map(_._1))
+ }
+
+ @Test
+ def badlyLocatedLineNumbers(): Unit = {
+ def t(ops: Instruction*) =
+ assertThrows[AssertionError](localOpt.removeEmptyLineNumbers(genMethod()(ops: _*)))
+
+ // line numbers have to be right after their referenced label node
+ t(LineNumber(0, Label(1)), Label(1))
+ t(Label(0), Label(1), LineNumber(0, Label(0)))
+ }
+
+ @Test
+ def removeEmptyLabels(): Unit = {
+ val handler = List(ExceptionHandler(Label(4), Label(5), Label(6), Some("java/lang/Throwable")))
+ def ops(target1: Int, target2: Int, target3: Int, target4: Int, target5: Int, target6: Int) = List[(Instruction, Boolean)](
+ Label(1),
+ Label(2).dead,
+ Label(3).dead,
+ LineNumber(3, Label(target1)),
+ VarOp(ILOAD, 1),
+ Jump(IFGE, Label(target2)),
+
+ Label(4),
+ Label(5).dead,
+ Label(6).dead,
+ VarOp(ILOAD, 2),
+ Jump(IFGE, Label(target3)),
+
+ Label(7),
+ Label(8).dead,
+ Label(9).dead,
+ Op(RETURN),
+
+ LookupSwitch(LOOKUPSWITCH, Label(target4), List(1,2), List(Label(target4), Label(target5))),
+ TableSwitch(TABLESWITCH, 1, 2, Label(target4), List(Label(target4), Label(target5))),
+
+ Label(10),
+ LineNumber(10, Label(10)),
+ Label(11).dead,
+ LineNumber(12, Label(target6))
+ )
+
+ val method = genMethod(handlers = handler)(ops(2, 3, 8, 8, 9, 11).map(_._1): _*)
+ assertTrue(localOpt.removeEmptyLabelNodes(method))
+ val m = convertMethod(method)
+ assertSameCode(m.instructions, ops(1, 1, 7, 7, 7, 10).filter(_._2).map(_._1))
+ assertTrue(m.handlers match {
+ case List(ExceptionHandler(Label(4), Label(4), Label(4), _)) => true
+ case _ => false
+ })
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala
new file mode 100644
index 0000000000..5b0f0f238a
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala
@@ -0,0 +1,83 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import scala.tools.testing.AssertUtil._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+
+@RunWith(classOf[JUnit4])
+class MethodLevelOpts {
+ val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method")
+
+ def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1))
+
+ @Test
+ def eliminateEmptyTry(): Unit = {
+ val code = "def f = { try {} catch { case _: Throwable => 0; () }; 1 }"
+ assertSameCode(singleMethodInstructions(methodOptCompiler)(code), wrapInDefault(Op(ICONST_1), Op(IRETURN)))
+ }
+
+ @Test
+ def cannotEliminateLoadBoxedUnit(): Unit = {
+ // the compiler inserts a boxed into the try block. it's therefore non-empty (and live) and not eliminated.
+ val code = "def f = { try {} catch { case _: Throwable => 0 }; 1 }"
+ val m = singleMethod(methodOptCompiler)(code)
+ assertTrue(m.handlers.length == 1)
+ assertSameCode(m.instructions.take(3), List(Label(0), LineNumber(1, Label(0)), Field(GETSTATIC, "scala/runtime/BoxedUnit", "UNIT", "Lscala/runtime/BoxedUnit;")))
+ }
+
+ @Test
+ def inlineThrowInCatchNotTry(): Unit = {
+ // the try block does not contain the `ATHROW` instruction, but in the catch block, `ATHROW` is inlined
+ val code = "def f(e: Exception) = throw { try e catch { case _: Throwable => e } }"
+ val m = singleMethod(methodOptCompiler)(code)
+ assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
+ assertSameCode(m.instructions,
+ wrapInDefault(VarOp(ALOAD, 1), Label(3), Op(ATHROW), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), VarOp(ALOAD, 1), Op(ATHROW))
+ )
+ }
+
+ @Test
+ def inlineReturnInCachtNotTry(): Unit = {
+ val code = "def f: Int = return { try 1 catch { case _: Throwable => 2 } }"
+ // cannot inline the IRETURN into the try block (because RETURN may throw IllegalMonitorState)
+ val m = singleMethod(methodOptCompiler)(code)
+ assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
+ assertSameCode(m.instructions,
+ wrapInDefault(Op(ICONST_1), Label(3), Op(IRETURN), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), Op(ICONST_2), Op(IRETURN)))
+ }
+
+ @Test
+ def simplifyJumpsInTryCatchFinally(): Unit = {
+ val code =
+ """def f: Int =
+ | try {
+ | return 1
+ | } catch {
+ | case _: Throwable =>
+ | return 2
+ | } finally {
+ | return 2
+ | // dead
+ | val x = try 10 catch { case _: Throwable => 11 }
+ | println(x)
+ | }
+ """.stripMargin
+ val m = singleMethod(methodOptCompiler)(code)
+ assertTrue(m.handlers.length == 2)
+ assertSameCode(m.instructions.dropNonOp, // drop line numbers and lables that are only used by line numbers
+
+ // one single label left :-)
+ List(Op(ICONST_1), VarOp(ISTORE, 2), Jump(GOTO, Label(20)), Op(POP), Op(ICONST_2), VarOp(ISTORE, 2), Jump(GOTO, Label(20)), VarOp(ASTORE, 3), Op(ICONST_2), Op(IRETURN), Label(20), Op(ICONST_2), Op(IRETURN))
+ )
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala
new file mode 100644
index 0000000000..360fa1d23d
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala
@@ -0,0 +1,221 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+
+@RunWith(classOf[JUnit4])
+class SimplifyJumpsTest {
+ @Test
+ def simpleGotoReturn(): Unit = {
+ val ops = List(
+ Jump(GOTO, Label(2)), // replaced by RETURN
+ Op(ICONST_1), // need some code, otherwise removeJumpToSuccessor kicks in
+ Op(POP),
+ Label(1), // multiple labels OK
+ Label(2),
+ Label(3),
+ Op(RETURN)
+ )
+ val method = genMethod()(ops: _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), Op(RETURN) :: ops.tail)
+ }
+
+ @Test
+ def simpleGotoThrow(): Unit = {
+ val rest = List(
+ Op(ICONST_1), // need some code, otherwise removeJumpToSuccessor kicks in
+ Op(POP),
+ Label(1),
+ Label(2),
+ Label(3),
+ Op(ATHROW)
+ )
+ val method = genMethod()(
+ Op(ACONST_NULL) ::
+ Jump(GOTO, Label(2)) :: // replaced by ATHROW
+ rest: _*
+ )
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), Op(ACONST_NULL) :: Op(ATHROW) :: rest)
+ }
+
+ @Test
+ def gotoThrowInTry(): Unit = {
+ val handler = List(ExceptionHandler(Label(1), Label(2), Label(4), Some("java/lang/Throwable")))
+ val initialInstrs = List(
+ Label(1),
+ Op(ACONST_NULL),
+ Jump(GOTO, Label(3)), // not by ATHROW (would move the ATHROW into a try block)
+ Label(2),
+ Op(ICONST_1), // need some code, otherwise removeJumpToSuccessor kicks in
+ Op(POP),
+ Label(3),
+ Op(ATHROW),
+ Label(4),
+ Op(POP),
+ Op(RETURN)
+ )
+ val method = genMethod(handlers = handler)(initialInstrs: _*)
+ assertFalse(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), initialInstrs)
+
+ val optMethod = genMethod()(initialInstrs: _*) // no handler
+ assertTrue(localOpt.simplifyJumps(optMethod))
+ assertSameCode(instructionsFromMethod(optMethod).take(3), List(Label(1), Op(ACONST_NULL), Op(ATHROW)))
+ }
+
+ @Test
+ def simplifyBranchOverGoto(): Unit = {
+ val begin = List(
+ VarOp(ILOAD, 1),
+ Jump(IFGE, Label(2))
+ )
+ val rest = List(
+ Jump(GOTO, Label(3)),
+ Label(11), // other labels here are allowed
+ Label(2),
+ VarOp(ILOAD, 1),
+ Op(RETURN),
+ Label(3),
+ VarOp(ILOAD, 1),
+ Op(IRETURN)
+ )
+ val method = genMethod()(begin ::: rest: _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(
+ instructionsFromMethod(method),
+ List(VarOp(ILOAD, 1), Jump(IFLT, Label(3))) ::: rest.tail )
+
+ // no label allowed between begin and rest. if there's another label, then there could be a
+ // branch that label. eliminating the GOTO would change the behavior.
+ val nonOptMethod = genMethod()(begin ::: Label(22) :: rest: _*)
+ assertFalse(localOpt.simplifyJumps(nonOptMethod))
+ }
+
+ @Test
+ def ensureGotoRemoved(): Unit = {
+ def code(jumps: Instruction*) = List(
+ VarOp(ILOAD, 1)) ::: jumps.toList ::: List(
+ Label(2),
+
+ Op(RETURN),
+ Label(3),
+ Op(RETURN)
+ )
+
+ // ensures that the goto is safely removed. ASM supports removing while iterating, but not the
+ // next element of the current. Here, the current is the IFGE, the next is the GOTO.
+ val method = genMethod()(code(Jump(IFGE, Label(2)), Jump(GOTO, Label(3))): _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), code(Jump(IFLT, Label(3))))
+ }
+
+ @Test
+ def removeJumpToSuccessor(): Unit = {
+ val ops = List(
+ Jump(GOTO, Label(1)),
+ Label(11),
+ Label(1),
+ Label(2),
+ VarOp(ILOAD, 1),
+ Op(IRETURN)
+ )
+ val method = genMethod()(ops: _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), ops.tail)
+ }
+
+ @Test
+ def collapseJumpChains(): Unit = {
+ def ops(target1: Int, target2: Int, target3: Int) = List(
+ VarOp(ILOAD, 1),
+ Jump(IFGE, Label(target1)), // initially 1, then 3
+ VarOp(ILOAD, 1),
+ Op(IRETURN),
+
+ Label(2),
+ Jump(GOTO, Label(target3)),
+
+ Label(1),
+ Jump(GOTO, Label(target2)), // initially 2, then 3
+
+ VarOp(ILOAD, 1), // some code to prevent jumpToSuccessor optimization (once target2 is replaced by 3)
+ Op(RETURN),
+
+ Label(3),
+ VarOp(ILOAD, 1),
+ Op(IRETURN)
+ )
+ val method = genMethod()(ops(1, 2, 3): _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), ops(3, 3, 3))
+ }
+
+ @Test
+ def collapseJumpChainLoop(): Unit = {
+ def ops(target: Int) = List(
+ VarOp(ILOAD, 1),
+ Jump(IFGE, Label(target)),
+
+ Label(4),
+ Jump(GOTO, Label(3)),
+
+ VarOp(ILOAD, 1), // some code to prevent jumpToSuccessor (label 3)
+ Op(IRETURN),
+
+ Label(3),
+ Jump(GOTO, Label(4)),
+
+ Label(2),
+ Jump(GOTO, Label(3))
+ )
+
+ val method = genMethod()(ops(2): _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), ops(3))
+ }
+
+ @Test
+ def simplifyThenElseSameTarget(): Unit = {
+ def ops(jumpOp: Instruction) = List(
+ VarOp(ILOAD, 1),
+ jumpOp,
+ Label(2),
+ Jump(GOTO, Label(1)),
+
+ VarOp(ILOAD, 1), // some code to prevent jumpToSuccessor (label 1)
+ Op(IRETURN),
+
+ Label(1),
+ VarOp(ILOAD, 1),
+ Op(IRETURN)
+ )
+
+ val method = genMethod()(ops(Jump(IFGE, Label(1))): _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), ops(Op(POP)))
+ }
+
+ @Test
+ def thenElseSameTargetLoop(): Unit = {
+ def ops(br: List[Instruction]) = List(
+ VarOp(ILOAD, 1),
+ VarOp(ILOAD, 2)) ::: br ::: List(
+ Label(1),
+ Jump(GOTO, Label(1))
+ )
+ val method = genMethod()(ops(List(Jump(IF_ICMPGE, Label(1)))): _*)
+ assertTrue(localOpt.simplifyJumps(method))
+ assertSameCode(instructionsFromMethod(method), ops(List(Op(POP), Op(POP))))
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
index a3bd7ae6fe..4a45dd9138 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
@@ -16,12 +16,20 @@ import ASMConverters._
@RunWith(classOf[JUnit4])
class UnreachableCodeTest {
- import UnreachableCodeTest._
+
+ def assertEliminateDead(code: (Instruction, Boolean)*): Unit = {
+ val method = genMethod()(code.map(_._1): _*)
+ localOpt.removeUnreachableCodeImpl(method, "C")
+ val nonEliminated = instructionsFromMethod(method)
+ val expectedLive = code.filter(_._2).map(_._1).toList
+ assertSameCode(nonEliminated, expectedLive)
+ }
// jvm-1.6 enables emitting stack map frames, which impacts the code generation wrt dead basic blocks,
// see comment in BCodeBodyBuilder
- val dceCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code")
- val noOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:none")
+ val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method")
+ val dceCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code")
+ val noOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:none")
// jvm-1.5 disables computing stack map frames, and it emits dead code as-is.
val noOptNoFramesCompiler = newCompiler(extraArgs = "-target:jvm-1.5 -Ybackend:GenBCode -Yopt:l:none")
@@ -48,8 +56,8 @@ class UnreachableCodeTest {
@Test
def eliminateNop(): Unit = {
assertEliminateDead(
- // not dead, since visited by data flow analysis. need a different opt to eliminate it.
- Op(NOP),
+ // reachable, but removed anyway.
+ Op(NOP).dead,
Op(RETURN),
Op(NOP).dead
)
@@ -136,28 +144,31 @@ class UnreachableCodeTest {
@Test
def eliminateDeadCatchBlocks(): Unit = {
+ // the Label(1) is live: it's used in the local variable descriptor table (local variable "this" has a range from 0 to 1).
+ def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1))
+
val code = "def f: Int = { return 0; try { 1 } catch { case _: Exception => 2 } }"
- assertSameCode(singleMethodInstructions(dceCompiler)(code).dropNonOp,
- List(Op(ICONST_0), Op(IRETURN)))
+ val m = singleMethod(dceCompiler)(code)
+ assertTrue(m.handlers.isEmpty) // redundant (if code is gone, handler is gone), but done once here for extra safety
+ assertSameCode(m.instructions,
+ wrapInDefault(Op(ICONST_0), Op(IRETURN)))
val code2 = "def f: Unit = { try { } catch { case _: Exception => () }; () }"
- // DCE only removes dead basic blocks, but not NOPs, and also not useless jumps
- assertSameCode(singleMethodInstructions(dceCompiler)(code2).dropNonOp,
- List(Op(NOP), Jump(GOTO, Label(33)), Label(33), Op(RETURN)))
+ // requires fixpoint optimization of methodOptCompiler (dce alone is not enough): first the handler is eliminated, then it's dead catch block.
+ assertSameCode(singleMethodInstructions(methodOptCompiler)(code2), wrapInDefault(Op(RETURN)))
val code3 = "def f: Unit = { try { } catch { case _: Exception => try { } catch { case _: Exception => () } }; () }"
- assertSameCode(singleMethodInstructions(dceCompiler)(code3).dropNonOp,
- List(Op(NOP), Jump(GOTO, Label(33)), Label(33), Op(RETURN)))
+ assertSameCode(singleMethodInstructions(methodOptCompiler)(code3), wrapInDefault(Op(RETURN)))
+ // this example requires two iterations to get rid of the outer handler.
+ // the first iteration of DCE cannot remove the inner handler. then the inner (empty) handler is removed.
+ // then the second iteration of DCE removes the inner catch block, and then the outer handler is removed.
val code4 = "def f: Unit = { try { try { } catch { case _: Exception => () } } catch { case _: Exception => () }; () }"
- assertSameCode(singleMethodInstructions(dceCompiler)(code4).dropNonOp,
- List(Op(NOP), Jump(GOTO, Label(4)), Label(4), Jump(GOTO, Label(7)), Label(7), Op(RETURN)))
+ assertSameCode(singleMethodInstructions(methodOptCompiler)(code4), wrapInDefault(Op(RETURN)))
}
@Test // test the dce-testing tools
def metaTest(): Unit = {
- assertEliminateDead() // no instructions
-
assertThrows[AssertionError](
assertEliminateDead(Op(RETURN).dead),
_.contains("Expected: List()\nActual : List(Op(RETURN))")
@@ -198,20 +209,3 @@ class UnreachableCodeTest {
List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(1)), List("java/lang/Object", Label(3))), Label(1), Label(3)))
}
}
-
-object UnreachableCodeTest {
- import scala.language.implicitConversions
- implicit def aliveInstruction(ins: Instruction): (Instruction, Boolean) = (ins, true)
-
- implicit class MortalInstruction(val ins: Instruction) extends AnyVal {
- def dead: (Instruction, Boolean) = (ins, false)
- }
-
- def assertEliminateDead(code: (Instruction, Boolean)*): Unit = {
- val cls = wrapInClass(genMethod()(code.map(_._1): _*))
- LocalOpt.removeUnreachableCode(cls)
- val nonEliminated = instructionsFromMethod(cls.methods.get(0))
- val expectedLive = code.filter(_._2).map(_._1).toList
- assertSameCode(nonEliminated, expectedLive)
- }
-}
diff --git a/test/junit/scala/tools/nsc/classpath/AggregateFlatClassPathTest.scala b/test/junit/scala/tools/nsc/classpath/AggregateFlatClassPathTest.scala
new file mode 100644
index 0000000000..9a004d5e0e
--- /dev/null
+++ b/test/junit/scala/tools/nsc/classpath/AggregateFlatClassPathTest.scala
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2014 Contributor. All rights reserved.
+ */
+package scala.tools.nsc.classpath
+
+import java.net.URL
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import scala.reflect.io.VirtualFile
+import scala.tools.nsc.io.AbstractFile
+
+/**
+ * Tests whether AggregateFlatClassPath returns correct entries taken from
+ * cp instances used during creating it and whether it preserves the ordering
+ * (in the case of the repeated entry for a class or a source it returns the first one).
+ */
+@RunWith(classOf[JUnit4])
+class AggregateFlatClassPathTest {
+
+ private class TestFlatClassPath extends FlatClassPath {
+ override def packages(inPackage: String): Seq[PackageEntry] = unsupported
+ override def sources(inPackage: String): Seq[SourceFileEntry] = unsupported
+ override def classes(inPackage: String): Seq[ClassFileEntry] = unsupported
+
+ override def list(inPackage: String): FlatClassPathEntries = unsupported
+ override def findClassFile(name: String): Option[AbstractFile] = unsupported
+
+ override def asClassPathStrings: Seq[String] = unsupported
+ override def asSourcePathString: String = unsupported
+ override def asURLs: Seq[URL] = unsupported
+ }
+
+ private case class TestClassPath(virtualPath: String, classesInPackage: EntryNamesInPackage*) extends TestFlatClassPath {
+
+ override def classes(inPackage: String): Seq[ClassFileEntry] =
+ for {
+ entriesWrapper <- classesInPackage if entriesWrapper.inPackage == inPackage
+ name <- entriesWrapper.names
+ } yield classFileEntry(virtualPath, inPackage, name)
+
+ override def sources(inPackage: String): Seq[SourceFileEntry] = Nil
+
+ // we'll ignore packages
+ override def list(inPackage: String): FlatClassPathEntries = FlatClassPathEntries(Nil, classes(inPackage))
+ }
+
+ private case class TestSourcePath(virtualPath: String, sourcesInPackage: EntryNamesInPackage*) extends TestFlatClassPath {
+
+ override def sources(inPackage: String): Seq[SourceFileEntry] =
+ for {
+ entriesWrapper <- sourcesInPackage if entriesWrapper.inPackage == inPackage
+ name <- entriesWrapper.names
+ } yield sourceFileEntry(virtualPath, inPackage, name)
+
+ override def classes(inPackage: String): Seq[ClassFileEntry] = Nil
+
+ // we'll ignore packages
+ override def list(inPackage: String): FlatClassPathEntries = FlatClassPathEntries(Nil, sources(inPackage))
+ }
+
+ private case class EntryNamesInPackage(inPackage: String)(val names: String*)
+
+ private val dir1 = "./dir1"
+ private val dir2 = "./dir2"
+ private val dir3 = "./dir3"
+ private val dir4 = ""
+
+ private val pkg1 = "pkg1"
+ private val pkg2 = "pkg2"
+ private val pkg3 = "pkg1.nested"
+ private val nonexistingPkg = "nonexisting"
+
+ private def unsupported = throw new UnsupportedOperationException
+
+ private def classFileEntry(pathPrefix: String, inPackage: String, fileName: String) =
+ ClassFileEntryImpl(classFile(pathPrefix, inPackage, fileName))
+
+ private def sourceFileEntry(pathPrefix: String, inPackage: String, fileName: String) =
+ SourceFileEntryImpl(sourceFile(pathPrefix, inPackage, fileName))
+
+ private def classFile(pathPrefix: String, inPackage: String, fileName: String) =
+ virtualFile(pathPrefix, inPackage, fileName, ".class")
+
+ private def sourceFile(pathPrefix: String, inPackage: String, fileName: String) =
+ virtualFile(pathPrefix, inPackage, fileName, ".scala")
+
+ private def virtualFile(pathPrefix: String, inPackage: String, fileName: String, extension: String) = {
+ val packageDirs =
+ if (inPackage == FlatClassPath.RootPackage) ""
+ else inPackage.split('.').mkString("/", "/", "")
+ new VirtualFile(fileName + extension, s"$pathPrefix$packageDirs/$fileName$extension")
+ }
+
+ private def createDefaultTestClasspath() = {
+ val partialClassPaths = Seq(TestSourcePath(dir1, EntryNamesInPackage(pkg1)("F", "A", "G")),
+ TestClassPath(dir2, EntryNamesInPackage(pkg1)("C", "B", "A"), EntryNamesInPackage(pkg2)("D", "A", "E")),
+ TestClassPath(dir3, EntryNamesInPackage(pkg1)("A", "D", "F")),
+ TestSourcePath(dir4, EntryNamesInPackage(pkg2)("A", "H", "I"), EntryNamesInPackage(pkg1)("A")),
+ TestSourcePath(dir2, EntryNamesInPackage(pkg3)("J", "K", "L"))
+ )
+
+ AggregateFlatClassPath(partialClassPaths)
+ }
+
+ @Test
+ def testGettingPackages: Unit = {
+ case class ClassPathWithPackages(packagesInPackage: EntryNamesInPackage*) extends TestFlatClassPath {
+ override def packages(inPackage: String): Seq[PackageEntry] =
+ packagesInPackage.find(_.inPackage == inPackage).map(_.names).getOrElse(Nil) map PackageEntryImpl
+ }
+
+ val partialClassPaths = Seq(ClassPathWithPackages(EntryNamesInPackage(pkg1)("pkg1.a", "pkg1.d", "pkg1.f")),
+ ClassPathWithPackages(EntryNamesInPackage(pkg1)("pkg1.c", "pkg1.b", "pkg1.a"),
+ EntryNamesInPackage(pkg2)("pkg2.d", "pkg2.a", "pkg2.e"))
+ )
+ val cp = AggregateFlatClassPath(partialClassPaths)
+
+ val packagesInPkg1 = Seq("pkg1.a", "pkg1.d", "pkg1.f", "pkg1.c", "pkg1.b")
+ assertEquals(packagesInPkg1, cp.packages(pkg1).map(_.name))
+
+ val packagesInPkg2 = Seq("pkg2.d", "pkg2.a", "pkg2.e")
+ assertEquals(packagesInPkg2, cp.packages(pkg2).map(_.name))
+
+ assertEquals(Seq.empty, cp.packages(nonexistingPkg))
+ }
+
+ @Test
+ def testGettingClasses: Unit = {
+ val cp = createDefaultTestClasspath()
+
+ val classesInPkg1 = Seq(classFileEntry(dir2, pkg1, "C"),
+ classFileEntry(dir2, pkg1, "B"),
+ classFileEntry(dir2, pkg1, "A"),
+ classFileEntry(dir3, pkg1, "D"),
+ classFileEntry(dir3, pkg1, "F")
+ )
+ assertEquals(classesInPkg1, cp.classes(pkg1))
+
+ val classesInPkg2 = Seq(classFileEntry(dir2, pkg2, "D"),
+ classFileEntry(dir2, pkg2, "A"),
+ classFileEntry(dir2, pkg2, "E")
+ )
+ assertEquals(classesInPkg2, cp.classes(pkg2))
+
+ assertEquals(Seq.empty, cp.classes(pkg3))
+ assertEquals(Seq.empty, cp.classes(nonexistingPkg))
+ }
+
+ @Test
+ def testGettingSources: Unit = {
+ val partialClassPaths = Seq(TestClassPath(dir1, EntryNamesInPackage(pkg1)("F", "A", "G")),
+ TestSourcePath(dir2, EntryNamesInPackage(pkg1)("C", "B", "A"), EntryNamesInPackage(pkg2)("D", "A", "E")),
+ TestSourcePath(dir3, EntryNamesInPackage(pkg1)("A", "D", "F")),
+ TestClassPath(dir4, EntryNamesInPackage(pkg2)("A", "H", "I")),
+ TestClassPath(dir2, EntryNamesInPackage(pkg3)("J", "K", "L"))
+ )
+ val cp = AggregateFlatClassPath(partialClassPaths)
+
+ val sourcesInPkg1 = Seq(sourceFileEntry(dir2, pkg1, "C"),
+ sourceFileEntry(dir2, pkg1, "B"),
+ sourceFileEntry(dir2, pkg1, "A"),
+ sourceFileEntry(dir3, pkg1, "D"),
+ sourceFileEntry(dir3, pkg1, "F")
+ )
+ assertEquals(sourcesInPkg1, cp.sources(pkg1))
+
+ val sourcesInPkg2 = Seq(sourceFileEntry(dir2, pkg2, "D"),
+ sourceFileEntry(dir2, pkg2, "A"),
+ sourceFileEntry(dir2, pkg2, "E")
+ )
+ assertEquals(sourcesInPkg2, cp.sources(pkg2))
+
+ assertEquals(Seq.empty, cp.sources(pkg3))
+ assertEquals(Seq.empty, cp.sources(nonexistingPkg))
+ }
+
+ @Test
+ def testList: Unit = {
+ val cp = createDefaultTestClasspath()
+
+ val classesAndSourcesInPkg1 = Seq(
+ ClassAndSourceFilesEntry(classFile(dir3, pkg1, "F"), sourceFile(dir1, pkg1, "F")),
+ ClassAndSourceFilesEntry(classFile(dir2, pkg1, "A"), sourceFile(dir1, pkg1, "A")),
+ sourceFileEntry(dir1, pkg1, "G"),
+ classFileEntry(dir2, pkg1, "C"),
+ classFileEntry(dir2, pkg1, "B"),
+ classFileEntry(dir3, pkg1, "D")
+ )
+ assertEquals(classesAndSourcesInPkg1, cp.list(pkg1).classesAndSources)
+
+ assertEquals(FlatClassPathEntries(Nil, Nil), cp.list(nonexistingPkg))
+ }
+
+ @Test
+ def testFindClass: Unit = {
+ val cp = createDefaultTestClasspath()
+
+ assertEquals(
+ Some(ClassAndSourceFilesEntry(classFile(dir2, pkg1, "A"), sourceFile(dir1, pkg1, "A"))),
+ cp.findClass(s"$pkg1.A")
+ )
+ assertEquals(Some(classFileEntry(dir3, pkg1, "D")), cp.findClass(s"$pkg1.D"))
+ assertEquals(Some(sourceFileEntry(dir2, pkg3, "L")), cp.findClass(s"$pkg3.L"))
+ assertEquals(None, cp.findClass("Nonexisting"))
+ }
+}
diff --git a/test/junit/scala/tools/nsc/classpath/FlatClassPathResolverTest.scala b/test/junit/scala/tools/nsc/classpath/FlatClassPathResolverTest.scala
new file mode 100644
index 0000000000..a37ba31b31
--- /dev/null
+++ b/test/junit/scala/tools/nsc/classpath/FlatClassPathResolverTest.scala
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2014 Contributor. All rights reserved.
+ */
+package scala.tools.nsc.classpath
+
+import java.io.File
+import org.junit.Assert._
+import org.junit._
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import scala.annotation.tailrec
+import scala.tools.nsc.io.AbstractFile
+import scala.tools.nsc.util.ClassPath
+import scala.tools.nsc.Settings
+import scala.tools.util.FlatClassPathResolver
+import scala.tools.util.PathResolver
+
+@RunWith(classOf[JUnit4])
+class FlatClassPathResolverTest {
+
+ val tempDir = new TemporaryFolder()
+
+ private val packagesToTest = List(FlatClassPath.RootPackage, "scala", "scala.reflect", "scala.reflect.io")
+ private val classFilesToFind = List("scala.tools.util.FlatClassPathResolver",
+ "scala.reflect.io.AbstractFile",
+ "scala.collection.immutable.List",
+ "scala.Option",
+ "scala.collection.immutable.Vector",
+ "scala.util.hashing.MurmurHash3",
+ "java.lang.Object",
+ "java.util.Date")
+
+ private val classesToFind = classFilesToFind ++ List("TestSourceInRootPackage",
+ "scala.reflect.io.TestScalaSource",
+ "scala.reflect.io.TestJavaSource")
+
+ private val settings = new Settings
+
+ @Before
+ def initTempDirAndSourcePath: Unit = {
+ // In Java TemporaryFolder in JUnit is managed automatically using @Rule.
+ // It would work also in Scala after adding and extending a class like
+ // TestWithTempFolder.java containing it. But in this case it doesn't work when running tests
+ // from the command line - java class is not compiled due to some, misterious reasons.
+ // That's why such dirs are here created and deleted manually.
+ tempDir.create()
+ tempDir.newFile("TestSourceInRootPackage.scala")
+ val ioDir = tempDir.newFolder("scala", "reflect", "io")
+ new File(ioDir, "AbstractFile.scala").createNewFile()
+ new File(ioDir, "ZipArchive.java").createNewFile()
+ new File(ioDir, "TestScalaSource.scala").createNewFile()
+ new File(ioDir, "TestJavaSource.java").createNewFile()
+
+ settings.usejavacp.value = true
+ settings.sourcepath.value = tempDir.getRoot.getAbsolutePath
+ }
+
+ @After
+ def deleteTempDir: Unit = tempDir.delete()
+
+ private def createFlatClassPath(settings: Settings) =
+ new FlatClassPathResolver(settings).result
+
+ @Test
+ def testEntriesFromListOperationAgainstSeparateMethods: Unit = {
+ val classPath = createFlatClassPath(settings)
+
+ def compareEntriesInPackage(inPackage: String): Unit = {
+ val packages = classPath.packages(inPackage)
+ val classes = classPath.classes(inPackage)
+ val sources = classPath.sources(inPackage)
+ val FlatClassPathEntries(packagesFromList, classesAndSourcesFromList) = classPath.list(inPackage)
+
+ val packageNames = packages.map(_.name).sorted
+ val packageNamesFromList = packagesFromList.map(_.name).sorted
+ assertEquals(s"Methods list and packages for package '$inPackage' should return the same packages",
+ packageNames, packageNamesFromList)
+
+ val classFileNames = classes.map(_.name).sorted
+ val classFileNamesFromList = classesAndSourcesFromList.filter(_.binary.isDefined).map(_.name).sorted
+ assertEquals(s"Methods list and classes for package '$inPackage' should return entries for the same class files",
+ classFileNames, classFileNamesFromList)
+
+ val sourceFileNames = sources.map(_.name).sorted
+ val sourceFileNamesFromList = classesAndSourcesFromList.filter(_.source.isDefined).map(_.name).sorted
+ assertEquals(s"Methods list and sources for package '$inPackage' should return entries for the same source files",
+ sourceFileNames, sourceFileNamesFromList)
+
+ val uniqueNamesOfClassAndSourceFiles = (classFileNames ++ sourceFileNames).toSet
+ assertEquals(s"Class and source entries with the same name obtained via list for package '$inPackage' should be merged into one containing both files",
+ uniqueNamesOfClassAndSourceFiles.size, classesAndSourcesFromList.length)
+ }
+
+ packagesToTest foreach compareEntriesInPackage
+ }
+
+ @Test
+ def testCreatedEntriesAgainstRecursiveClassPath: Unit = {
+ val flatClassPath = createFlatClassPath(settings)
+ val recursiveClassPath = new PathResolver(settings).result
+
+ def compareEntriesInPackage(inPackage: String): Unit = {
+
+ @tailrec
+ def traverseToPackage(packageNameParts: Seq[String], cp: ClassPath[AbstractFile]): ClassPath[AbstractFile] = {
+ packageNameParts match {
+ case Nil => cp
+ case h :: t =>
+ cp.packages.find(_.name == h) match {
+ case Some(nestedCp) => traverseToPackage(t, nestedCp)
+ case _ => throw new Exception(s"There's no package $inPackage in recursive classpath - error when searching for '$h'")
+ }
+ }
+ }
+
+ val packageNameParts = if (inPackage == FlatClassPath.RootPackage) Nil else inPackage.split('.').toList
+ val recursiveClassPathInPackage = traverseToPackage(packageNameParts, recursiveClassPath)
+
+ val flatCpPackages = flatClassPath.packages(inPackage).map(_.name)
+ val pkgPrefix = PackageNameUtils.packagePrefix(inPackage)
+ val recursiveCpPackages = recursiveClassPathInPackage.packages.map(pkgPrefix + _.name)
+ assertEquals(s"Packages in package '$inPackage' on flat cp should be the same as on the recursive cp",
+ recursiveCpPackages, flatCpPackages)
+
+ val flatCpSources = flatClassPath.sources(inPackage).map(_.name).sorted
+ val recursiveCpSources = recursiveClassPathInPackage.classes
+ .filter(_.source.nonEmpty)
+ .map(_.name).sorted
+ assertEquals(s"Source entries in package '$inPackage' on flat cp should be the same as on the recursive cp",
+ recursiveCpSources, flatCpSources)
+
+ val flatCpClasses = flatClassPath.classes(inPackage).map(_.name).sorted
+ val recursiveCpClasses = recursiveClassPathInPackage.classes
+ .filter(_.binary.nonEmpty)
+ .map(_.name).sorted
+ assertEquals(s"Class entries in package '$inPackage' on flat cp should be the same as on the recursive cp",
+ recursiveCpClasses, flatCpClasses)
+ }
+
+ packagesToTest foreach compareEntriesInPackage
+ }
+
+ @Test
+ def testFindClassFile: Unit = {
+ val classPath = createFlatClassPath(settings)
+ classFilesToFind foreach { className =>
+ assertTrue(s"File for $className should be found", classPath.findClassFile(className).isDefined)
+ }
+ }
+
+ @Test
+ def testFindClass: Unit = {
+ val classPath = createFlatClassPath(settings)
+ classesToFind foreach { className =>
+ assertTrue(s"File for $className should be found", classPath.findClass(className).isDefined)
+ }
+ }
+}
diff --git a/test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala b/test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala
new file mode 100644
index 0000000000..77a2da828e
--- /dev/null
+++ b/test/junit/scala/tools/nsc/settings/ScalaVersionTest.scala
@@ -0,0 +1,18 @@
+package scala.tools.nsc
+package settings
+
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import scala.tools.testing.AssertUtil.assertThrows
+
+@RunWith(classOf[JUnit4])
+class ScalaVersionTest {
+ // SI-8711
+ @Test def versionUnparse() {
+ val v = "2.11.3"
+
+ assertEquals(ScalaVersion(v).unparse, v)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/settings/SettingsTest.scala b/test/junit/scala/tools/nsc/settings/SettingsTest.scala
index eda0c27834..96f83c4c2f 100644
--- a/test/junit/scala/tools/nsc/settings/SettingsTest.scala
+++ b/test/junit/scala/tools/nsc/settings/SettingsTest.scala
@@ -164,4 +164,20 @@ class SettingsTest {
assertThrows[IllegalArgumentException](check("-m:a,b,-ab")(_ => true), _ contains "'ab' cannot be negated")
assertThrows[IllegalArgumentException](check("-m:a,ac,-uber,uber")(_ => true), _ contains "'uber' cannot be negated")
}
+
+ @Test def xSourceTest(): Unit = {
+ def check(expected: String, args: String*): Unit = {
+ val s = new MutableSettings(msg => throw new IllegalArgumentException(msg))
+ val (_, residual) = s.processArguments(args.toList, processAll = true)
+ assert(residual.isEmpty)
+ assertTrue(s.source.value == ScalaVersion(expected))
+ }
+ check(expected = "2.11.0") // default
+ check(expected = "2.11.0", "-Xsource:2.11")
+ check(expected = "2.10", "-Xsource:2.10.0")
+ check(expected = "2.12", "-Xsource:2.12")
+ assertThrows[IllegalArgumentException](check(expected = "2.11", "-Xsource"), _ == "-Xsource requires an argument, the syntax is -Xsource:<version>")
+ assertThrows[IllegalArgumentException](check(expected = "2.11", "-Xsource", "2.11"), _ == "-Xsource requires an argument, the syntax is -Xsource:<version>")
+ assertThrows[IllegalArgumentException](check(expected = "2.11", "-Xsource:2.invalid"), _ contains "There was a problem parsing 2.invalid")
+ }
}
diff --git a/test/junit/scala/tools/nsc/symtab/CannotHaveAttrsTest.scala b/test/junit/scala/tools/nsc/symtab/CannotHaveAttrsTest.scala
index d424f12710..69931c9e24 100644
--- a/test/junit/scala/tools/nsc/symtab/CannotHaveAttrsTest.scala
+++ b/test/junit/scala/tools/nsc/symtab/CannotHaveAttrsTest.scala
@@ -64,4 +64,16 @@ class CannotHaveAttrsTest {
assertThrows[IllegalArgumentException] { t.setType(tpe) }
}
}
+
+ class Attach
+ @Test
+ def attachmentsAreIgnored = {
+ attrlessTrees.foreach { t =>
+ t.setAttachments(NoPosition.update(new Attach))
+ assert(t.attachments == NoPosition)
+ t.updateAttachment(new Attach)
+ assert(t.attachments == NoPosition)
+ t.removeAttachment[Attach] // no exception
+ }
+ }
}
diff --git a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala
index e4be42ac96..f0f20acf07 100644
--- a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala
+++ b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala
@@ -3,6 +3,9 @@ package symtab
import scala.reflect.ClassTag
import scala.reflect.internal.{Phase, NoPhase, SomePhase}
+import scala.tools.nsc.classpath.FlatClassPath
+import scala.tools.nsc.settings.ClassPathRepresentationType
+import scala.tools.util.FlatClassPathResolver
import scala.tools.util.PathResolver
import util.ClassPath
import io.AbstractFile
@@ -26,13 +29,28 @@ class SymbolTableForUnitTesting extends SymbolTable {
class LazyTreeCopier extends super.LazyTreeCopier with TreeCopier
override def isCompilerUniverse: Boolean = true
- def classPath = new PathResolver(settings).result
+
+ def classPath = platform.classPath
+ def flatClassPath: FlatClassPath = platform.flatClassPath
object platform extends backend.Platform {
val symbolTable: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this
lazy val loaders: SymbolTableForUnitTesting.this.loaders.type = SymbolTableForUnitTesting.this.loaders
+
def platformPhases: List[SubComponent] = Nil
- val classPath: ClassPath[AbstractFile] = new PathResolver(settings).result
+
+ lazy val classPath: ClassPath[AbstractFile] = {
+ assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Recursive,
+ "It's not possible to use the recursive classpath representation, when it's not the chosen classpath scanning method")
+ new PathResolver(settings).result
+ }
+
+ private[nsc] lazy val flatClassPath: FlatClassPath = {
+ assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Flat,
+ "It's not possible to use the flat classpath representation, when it's not the chosen classpath scanning method")
+ new FlatClassPathResolver(settings).result
+ }
+
def isMaybeBoxed(sym: Symbol): Boolean = ???
def needCompile(bin: AbstractFile, src: AbstractFile): Boolean = ???
def externalEquals: Symbol = ???
@@ -50,7 +68,12 @@ class SymbolTableForUnitTesting extends SymbolTable {
class GlobalMirror extends Roots(NoSymbol) {
val universe: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this
- def rootLoader: LazyType = new loaders.PackageLoader(classPath)
+
+ def rootLoader: LazyType = settings.YclasspathImpl.value match {
+ case ClassPathRepresentationType.Flat => new loaders.PackageLoaderUsingFlatClassPath(FlatClassPath.RootPackage, flatClassPath)
+ case ClassPathRepresentationType.Recursive => new loaders.PackageLoader(classPath)
+ }
+
override def toString = "compiler mirror"
}
@@ -60,7 +83,7 @@ class SymbolTableForUnitTesting extends SymbolTable {
rm.asInstanceOf[Mirror]
}
- def settings: Settings = {
+ lazy val settings: Settings = {
val s = new Settings
// initialize classpath using java classpath
s.usejavacp.value = true
diff --git a/test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala b/test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala
new file mode 100644
index 0000000000..f2926e3e17
--- /dev/null
+++ b/test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2014 Contributor. All rights reserved.
+ */
+package scala.tools.nsc.util
+
+import scala.reflect.io.AbstractFile
+import scala.tools.nsc.Settings
+import scala.tools.nsc.settings.ClassPathRepresentationType
+import scala.tools.util.PathResolverFactory
+
+/**
+ * Simple application to compare efficiency of the recursive and the flat classpath representations
+ */
+object ClassPathImplComparator {
+
+ private class TestSettings extends Settings {
+ val checkClasses = PathSetting("-checkClasses", "Specify names of classes which should be found separated with ;", "")
+ val requiredIterations = IntSetting("-requiredIterations",
+ "Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None)
+ val cpCreationRepetitions = IntSetting("-cpCreationRepetitions",
+ "Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None)
+ val cpLookupRepetitions = IntSetting("-cpLookupRepetitions",
+ "Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None)
+ }
+
+ private class DurationStats(name: String) {
+ private var sum = 0L
+ private var iterations = 0
+
+ def noteMeasuredTime(millis: Long): Unit = {
+ sum += millis
+ iterations += 1
+ }
+
+ def printResults(): Unit = {
+ val avg = if (iterations == 0) 0 else sum.toDouble / iterations
+ println(s"$name - total duration: $sum ms; iterations: $iterations; avg: $avg ms")
+ }
+ }
+
+ private lazy val defaultClassesToFind = List(
+ "scala.collection.immutable.List",
+ "scala.Option",
+ "scala.Int",
+ "scala.collection.immutable.Vector",
+ "scala.util.hashing.MurmurHash3"
+ )
+
+ private val oldCpCreationStats = new DurationStats("Old classpath - create")
+ private val oldCpSearchingStats = new DurationStats("Old classpath - search")
+
+ private val flatCpCreationStats = new DurationStats("Flat classpath - create")
+ private val flatCpSearchingStats = new DurationStats("Flat classpath - search")
+
+ def main(args: Array[String]): Unit = {
+
+ if (args contains "-help")
+ usage()
+ else {
+ val oldCpSettings = loadSettings(args.toList, ClassPathRepresentationType.Recursive)
+ val flatCpSettings = loadSettings(args.toList, ClassPathRepresentationType.Flat)
+
+ val classesToCheck = oldCpSettings.checkClasses.value
+ val classesToFind =
+ if (classesToCheck.isEmpty) defaultClassesToFind
+ else classesToCheck.split(";").toList
+
+ def doTest(classPath: => ClassFileLookup[AbstractFile], cpCreationStats: DurationStats, cpSearchingStats: DurationStats,
+ cpCreationRepetitions: Int, cpLookupRepetitions: Int)= {
+
+ def createClassPaths() = (1 to cpCreationRepetitions).map(_ => classPath).last
+ def testClassLookup(cp: ClassFileLookup[AbstractFile]): Boolean = (1 to cpCreationRepetitions).foldLeft(true) {
+ case (a, _) => a && checkExistenceOfClasses(classesToFind)(cp)
+ }
+
+ val cp = withMeasuredTime("Creating classpath", createClassPaths(), cpCreationStats)
+ val result = withMeasuredTime("Searching for specified classes", testClassLookup(cp), cpSearchingStats)
+ println(s"The end of the test case. All expected classes found = $result \n")
+ }
+
+ (1 to oldCpSettings.requiredIterations.value) foreach { iteration =>
+ if (oldCpSettings.requiredIterations.value > 1)
+ println(s"Iteration no $iteration")
+
+ println("Recursive (old) classpath representation:")
+ doTest(PathResolverFactory.create(oldCpSettings).result, oldCpCreationStats, oldCpSearchingStats,
+ oldCpSettings.cpCreationRepetitions.value, oldCpSettings.cpLookupRepetitions.value)
+
+ println("Flat classpath representation:")
+ doTest(PathResolverFactory.create(flatCpSettings).result, flatCpCreationStats, flatCpSearchingStats,
+ flatCpSettings.cpCreationRepetitions.value, flatCpSettings.cpLookupRepetitions.value)
+ }
+
+ if (oldCpSettings.requiredIterations.value > 1) {
+ println("\nOld classpath - summary")
+ oldCpCreationStats.printResults()
+ oldCpSearchingStats.printResults()
+
+ println("\nFlat classpath - summary")
+ flatCpCreationStats.printResults()
+ flatCpSearchingStats.printResults()
+ }
+ }
+ }
+
+ /**
+ * Prints usage information
+ */
+ private def usage(): Unit =
+ println("""Use classpath and sourcepath options like in the case of e.g. 'scala' command.
+ | There are also two additional options:
+ | -checkClasses <semicolon separated class names> Specify names of classes which should be found
+ | -requiredIterations <int value> Repeat tests specified count of times (to check e.g. impact of caches)
+ | Note: Option -YclasspathImpl will be set automatically for each case.
+ """.stripMargin.trim)
+
+ private def loadSettings(args: List[String], implType: String) = {
+ val settings = new TestSettings()
+ settings.processArguments(args, processAll = true)
+ settings.YclasspathImpl.value = implType
+ if (settings.classpath.isDefault)
+ settings.classpath.value = sys.props("java.class.path")
+ settings
+ }
+
+ private def withMeasuredTime[T](operationName: String, f: => T, durationStats: DurationStats): T = {
+ val startTime = System.currentTimeMillis()
+ val res = f
+ val elapsed = System.currentTimeMillis() - startTime
+ durationStats.noteMeasuredTime(elapsed)
+ println(s"$operationName - elapsed $elapsed ms")
+ res
+ }
+
+ private def checkExistenceOfClasses(classesToCheck: Seq[String])(classPath: ClassFileLookup[AbstractFile]): Boolean =
+ classesToCheck.foldLeft(true) {
+ case (res, classToCheck) =>
+ val found = classPath.findClass(classToCheck).isDefined
+ if (!found)
+ println(s"Class $classToCheck not found") // of course in this case the measured time will be affected by IO operation
+ found
+ }
+}
diff --git a/test/junit/scala/tools/testing/AssertUtil.scala b/test/junit/scala/tools/testing/AssertUtil.scala
index 9b4833d46b..83a637783f 100644
--- a/test/junit/scala/tools/testing/AssertUtil.scala
+++ b/test/junit/scala/tools/testing/AssertUtil.scala
@@ -1,6 +1,11 @@
package scala.tools
package testing
+import org.junit.Assert
+import Assert.fail
+import scala.runtime.ScalaRunTime.stringOf
+import scala.collection.{ GenIterable, IterableLike }
+
/** This module contains additional higher-level assert statements
* that are ultimately based on junit.Assert primitives.
*/
@@ -21,6 +26,19 @@ object AssertUtil {
throw e
else return
}
- throw new AssertionError("Expression did not throw!")
+ fail("Expression did not throw!")
}
+
+ /** JUnit-style assertion for `IterableLike.sameElements`.
+ */
+ def assertSameElements[A, B >: A](expected: IterableLike[A, _], actual: GenIterable[B], message: String = ""): Unit =
+ if (!(expected sameElements actual))
+ fail(
+ f"${ if (message.nonEmpty) s"$message " else "" }expected:<${ stringOf(expected) }> but was:<${ stringOf(actual) }>"
+ )
+
+ /** Convenient for testing iterators.
+ */
+ def assertSameElements[A, B >: A](expected: IterableLike[A, _], actual: Iterator[B]): Unit =
+ assertSameElements(expected, actual.toList, "")
}