Skip to content

Revive tests for sbt-bridge #16659

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")).
Expand Down
7 changes: 3 additions & 4 deletions sbt-bridge/test/xsbt/ExtractAPISpecification.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down
25 changes: 10 additions & 15 deletions sbt-bridge/test/xsbt/ExtractUsedNamesSpecification.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down Expand Up @@ -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",
Expand All @@ -146,7 +146,7 @@ class ExtractUsedNamesSpecification {
"???",
"Nothing")
val expectedNames_bar =
standardNames ++ objectStandardNames ++
standardNames ++
Set("B",
"bar",
"P1",
Expand Down Expand Up @@ -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"))
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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"
)
}
76 changes: 26 additions & 50 deletions sbt-bridge/test/xsbt/ScalaCompilerForUnitTesting.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
/** 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
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

Expand All @@ -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)
}

Expand Down Expand Up @@ -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)
Expand All @@ -117,79 +119,53 @@ 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 = {
val srcFile = new File(baseDir, fileName)
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 = ()
}

}

9 changes: 9 additions & 0 deletions sbt-bridge/test/xsbt/TestDependencyChanges.scala
Original file line number Diff line number Diff line change
@@ -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] = ???
13 changes: 0 additions & 13 deletions sbt-bridge/test/xsbt/TestDriver.scala

This file was deleted.

12 changes: 12 additions & 0 deletions sbt-bridge/test/xsbt/TestLogger.scala
Original file line number Diff line number Diff line change
@@ -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 = ()
13 changes: 13 additions & 0 deletions sbt-bridge/test/xsbt/TestReporter.scala
Original file line number Diff line number Diff line change
@@ -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()
14 changes: 14 additions & 0 deletions sbt-bridge/test/xsbt/TestVirtualFile.scala
Original file line number Diff line number Diff line change
@@ -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
31 changes: 19 additions & 12 deletions sbt-bridge/test/xsbti/TestCallback.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand All @@ -24,25 +26,28 @@ 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 = {
products += ((source, module))
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))
Expand All @@ -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 {
Expand All @@ -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)
}
}
}
Expand Down