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/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/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..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 { @@ -1140,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/sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSCompilationTests.scala b/sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSCompilationTests.scala new file mode 100644 index 000000000000..fa28736453c6 --- /dev/null +++ b/sjs-compiler-tests/test/scala/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/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 + } +}