diff --git a/project/Build.scala b/project/Build.scala index 3d47be84c91b..4dc438cb6bb9 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1054,15 +1054,13 @@ object Build { // with the bootstrapped library on the classpath. lazy val `scala3-sbt-bridge-tests` = project.in(file("sbt-bridge/test")). dependsOn(dottyCompiler(Bootstrapped) % Test). + dependsOn(`scala3-sbt-bridge`). settings(commonBootstrappedSettings). settings( Compile / sources := Seq(), Test / scalaSource := baseDirectory.value, Test / javaSource := baseDirectory.value, - - // Tests disabled until zinc-api-info cross-compiles with 2.13, - // alternatively we could just copy in sources the part of zinc-api-info we need. - Test / sources := Seq() + libraryDependencies += ("org.scala-sbt" %% "zinc-apiinfo" % "1.8.0" % Test).cross(CrossVersion.for3Use2_13) ) lazy val `scala3-language-server` = project.in(file("language-server")). diff --git a/sbt-bridge/test/xsbt/ExtractAPISpecification.scala b/sbt-bridge/test/xsbt/ExtractAPISpecification.scala index dfbcbf2181a2..e85cf8989b0f 100644 --- a/sbt-bridge/test/xsbt/ExtractAPISpecification.scala +++ b/sbt-bridge/test/xsbt/ExtractAPISpecification.scala @@ -147,9 +147,8 @@ class ExtractAPISpecification { |""".stripMargin val compilerForTesting = new ScalaCompilerForUnitTesting val apis = - compilerForTesting.extractApisFromSrcs(reuseCompilerInstance = false)(List(src1, src2), - List(src2)) - val _ :: src2Api1 :: src2Api2 :: Nil = apis.toList + compilerForTesting.extractApisFromSrcs(List(src1, src2), List(src2)) + val _ :: src2Api1 :: src2Api2 :: Nil = apis.toList: @unchecked val namerApi1 = selectNamer(src2Api1) val namerApi2 = selectNamer(src2Api2) assertTrue(SameAPI(namerApi1, namerApi2)) @@ -202,7 +201,7 @@ class ExtractAPISpecification { val srcC8 = "class C8 { self => }" val compilerForTesting = new ScalaCompilerForUnitTesting val apis = compilerForTesting - .extractApisFromSrcs(reuseCompilerInstance = true)( + .extractApisFromSrcs( List(srcX, srcY, srcC1, srcC2, srcC3, srcC4, srcC5, srcC6, srcC8) ) .map(_.head) diff --git a/sbt-bridge/test/xsbt/ExtractUsedNamesSpecification.scala b/sbt-bridge/test/xsbt/ExtractUsedNamesSpecification.scala index ee50b3717213..819bedec3cbc 100644 --- a/sbt-bridge/test/xsbt/ExtractUsedNamesSpecification.scala +++ b/sbt-bridge/test/xsbt/ExtractUsedNamesSpecification.scala @@ -79,10 +79,10 @@ class ExtractUsedNamesSpecification { val usedNames = compilerForTesting.extractUsedNamesFromSrc(srcA, srcB, srcC, srcD) val scalaVersion = scala.util.Properties.versionNumberString val namesA = standardNames ++ Set("Nothing", "Any") - val namesAX = standardNames ++ objectStandardNames ++ Set("x", "T", "A", "Nothing", "Any", "scala") + val namesAX = standardNames ++ Set("x", "T", "A", "Nothing", "Any") val namesB = Set("A", "Int", "A;init;", "Unit") - val namesC = objectStandardNames ++ Set("B;init;", "B", "Unit") - val namesD = standardNames ++ objectStandardNames ++ Set("C", "X", "foo", "Int", "T") + val namesC = Set("B;init;", "B", "Unit") + val namesD = standardNames ++ Set("C", "X", "foo", "Int", "T") assertEquals(namesA, usedNames("A")) assertEquals(namesAX, usedNames("A.X")) assertEquals(namesB, usedNames("B")) @@ -131,13 +131,13 @@ class ExtractUsedNamesSpecification { val compilerForTesting = new ScalaCompilerForUnitTesting val usedNames = compilerForTesting.extractUsedNamesFromSrc(src1, src2) val expectedNames_lista = - standardNames ++ objectStandardNames ++ Set("B", "lista", "List", "A") + standardNames ++ Set("B", "lista", "List", "A") val expectedNames_at = - standardNames ++ objectStandardNames ++ Set("B", "at", "A", "T", "X0", "X1") + standardNames ++ Set("B", "at", "A", "T", "X0", "X1") val expectedNames_as = - standardNames ++ objectStandardNames ++ Set("B", "as", "S", "Y") + standardNames ++ Set("B", "as", "S", "Y") val expectedNames_foo = - standardNames ++ objectStandardNames ++ + standardNames ++ Set("B", "foo", "M", @@ -146,7 +146,7 @@ class ExtractUsedNamesSpecification { "???", "Nothing") val expectedNames_bar = - standardNames ++ objectStandardNames ++ + standardNames ++ Set("B", "bar", "P1", @@ -174,7 +174,7 @@ class ExtractUsedNamesSpecification { |""".stripMargin val compilerForTesting = new ScalaCompilerForUnitTesting val usedNames = compilerForTesting.extractUsedNamesFromSrc(srcFoo, srcBar) - val expectedNames = standardNames ++ objectStandardNames ++ Set("Outer", "TypeInner", "Inner", "Int") + val expectedNames = standardNames ++ Set("Outer", "TypeInner", "Inner", "Int") assertEquals(expectedNames, usedNames("Bar")) } @@ -227,7 +227,7 @@ class ExtractUsedNamesSpecification { def findPatMatUsages(in: String): Set[String] = { val compilerForTesting = new ScalaCompilerForUnitTesting val (_, callback) = - compilerForTesting.compileSrcs(List(List(sealedClass, in)), reuseCompilerInstance = false) + compilerForTesting.compileSrcs(List(List(sealedClass, in))) val clientNames = callback.usedNamesAndScopes.view.filterKeys(!_.startsWith("base.")) val names: Set[String] = clientNames.flatMap { @@ -309,9 +309,4 @@ class ExtractUsedNamesSpecification { // the return type of the default constructor is Unit "Unit" ) - - private val objectStandardNames = Set( - // all Dotty objects extend scala.Serializable - "scala", "Serializable" - ) } diff --git a/sbt-bridge/test/xsbt/ScalaCompilerForUnitTesting.scala b/sbt-bridge/test/xsbt/ScalaCompilerForUnitTesting.scala index e81d58a07744..e58f9fefd92d 100644 --- a/sbt-bridge/test/xsbt/ScalaCompilerForUnitTesting.scala +++ b/sbt-bridge/test/xsbt/ScalaCompilerForUnitTesting.scala @@ -1,7 +1,7 @@ /** Adapted from https://github.com/sbt/sbt/blob/0.13/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala */ package xsbt -import xsbti.compile.SingleOutput +import xsbti.compile.{CompileProgress, SingleOutput} import java.io.File import xsbti._ import sbt.io.IO @@ -9,6 +9,8 @@ import xsbti.api.{ ClassLike, Def, DependencyContext } import DependencyContext._ import xsbt.api.SameAPI import sbt.internal.util.ConsoleLogger +import dotty.tools.io.PlainFile.toPlainFile +import dotty.tools.xsbt.CompilerBridge import TestCallback.ExtractedClassDependencies @@ -32,8 +34,8 @@ class ScalaCompilerForUnitTesting { * Compiles given source code using Scala compiler and returns API representation * extracted by ExtractAPI class. */ - def extractApisFromSrcs(reuseCompilerInstance: Boolean)(srcs: List[String]*): Seq[Seq[ClassLike]] = { - val (tempSrcFiles, analysisCallback) = compileSrcs(srcs.toList, reuseCompilerInstance) + def extractApisFromSrcs(srcs: List[String]*): Seq[Seq[ClassLike]] = { + val (tempSrcFiles, analysisCallback) = compileSrcs(srcs.toList) tempSrcFiles.map(analysisCallback.apis) } @@ -91,7 +93,7 @@ class ScalaCompilerForUnitTesting { * file system-independent way of testing dependencies between source code "files". */ def extractDependenciesFromSrcs(srcs: List[List[String]]): ExtractedClassDependencies = { - val (_, testCallback) = compileSrcs(srcs, reuseCompilerInstance = true) + val (_, testCallback) = compileSrcs(srcs) val memberRefDeps = testCallback.classDependencies collect { case (target, src, DependencyByMemberRef) => (src, target) @@ -117,50 +119,47 @@ class ScalaCompilerForUnitTesting { * useful to compile macros, which cannot be used in the same compilation run that * defines them. * - * The `reuseCompilerInstance` parameter controls whether the same Scala compiler instance - * is reused between compiling source groups. Separate compiler instances can be used to - * test stability of API representation (with respect to pickling) or to test handling of - * binary dependencies. - * * The sequence of temporary files corresponding to passed snippets and analysis * callback is returned as a result. */ - def compileSrcs(groupedSrcs: List[List[String]], - reuseCompilerInstance: Boolean): (Seq[File], TestCallback) = { - // withTemporaryDirectory { temp => - { + def compileSrcs(groupedSrcs: List[List[String]]): (Seq[File], TestCallback) = { val temp = IO.createTemporaryDirectory val analysisCallback = new TestCallback val classesDir = new File(temp, "classes") classesDir.mkdir() - lazy val commonCompilerInstanceAndCtx = prepareCompiler(classesDir, analysisCallback, classesDir.toString) + val bridge = new CompilerBridge val files = for ((compilationUnit, unitId) <- groupedSrcs.zipWithIndex) yield { - // use a separate instance of the compiler for each group of sources to - // have an ability to test for bugs in instability between source and pickled - // representation of types - val (compiler, ctx) = if (reuseCompilerInstance) commonCompilerInstanceAndCtx else - prepareCompiler(classesDir, analysisCallback, classesDir.toString) - val run = compiler.newRun(ctx) - val srcFiles = compilationUnit.toSeq.zipWithIndex map { - case (src, i) => + val srcFiles = compilationUnit.toSeq.zipWithIndex.map { + (src, i) => val fileName = s"Test-$unitId-$i.scala" prepareSrcFile(temp, fileName, src) } - val srcFilePaths = srcFiles.map(srcFile => srcFile.getAbsolutePath).toList - run.compile(srcFilePaths) + val virtualSrcFiles = srcFiles.map(file => TestVirtualFile(file.toPath)).toArray + val classesDirPath = classesDir.getAbsolutePath.toString + val output = new SingleOutput: + def getOutputDirectory() = classesDir + + bridge.run( + virtualSrcFiles.toArray, + new TestDependencyChanges, + Array("-Yforce-sbt-phases", "-classpath", classesDirPath, "-usejavacp", "-d", classesDirPath), + output, + analysisCallback, + new TestReporter, + new CompileProgress {}, + new TestLogger + ) - // srcFilePaths.foreach(f => new File(f).delete) srcFiles } (files.flatten.toSeq, analysisCallback) - } } def compileSrcs(srcs: String*): (Seq[File], TestCallback) = { - compileSrcs(List(srcs.toList), reuseCompilerInstance = true) + compileSrcs(List(srcs.toList)) } private def prepareSrcFile(baseDir: File, fileName: String, src: String): File = { @@ -168,28 +167,5 @@ class ScalaCompilerForUnitTesting { IO.write(srcFile, src) srcFile } - - private def prepareCompiler(outputDir: File, analysisCallback: AnalysisCallback, classpath: String = ".") = { - val args = Array.empty[String] - - import dotty.tools.dotc.{Compiler, Driver} - import dotty.tools.dotc.core.Contexts._ - - val driver = new TestDriver - val ctx = (new ContextBase).initialCtx.fresh.setSbtCallback(analysisCallback) - driver.getCompiler(Array("-classpath", classpath, "-usejavacp", "-d", outputDir.getAbsolutePath), ctx) - } - - private object ConsoleReporter extends Reporter { - def reset(): Unit = () - def hasErrors: Boolean = false - def hasWarnings: Boolean = false - def printWarnings(): Unit = () - def problems(): Array[xsbti.Problem] = Array.empty - def log(problem: xsbti.Problem): Unit = println(problem.message) - def comment(pos: Position, msg: String): Unit = () - def printSummary(): Unit = () - } - } diff --git a/sbt-bridge/test/xsbt/TestDependencyChanges.scala b/sbt-bridge/test/xsbt/TestDependencyChanges.scala new file mode 100644 index 000000000000..f31a314ba036 --- /dev/null +++ b/sbt-bridge/test/xsbt/TestDependencyChanges.scala @@ -0,0 +1,9 @@ +package xsbt + +import xsbti.compile.* + +class TestDependencyChanges extends DependencyChanges: + def isEmpty(): Boolean = ??? + def modifiedBinaries(): Array[java.io.File] = ??? + def modifiedClasses(): Array[String] = ??? + def modifiedLibraries(): Array[xsbti.VirtualFileRef] = ??? diff --git a/sbt-bridge/test/xsbt/TestDriver.scala b/sbt-bridge/test/xsbt/TestDriver.scala deleted file mode 100644 index 790c14f4b912..000000000000 --- a/sbt-bridge/test/xsbt/TestDriver.scala +++ /dev/null @@ -1,13 +0,0 @@ -package xsbt - -import dotty.tools.dotc._ -import core.Contexts._ - -class TestDriver extends Driver { - override protected def sourcesRequired = false - - def getCompiler(args: Array[String], rootCtx: Context) = { - val (fileNames, ctx) = setup(args, rootCtx) - (newCompiler(ctx), ctx) - } -} diff --git a/sbt-bridge/test/xsbt/TestLogger.scala b/sbt-bridge/test/xsbt/TestLogger.scala new file mode 100644 index 000000000000..598887e3f8e6 --- /dev/null +++ b/sbt-bridge/test/xsbt/TestLogger.scala @@ -0,0 +1,12 @@ +package xsbt + +import java.util.function.Supplier + +import xsbti.* + +class TestLogger extends Logger: + override def debug(msg: Supplier[String]): Unit = () + override def error(msg: Supplier[String]): Unit = () + override def info(msg: Supplier[String]): Unit = () + override def warn(msg: Supplier[String]): Unit = () + override def trace(exception: Supplier[Throwable]): Unit = () diff --git a/sbt-bridge/test/xsbt/TestReporter.scala b/sbt-bridge/test/xsbt/TestReporter.scala new file mode 100644 index 000000000000..cab9823813a6 --- /dev/null +++ b/sbt-bridge/test/xsbt/TestReporter.scala @@ -0,0 +1,13 @@ +package xsbt + +import xsbti.* + +class TestReporter extends Reporter: + private val allProblems = collection.mutable.ListBuffer.empty[Problem] + def comment(position: Position, msg: String): Unit = () + def hasErrors(): Boolean = allProblems.exists(_.severity == Severity.Error) + def hasWarnings(): Boolean = allProblems.exists(_.severity == Severity.Warn) + def log(problem: Problem): Unit = allProblems.append(problem) + def printSummary(): Unit = () + def problems(): Array[Problem] = allProblems.toArray + def reset(): Unit = allProblems.clear() diff --git a/sbt-bridge/test/xsbt/TestVirtualFile.scala b/sbt-bridge/test/xsbt/TestVirtualFile.scala new file mode 100644 index 000000000000..db00038272a8 --- /dev/null +++ b/sbt-bridge/test/xsbt/TestVirtualFile.scala @@ -0,0 +1,14 @@ +package xsbt + +import xsbti.PathBasedFile +import java.nio.file.{Files, Path} +import scala.io.Source +import scala.io.Codec + +class TestVirtualFile(path: Path) extends PathBasedFile: + override def contentHash(): Long = ??? + override def input(): java.io.InputStream = Files.newInputStream(path) + override def id(): String = name() + override def name(): String = path.toFile.getName + override def names(): Array[String] = ??? + override def toPath(): Path = path diff --git a/sbt-bridge/test/xsbti/TestCallback.scala b/sbt-bridge/test/xsbti/TestCallback.scala index 3348fd2d90f3..a0919dc69bc4 100644 --- a/sbt-bridge/test/xsbti/TestCallback.scala +++ b/sbt-bridge/test/xsbti/TestCallback.scala @@ -2,7 +2,9 @@ package xsbti import java.io.File +import java.nio.file.Path import scala.collection.mutable.ArrayBuffer +import xsbti.VirtualFileRef import xsbti.api.ClassLike import xsbti.api.DependencyContext import DependencyContext._ @@ -24,12 +26,14 @@ class TestCallback extends AnalysisCallback assert(!apis.contains(source), s"startSource can be called only once per source file: $source") apis(source) = Seq.empty } + override def startSource(source: VirtualFile): Unit = ??? override def binaryDependency(binary: File, name: String, fromClassName: String, source: File, context: DependencyContext): Unit = { binaryDependencies += ((binary, name, fromClassName, source, context)) } + override def binaryDependency(binary: Path, name: String, fromClassName: String, source: VirtualFileRef, context: DependencyContext): Unit = ??? - def generatedNonLocalClass(source: File, + override def generatedNonLocalClass(source: File, module: File, binaryClassName: String, srcClassName: String): Unit = { @@ -37,12 +41,13 @@ class TestCallback extends AnalysisCallback classNames(source) += ((srcClassName, binaryClassName)) () } + override def generatedNonLocalClass(source: VirtualFileRef, module: Path, binaryClassName: String, srcClassName: String): Unit = ??? - def generatedLocalClass(source: File, module: File): Unit = { + override def generatedLocalClass(source: File, module: File): Unit = { products += ((source, module)) () } - + override def generatedLocalClass(source: VirtualFileRef, module: Path): Unit = ??? override def classDependency(onClassName: String, sourceClassName: String, context: DependencyContext): Unit = { if (onClassName != sourceClassName) classDependencies += ((onClassName, sourceClassName, context)) @@ -51,15 +56,23 @@ class TestCallback extends AnalysisCallback override def usedName(className: String, name: String, scopes: EnumSet[UseScope]): Unit = { usedNamesAndScopes(className) += TestUsedName(name, scopes) } + override def api(source: File, classApi: ClassLike): Unit = { apis(source) = classApi +: apis(source) } + override def api(source: VirtualFileRef, classApi: ClassLike): Unit = ??? + override def problem(category: String, pos: xsbti.Position, message: String, severity: xsbti.Severity, reported: Boolean): Unit = () override def dependencyPhaseCompleted(): Unit = () override def apiPhaseCompleted(): Unit = () override def enabled(): Boolean = true - def mainClass(source: File, className: String): Unit = () + override def mainClass(source: File, className: String): Unit = () + override def mainClass(source: VirtualFileRef, className: String): Unit = ??? + + override def classesInOutputJar(): java.util.Set[String] = ??? + override def getPickleJarPair(): java.util.Optional[xsbti.T2[Path, Path]] = ??? + override def isPickleJava(): Boolean = ??? } object TestCallback { @@ -78,14 +91,8 @@ object TestCallback { } private def pairsToMultiMap[A, B](pairs: collection.Seq[(A, B)]): Map[A, Set[B]] = { - import scala.collection.mutable.{ HashMap, MultiMap } - val emptyMultiMap = new HashMap[A, scala.collection.mutable.Set[B]] with MultiMap[A, B] - val multiMap = pairs.foldLeft(emptyMultiMap) { - case (acc, (key, value)) => - acc.addBinding(key, value) - } - // convert all collections to immutable variants - multiMap.toMap.view.mapValues(_.toSet).toMap.withDefaultValue(Set.empty) + pairs.groupBy(_._1).view.mapValues(values => values.map(_._2).toSet) + .toMap.withDefaultValue(Set.empty) } } }