From 400b6c282134e57a7eb2ddd7bc0da803c5d6a7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 21 Aug 2020 11:27:26 +0200 Subject: [PATCH 1/2] Scala.js: Add neg tests for bad uses of `reflect.Selectable`. We add some infrastructure to be able to perform neg tests that are specific to Scala.js. Such tests require the scalajs-library jar and the dotty-libraryJS jar on the classpath, and must be compiled with the `-scalajs` option. We use this infrastructure to test existing compile errors reported for unsupported uses of `reflect.Selectable` in Scala.js. --- compiler/test/dotty/Properties.scala | 6 +++ .../tools/dotc/ScalaJSCompilationTests.scala | 40 +++++++++++++++++ .../tools/vulpix/TestConfiguration.scala | 7 +++ project/Build.scala | 6 ++- tests/neg-scalajs/reflective-calls.scala | 45 +++++++++++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 compiler/test/dotty/tools/dotc/ScalaJSCompilationTests.scala create mode 100644 tests/neg-scalajs/reflective-calls.scala diff --git a/compiler/test/dotty/Properties.scala b/compiler/test/dotty/Properties.scala index 12b3e2083017..042773505dc5 100644 --- a/compiler/test/dotty/Properties.scala +++ b/compiler/test/dotty/Properties.scala @@ -48,6 +48,9 @@ object Properties { /** dotty-library jar */ def dottyLibrary: String = sys.props("dotty.tests.classes.dottyLibrary") + /** dotty-library-js jar */ + def dottyLibraryJS: String = sys.props("dotty.tests.classes.dottyLibraryJS") + /** dotty-compiler jar */ def dottyCompiler: String = sys.props("dotty.tests.classes.dottyCompiler") @@ -74,4 +77,7 @@ object Properties { /** jline-reader jar */ def jlineReader: String = sys.props("dotty.tests.classes.jlineReader") + + /** scalajs-library jar */ + def scalaJSLibrary: String = sys.props("dotty.tests.classes.scalaJSLibrary") } diff --git a/compiler/test/dotty/tools/dotc/ScalaJSCompilationTests.scala b/compiler/test/dotty/tools/dotc/ScalaJSCompilationTests.scala new file mode 100644 index 000000000000..fa28736453c6 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/ScalaJSCompilationTests.scala @@ -0,0 +1,40 @@ +package dotty +package tools +package dotc + +import org.junit.{ Test, BeforeClass, AfterClass } +import org.junit.experimental.categories.Category + +import scala.concurrent.duration._ +import vulpix._ + +@Category(Array(classOf[ScalaJSCompilationTests])) +class ScalaJSCompilationTests extends ParallelTesting { + import ParallelTesting._ + import TestConfiguration._ + import ScalaJSCompilationTests._ + import CompilationTest.aggregateTests + + // Test suite configuration -------------------------------------------------- + + def maxDuration = 60.seconds + def numberOfSlaves = 5 + def safeMode = Properties.testsSafeMode + def isInteractive = SummaryReport.isInteractive + def testFilter = Properties.testsFilter + def updateCheckFiles: Boolean = Properties.testsUpdateCheckfile + + // Negative tests ------------------------------------------------------------ + + @Test def negScalaJS: Unit = { + implicit val testGroup: TestGroup = TestGroup("negScalaJS") + aggregateTests( + compileFilesInDir("tests/neg-scalajs", scalaJSOptions), + ).checkExpectedErrors() + } +} + +object ScalaJSCompilationTests { + implicit val summaryReport: SummaryReporting = new SummaryReport + @AfterClass def cleanup(): Unit = summaryReport.echoSummary() +} diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index c067fdd8399b..e4348e198941 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -44,6 +44,11 @@ object TestConfiguration { lazy val withTastyInspectorClasspath = withCompilerClasspath + File.pathSeparator + mkClasspath(List(Properties.dottyTastyInspector)) + lazy val scalaJSClasspath = mkClasspath(List( + Properties.scalaJSLibrary, + Properties.dottyLibraryJS + )) + def mkClasspath(classpaths: List[String]): String = classpaths.map({ p => val file = new java.io.File(p) @@ -61,6 +66,8 @@ object TestConfiguration { defaultOptions.withClasspath(withStagingClasspath).withRunClasspath(withStagingClasspath) lazy val withTastyInspectorOptions = defaultOptions.withClasspath(withTastyInspectorClasspath).withRunClasspath(withTastyInspectorClasspath) + lazy val scalaJSOptions = + defaultOptions.and("-scalajs").withClasspath(scalaJSClasspath) val allowDeepSubtypes = defaultOptions without "-Yno-deep-subtypes" val allowDoubleBindings = defaultOptions without "-Yno-double-bindings" val picklingOptions = defaultOptions and ( diff --git a/project/Build.scala b/project/Build.scala index 6a40197cbb4f..6d6a5b64628f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -526,12 +526,14 @@ object Build { (sourceManaged in Compile).value } val externalDeps = externalCompilerClasspathTask.value + val externalJSDeps = (externalDependencyClasspath in (LocalProject("dotty-library-bootstrappedJS"), Compile)).value val jars = packageAll.value Seq( "-Ddotty.tests.dottyCompilerManagedSources=" + managedSrcDir, "-Ddotty.tests.classes.dottyInterfaces=" + jars("dotty-interfaces"), "-Ddotty.tests.classes.dottyLibrary=" + jars("dotty-library"), + "-Ddotty.tests.classes.dottyLibraryJS=" + jars("dotty-library-js"), "-Ddotty.tests.classes.dottyCompiler=" + jars("dotty-compiler"), "-Ddotty.tests.classes.tastyCore=" + jars("tasty-core"), "-Ddotty.tests.classes.compilerInterface=" + findArtifactPath(externalDeps, "compiler-interface"), @@ -539,6 +541,7 @@ object Build { "-Ddotty.tests.classes.scalaAsm=" + findArtifactPath(externalDeps, "scala-asm"), "-Ddotty.tests.classes.jlineTerminal=" + findArtifactPath(externalDeps, "jline-terminal"), "-Ddotty.tests.classes.jlineReader=" + findArtifactPath(externalDeps, "jline-reader"), + "-Ddotty.tests.classes.scalaJSLibrary=" + findArtifactPath(externalJSDeps, "scalajs-library_2.13"), ) }, @@ -705,7 +708,8 @@ object Build { // running the compiler, we should always have the bootstrapped // library on the compiler classpath since the non-bootstrapped one // may not be binary-compatible. - "dotty-library" -> packageBin.in(`dotty-library-bootstrapped`, Compile).value + "dotty-library" -> packageBin.in(`dotty-library-bootstrapped`, Compile).value, + "dotty-library-js" -> packageBin.in(`dotty-library-bootstrappedJS`, Compile).value, ).mapValues(_.getAbsolutePath) } }.value, diff --git a/tests/neg-scalajs/reflective-calls.scala b/tests/neg-scalajs/reflective-calls.scala new file mode 100644 index 000000000000..b5beeac0c302 --- /dev/null +++ b/tests/neg-scalajs/reflective-calls.scala @@ -0,0 +1,45 @@ +import scala.reflect.{ClassTag, Selectable => ReflectSel} +import ReflectSel.reflectiveSelectable + +object Test { + /* Make sure that an explicit desugaring of the legit cases actually compiles, + * ensuring that the error cases we test are actually testing the right things. + */ + def sanityCheck(): Unit = { + val receiver: Any = ??? + reflectiveSelectable(receiver).selectDynamic("foo") // OK + reflectiveSelectable(receiver).applyDynamic("foo")() // OK + reflectiveSelectable(receiver).applyDynamic("foo", ClassTag(classOf[String]), ClassTag(classOf[List[_]]))("bar", Nil) // OK + } + + def badReceider(): Unit = { + val receiver: ReflectSel = ??? + receiver.selectDynamic("foo") // error + receiver.applyDynamic("foo")() // error + } + + def nonLiteralMethodName(): Unit = { + val receiver: Any = ??? + val methodName: String = "foo" + reflectiveSelectable(receiver).selectDynamic(methodName) // error + reflectiveSelectable(receiver).applyDynamic(methodName)() // error + } + + def nonLiteralClassTag(): Unit = { + val receiver: Any = ??? + val myClassTag: ClassTag[String] = ClassTag(classOf[String]) + reflectiveSelectable(receiver).applyDynamic("foo", myClassTag, ClassTag(classOf[List[_]]))("bar", Nil) // error + } + + def classTagVarArgs(): Unit = { + val receiver: Any = ??? + val classTags: List[ClassTag[_]] = List(ClassTag(classOf[String]), ClassTag(classOf[List[_]])) + reflectiveSelectable(receiver).applyDynamic("foo", classTags: _*)("bar", Nil) // error + } + + def argsVarArgs(): Unit = { + val receiver: Any = ??? + val args: List[Any] = List("bar", Nil) + reflectiveSelectable(receiver).applyDynamic("foo", ClassTag(classOf[String]), ClassTag(classOf[List[_]]))(args: _*) // error + } +} From eb54a18b2cdef10b1ec36675376dd0182834726f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 21 Aug 2020 15:40:39 +0200 Subject: [PATCH 2/2] Move the Scala.js-specific compiler tests to a separate project. This way, it is not necessary to compile `dotty-library-bootstrappedJS` unless we want to run the Scala.js-specific tests. This reduces test coverage, since those tests won't be run with the bootstrapped compiler anymore. --- .github/workflows/ci.yaml | 2 +- build.sbt | 1 + project/Build.scala | 31 ++++++++++++++----- .../tools/dotc/ScalaJSCompilationTests.scala | 0 4 files changed, 25 insertions(+), 9 deletions(-) rename {compiler/test => sjs-compiler-tests/test/scala}/dotty/tools/dotc/ScalaJSCompilationTests.scala (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bbde24de7f90..61a26427664d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -93,7 +93,7 @@ jobs: - name: Test run: | - ./project/scripts/sbt ";dotty-bootstrapped/compile ;dotty-bootstrapped/test;sjsSandbox/run;sjsSandbox/test;sjsJUnitTests/test ;configureIDE" + ./project/scripts/sbt ";dotty-bootstrapped/compile ;dotty-bootstrapped/test;sjsSandbox/run;sjsSandbox/test;sjsJUnitTests/test;sjsCompilerTests/test ;configureIDE" ./project/scripts/bootstrapCmdTests community_build: diff --git a/build.sbt b/build.sbt index b719ef85b892..28559cb1c4a9 100644 --- a/build.sbt +++ b/build.sbt @@ -30,6 +30,7 @@ val `community-build` = Build.`community-build` val sjsSandbox = Build.sjsSandbox val sjsJUnitTests = Build.sjsJUnitTests +val sjsCompilerTests = Build.sjsCompilerTests val `sbt-dotty` = Build.`sbt-dotty` val `vscode-dotty` = Build.`vscode-dotty` diff --git a/project/Build.scala b/project/Build.scala index 6d6a5b64628f..e31b8a23c08d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -174,6 +174,8 @@ object Build { fork in Test := true, parallelExecution in Test := false, + outputStrategy := Some(StdoutOutput), + // enable verbose exception messages for JUnit testOptions in Test += Tests.Argument(TestFrameworks.JUnit, "-a", "-v"), ) @@ -335,7 +337,6 @@ object Build { ) lazy val commonBenchmarkSettings = Seq( - outputStrategy := Some(StdoutOutput), mainClass in (Jmh, run) := Some("dotty.tools.benchmarks.Bench"), // custom main for jmh:run javaOptions += "-DBENCH_COMPILER_CLASS_PATH=" + Attributed.data((fullClasspath in (`dotty-bootstrapped`, Compile)).value).mkString("", File.pathSeparator, ""), javaOptions += "-DBENCH_CLASS_PATH=" + Attributed.data((fullClasspath in (`dotty-library-bootstrapped`, Compile)).value).mkString("", File.pathSeparator, "") @@ -404,7 +405,6 @@ object Build { def dottyDocSettings(implicit mode: Mode) = Seq( connectInput in run := true, - outputStrategy := Some(StdoutOutput), javaOptions ++= (javaOptions in `dotty-compiler`).value, @@ -466,7 +466,6 @@ object Build { lazy val commonDottyCompilerSettings = Seq( // set system in/out for repl connectInput in run := true, - outputStrategy := Some(StdoutOutput), // Generate compiler.properties, used by sbt resourceGenerators in Compile += Def.task { @@ -526,14 +525,12 @@ object Build { (sourceManaged in Compile).value } val externalDeps = externalCompilerClasspathTask.value - val externalJSDeps = (externalDependencyClasspath in (LocalProject("dotty-library-bootstrappedJS"), Compile)).value val jars = packageAll.value Seq( "-Ddotty.tests.dottyCompilerManagedSources=" + managedSrcDir, "-Ddotty.tests.classes.dottyInterfaces=" + jars("dotty-interfaces"), "-Ddotty.tests.classes.dottyLibrary=" + jars("dotty-library"), - "-Ddotty.tests.classes.dottyLibraryJS=" + jars("dotty-library-js"), "-Ddotty.tests.classes.dottyCompiler=" + jars("dotty-compiler"), "-Ddotty.tests.classes.tastyCore=" + jars("tasty-core"), "-Ddotty.tests.classes.compilerInterface=" + findArtifactPath(externalDeps, "compiler-interface"), @@ -541,7 +538,6 @@ object Build { "-Ddotty.tests.classes.scalaAsm=" + findArtifactPath(externalDeps, "scala-asm"), "-Ddotty.tests.classes.jlineTerminal=" + findArtifactPath(externalDeps, "jline-terminal"), "-Ddotty.tests.classes.jlineReader=" + findArtifactPath(externalDeps, "jline-reader"), - "-Ddotty.tests.classes.scalaJSLibrary=" + findArtifactPath(externalJSDeps, "scalajs-library_2.13"), ) }, @@ -708,8 +704,7 @@ object Build { // running the compiler, we should always have the bootstrapped // library on the compiler classpath since the non-bootstrapped one // may not be binary-compatible. - "dotty-library" -> packageBin.in(`dotty-library-bootstrapped`, Compile).value, - "dotty-library-js" -> packageBin.in(`dotty-library-bootstrappedJS`, Compile).value, + "dotty-library" -> packageBin.in(`dotty-library-bootstrapped`, Compile).value ).mapValues(_.getAbsolutePath) } }.value, @@ -1144,6 +1139,26 @@ object Build { } ) + lazy val sjsCompilerTests = project.in(file("sjs-compiler-tests")). + dependsOn(`dotty-compiler` % "test->test"). + settings( + commonNonBootstrappedSettings, + + // Change the baseDirectory when running the tests + baseDirectory in Test := baseDirectory.value.getParentFile, + + javaOptions ++= (javaOptions in `dotty-compiler`).value, + javaOptions ++= { + val externalJSDeps = (externalDependencyClasspath in (`dotty-library-bootstrappedJS`, Compile)).value + val dottyLibraryJSJar = (packageBin in (`dotty-library-bootstrappedJS`, Compile)).value.getAbsolutePath + + Seq( + "-Ddotty.tests.classes.dottyLibraryJS=" + dottyLibraryJSJar, + "-Ddotty.tests.classes.scalaJSLibrary=" + findArtifactPath(externalJSDeps, "scalajs-library_2.13"), + ) + }, + ) + lazy val `dotty-bench` = project.in(file("bench")).asDottyBench(NonBootstrapped) lazy val `dotty-bench-bootstrapped` = project.in(file("bench")).asDottyBench(Bootstrapped) lazy val `dotty-bench-run` = project.in(file("bench-run")).asDottyBench(Bootstrapped) diff --git a/compiler/test/dotty/tools/dotc/ScalaJSCompilationTests.scala b/sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSCompilationTests.scala similarity index 100% rename from compiler/test/dotty/tools/dotc/ScalaJSCompilationTests.scala rename to sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSCompilationTests.scala