From fc6da8d8b765ddc3c492d0884164561ca7a8b4d8 Mon Sep 17 00:00:00 2001 From: James Iry Date: Wed, 29 May 2013 14:10:02 -0700 Subject: Test for reading JDK 8 (classfile format 52) class files. This commit includes a test for reading JDK 8 (classfile format 52) class files, in particular default (aka defender) methods. It uses ASM to generate an interface with default methods then exercises that interface from Scala. Surprisingly no changes are necessary to the Scala code base to support reading format 52 class files. Because the test can only run under JDK 8, the JDK version is checked and the expected output is synthesized for previous versions. --- test/files/run/classfile-format-52.check | 2 + test/files/run/classfile-format-52.scala | 80 ++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 test/files/run/classfile-format-52.check create mode 100644 test/files/run/classfile-format-52.scala (limited to 'test/files') diff --git a/test/files/run/classfile-format-52.check b/test/files/run/classfile-format-52.check new file mode 100644 index 0000000000..5d24ef03cc --- /dev/null +++ b/test/files/run/classfile-format-52.check @@ -0,0 +1,2 @@ +hello from publicMethod +hello from staticMethod diff --git a/test/files/run/classfile-format-52.scala b/test/files/run/classfile-format-52.scala new file mode 100644 index 0000000000..f0ad7c2ed6 --- /dev/null +++ b/test/files/run/classfile-format-52.scala @@ -0,0 +1,80 @@ +import java.io.{File, FileOutputStream} + +import scala.tools.nsc.settings.ScalaVersion +import scala.tools.partest._ +import scala.tools.asm +import asm.{AnnotationVisitor, ClassWriter, FieldVisitor, Handle, MethodVisitor, Opcodes} +import Opcodes._ + +// This test ensures that we can read JDK 8 (classfile format 52) files, including those +// with default methods. To do that it first uses ASM to generate an interface called +// HasDefaultMethod. Then it runs a normal compile on Scala source that extends that +// interface. Any failure will be dumped to std out. +// +// By it's nature the test can only work on JDK 8+ because under JDK 7- the +// interface won't verify. +object Test extends DirectTest { + override def extraSettings: String = "-optimise -usejavacp -d " + testOutput.path + " -cp " + testOutput.path + + def generateInterface() { + val interfaceName = "HasDefaultMethod" + val methodType = "()Ljava/lang/String;" + + val cw = new ClassWriter(0) + cw.visit(52, ACC_PUBLIC+ACC_ABSTRACT+ACC_INTERFACE, interfaceName, null, "java/lang/Object", null) + + def createMethod(flags:Int, name: String) { + val method = cw.visitMethod(flags, name, methodType, null, null) + method.visitCode() + method.visitLdcInsn(s"hello from $name") + method.visitInsn(ARETURN) + method.visitMaxs(1, 1) + method.visitEnd() + } + + createMethod(ACC_PUBLIC, "publicMethod") + createMethod(ACC_PUBLIC+ACC_STATIC, "staticMethod") + createMethod(ACC_PRIVATE, "privateMethod") + + cw.visitEnd() + val bytes = cw.toByteArray() + + val fos = new FileOutputStream(new File(s"${testOutput.path}/$interfaceName.class")) + try + fos write bytes + finally + fos.close() + + } + + def code = +""" +class Driver extends HasDefaultMethod { + println(publicMethod()) + println(HasDefaultMethod.staticMethod()) +} +""" + + override def show(): Unit = { + // redirect err to out, for logging + val prevErr = System.err + System.setErr(System.out) + try { + // this test is only valid under JDK 1.8+ + // cheat a little by using 'ScalaVersion' because it can parse java versions just as well + val requiredJavaVersion = ScalaVersion("1.8") + val executingJavaVersion = ScalaVersion(System.getProperty("java.specification.version")) + if (executingJavaVersion >= requiredJavaVersion) { + generateInterface() + compile() + Class.forName("Driver").newInstance() + } else { + // under other versions just dump the expected results + println("hello from publicMethod") + println("hello from staticMethod") + } + } + finally + System.setErr(prevErr) + } +} -- cgit v1.2.3 From ac4e3ca19246ae3d983f99607b865c5ed3acb2b9 Mon Sep 17 00:00:00 2001 From: James Iry Date: Tue, 4 Jun 2013 10:22:43 -0700 Subject: Refactor testing logic for only running under certain JDK versions We had several tests designed to only run if the JDK version was at least some specified version. This commit refactors that common logic into DirectTest. --- src/partest/scala/tools/partest/DirectTest.scala | 27 ++++++++++++++++++++++++ test/files/run/classfile-format-51.scala | 8 +++---- test/files/run/classfile-format-52.scala | 11 ++++------ test/files/run/t7398.scala | 11 ++++------ 4 files changed, 39 insertions(+), 18 deletions(-) (limited to 'test/files') diff --git a/src/partest/scala/tools/partest/DirectTest.scala b/src/partest/scala/tools/partest/DirectTest.scala index e2dac2fd55..8fcaa6423c 100644 --- a/src/partest/scala/tools/partest/DirectTest.scala +++ b/src/partest/scala/tools/partest/DirectTest.scala @@ -6,6 +6,7 @@ package scala.tools.partest import scala.tools.nsc._ +import settings.ScalaVersion import io.Directory import util.{ SourceFile, BatchSourceFile, CommandLineParser } import reporters.{Reporter, ConsoleReporter} @@ -101,4 +102,30 @@ abstract class DirectTest extends App { final def log(msg: => Any) { if (isDebug) Console.err println msg } + + /** + * Run a test only if the current java version is at least the version specified. + */ + def testUnderJavaAtLeast[A](version: String)(yesRun: =>A) = new TestUnderJavaAtLeast(version, { yesRun }) + + class TestUnderJavaAtLeast[A](version: String, yesRun: => A) { + val javaVersion = System.getProperty("java.specification.version") + + // the "ScalaVersion" class parses Java specification versions just fine + val requiredJavaVersion = ScalaVersion(version) + val executingJavaVersion = ScalaVersion(javaVersion) + val shouldRun = executingJavaVersion >= requiredJavaVersion + val preamble = if (shouldRun) "Attempting" else "Doing fallback for" + + def logInfo() = log(s"$preamble java $version specific test under java version $javaVersion") + + /* + * If the current java version is at least 'version' then 'yesRun' is evaluated + * otherwise 'fallback' is + */ + def otherwise(fallback: =>A): A = { + logInfo() + if (shouldRun) yesRun else fallback + } + } } diff --git a/test/files/run/classfile-format-51.scala b/test/files/run/classfile-format-51.scala index 9b1e612f4f..378caa7936 100644 --- a/test/files/run/classfile-format-51.scala +++ b/test/files/run/classfile-format-51.scala @@ -112,12 +112,12 @@ object Driver { System.setErr(System.out) try { // this test is only valid under JDK 1.7+ - // cheat a little by using 'ScalaVersion' because it can parse java versions just as well - val requiredJavaVersion = ScalaVersion("1.7") - val executingJavaVersion = ScalaVersion(System.getProperty("java.specification.version")) - if (executingJavaVersion >= requiredJavaVersion) { + testUnderJavaAtLeast("1.7") { generateClass() compile() + () + } otherwise { + () } } finally diff --git a/test/files/run/classfile-format-52.scala b/test/files/run/classfile-format-52.scala index f0ad7c2ed6..7afa09ae0b 100644 --- a/test/files/run/classfile-format-52.scala +++ b/test/files/run/classfile-format-52.scala @@ -61,17 +61,14 @@ class Driver extends HasDefaultMethod { System.setErr(System.out) try { // this test is only valid under JDK 1.8+ - // cheat a little by using 'ScalaVersion' because it can parse java versions just as well - val requiredJavaVersion = ScalaVersion("1.8") - val executingJavaVersion = ScalaVersion(System.getProperty("java.specification.version")) - if (executingJavaVersion >= requiredJavaVersion) { + testUnderJavaAtLeast("1.8") { generateInterface() compile() Class.forName("Driver").newInstance() - } else { - // under other versions just dump the expected results + () + } otherwise { println("hello from publicMethod") - println("hello from staticMethod") + println("hello from staticMethod") } } finally diff --git a/test/files/run/t7398.scala b/test/files/run/t7398.scala index e4090f7db3..dd59697b71 100644 --- a/test/files/run/t7398.scala +++ b/test/files/run/t7398.scala @@ -3,14 +3,11 @@ import scala.tools.partest._ object Test extends CompilerTest { import global._ - def javaVersion = scala.util.Properties.javaVersion - def isJavaEight = javaVersion startsWith "1.8" // This way we auto-pass on non-java8 since there's nothing to check - override lazy val units = { - val res: List[CompilationUnit] = if (isJavaEight) javaCompilationUnits(global)(defaultMethodSource) else Nil - val word = if (isJavaEight) "Attempting" else "Skipping" - log(s"$word java8-specific test under java version $javaVersion") - res + override lazy val units: List[CompilationUnit] = testUnderJavaAtLeast("1.8") { + javaCompilationUnits(global)(defaultMethodSource) + } otherwise { + Nil } private def defaultMethodSource = """ -- cgit v1.2.3