Skip to content

Commit a506548

Browse files
committed
Add Scala 3 to MiMa's version matrix
1 parent f0a002e commit a506548

File tree

43 files changed

+214
-84
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+214
-84
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ jobs:
1010
- { name: testFunctional 2.11, script: sbt "functional-tests/runMain com.typesafe.tools.mima.lib.UnitTests -211" }
1111
- { name: testFunctional 2.12, script: sbt "functional-tests/runMain com.typesafe.tools.mima.lib.UnitTests -212" }
1212
- { name: testFunctional 2.13, script: sbt "functional-tests/runMain com.typesafe.tools.mima.lib.UnitTests -213" }
13+
- { name: testFunctional 3, script: sbt "functional-tests/runMain com.typesafe.tools.mima.lib.UnitTests -3" }
1314
- { name: scripted 1/2, script: sbt "scripted sbt-mima-plugin/*1of2" }
1415
- { name: scripted 2/2, script: sbt "scripted sbt-mima-plugin/*2of2" }
1516

core/src/main/scala/com/typesafe/tools/mima/lib/analyze/method/MethodChecker.scala

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ private[analyze] object MethodChecker {
1313

1414
/** Analyze incompatibilities that may derive from new methods in `newclazz`. */
1515
private def checkNew(oldclazz: ClassInfo, newclazz: ClassInfo): List[Problem] = {
16-
(if (newclazz.isClass) Nil else checkEmulatedConcreteMethodsProblems(oldclazz, newclazz)) :::
17-
checkDeferredMethodsProblems(oldclazz, newclazz) :::
18-
checkInheritedNewAbstractMethodProblems(oldclazz, newclazz)
16+
val problems1 = if (newclazz.isClass) Nil else checkEmulatedConcreteMethodsProblems(oldclazz, newclazz)
17+
val problems2 = checkDeferredMethodsProblems(oldclazz, newclazz)
18+
val problems3 = checkInheritedNewAbstractMethodProblems(oldclazz, newclazz)
19+
problems1 ::: problems2 ::: problems3
1920
}
2021

2122
private def checkExisting1(oldmeth: MethodInfo, newclazz: ClassInfo): Option[Problem] = {
@@ -138,11 +139,9 @@ private[analyze] object MethodChecker {
138139
for {
139140
newmeth <- newclazz.deferredMethods.iterator
140141
problem <- oldclazz.lookupMethods(newmeth).find(_.descriptor == newmeth.descriptor) match {
141-
case None => Some(ReversedMissingMethodProblem(newmeth))
142-
case Some(oldmeth) =>
143-
if (newclazz.isClass && oldmeth.isConcrete)
144-
Some(ReversedAbstractMethodProblem(newmeth))
145-
else None
142+
case None => Some(ReversedMissingMethodProblem(newmeth))
143+
case Some(oldmeth) if newclazz.isClass && oldmeth.isConcrete => Some(ReversedAbstractMethodProblem(newmeth))
144+
case Some(_) => None
146145
}
147146
} yield problem
148147
}.toList

functional-tests/src/main/scala/com/typesafe/tools/mima/lib/AppRunTest.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@ object AppRunTest {
1313
def testAppRun1(testCase: TestCase, v1: Directory, v2: Directory, oracleFile: Path): Try[Unit] = for {
1414
() <- testCase.compileBoth
1515
pending = testCase.versionedFile("testAppRun.pending").exists
16+
insane = testCase.versionedFile("testAppRun.insane").exists
1617
expectOk = testCase.blankFile(testCase.versionedFile(oracleFile))
1718
//() <- testCase.compileApp(v2) // compile app with v2
1819
//() <- testCase.runMain(v2) // sanity check 1: run app with v2
1920
() <- testCase.compileApp(v1) // recompile app with v1
20-
() <- testCase.runMain(v1) // sanity check 2: run app with v1
21+
() <- testCase.runMain(v1) match { // sanity check 2: run app with v1
22+
case Failure(t) if !insane => Failure(new Exception("Sanity runMain check failed", t, true, false) {})
23+
case _ => Success(())
24+
}
2125
() <- testCase.runMain(v2) match { // test: run app, compiled with v1, with v2
2226
case Failure(t) if !pending && expectOk => Failure(t)
2327
case Success(()) if !pending && !expectOk => Failure(new Exception("expected running App to fail"))

functional-tests/src/main/scala/com/typesafe/tools/mima/lib/CollectProblemsTest.scala

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,24 @@ object CollectProblemsTest {
3131
case Forwards => "other"
3232
}
3333

34-
// diff between the oracle and the collected problems
35-
val unexpected = problems.filter(p => !expected.contains(p.description(affectedVersion)))
36-
val unreported = expected.diff(problems.map(_.description(affectedVersion)))
34+
val reported = problems.map(_.description(affectedVersion))
3735

3836
val msg = new StringBuilder("\n")
39-
def pp(start: String, lines: List[String]) = {
40-
if (lines.isEmpty) ()
41-
else lines.sorted.distinct.addString(msg, s"$start (${lines.size}):\n - ", "\n - ", "\n")
42-
}
43-
pp("The following problem(s) were expected but not reported", unreported)
44-
pp("The following problem(s) were reported but not expected", unexpected.map(_.description(affectedVersion)))
45-
pp("Filter with:", unexpected.flatMap(_.howToFilter))
46-
pp("Or filter with:", unexpected.flatMap(p => p.matchName.map { matchName =>
47-
s"{ matchName=$dq$matchName$dq , problemName=${p.getClass.getSimpleName} }"
48-
}))
37+
def pp(start: String, lines: List[String]) =
38+
if (lines.nonEmpty) {
39+
msg.append(s"$start (${lines.size}):")
40+
lines.sorted.distinct.map("\n - " + _).foreach(msg.append(_))
41+
msg.append("\n")
42+
}
43+
44+
pp("The following problem(s) were expected but not reported", expected.diff(reported))
45+
pp("The following problem(s) were reported but not expected", reported.diff(expected))
4946

5047
msg.mkString match {
5148
case "\n" => Success(())
52-
case msg => Failure(new Exception(msg))
49+
case msg =>
50+
Console.err.println(msg)
51+
Failure(new Exception("CollectProblemsTest failure"))
5352
}
5453
}
55-
56-
private final val dq = '"' // scala/bug#6476 -.-
5754
}

functional-tests/src/main/scala/com/typesafe/tools/mima/lib/ScalaCompiler.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import scala.util.{ Failure, Success, Try }
77
import coursier._
88

99
final class ScalaCompiler(val version: String) {
10-
val jars = Coursier.fetch(Dependency(mod"org.scala-lang:scala-compiler", version))
10+
val isScala3 = version.startsWith("3.")
11+
12+
val name = if (isScala3) ModuleName(s"scala3-compiler_$version") else name"scala-compiler"
13+
val jars = Coursier.fetch(Dependency(Module(org"org.scala-lang", name), version))
1114

1215
val classLoader = new URLClassLoader(jars.toArray.map(_.toURI.toURL), parentClassLoader())
1316

@@ -17,16 +20,18 @@ final class ScalaCompiler(val version: String) {
1720

1821
def compile(args: Seq[String]): Try[Unit] = {
1922
import scala.language.reflectiveCalls
20-
val cls = classLoader.loadClass("scala.tools.nsc.Main$")
23+
val clsName = if (isScala3) "dotty.tools.dotc.Main$" else "scala.tools.nsc.Main$"
24+
val cls = classLoader.loadClass(clsName)
2125
type Main = { def process(args: Array[String]): Any; def reporter: Reporter }
2226
type Reporter = { def hasErrors: Boolean }
2327
val m = cls.getField("MODULE$").get(null).asInstanceOf[Main]
2428
Try {
2529
val success = m.process(args.toArray) match {
2630
case b: Boolean => b
2731
case null => !m.reporter.hasErrors // nsc 2.11
32+
case x => !x.asInstanceOf[Reporter].hasErrors // dotc
2833
}
2934
if (success) Success(()) else Failure(new Exception("scalac failed"))
30-
}
35+
}.flatten
3136
}
3237
}

functional-tests/src/main/scala/com/typesafe/tools/mima/lib/Test.scala

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,19 @@ object Test {
1010
def pass = s"${Console.GREEN}\u2713${Console.RESET}" // check mark (green)
1111
def fail = s"${Console.RED}\u2717${Console.RESET}" // cross mark (red)
1212

13-
def testAll(tests: List[Test]): Try[Unit] = {
14-
tests.iterator.map(_.run()).foldLeft(Try(())) {
15-
case (res @ Failure(e1), Failure(e2)) => e1.addSuppressed(e2); res
16-
case (res @ Failure(_), _) => res
17-
case (_, res) => res
13+
def testAll(tests: List[Test1]): Try[Unit] = {
14+
val (successes, failures) = tests.map(t => t -> Test.run1(t.label, t.action)).partition(_._2.isSuccess)
15+
println(s"${tests.size} tests, ${successes.size} successes, ${failures.size} failures")
16+
if (failures.nonEmpty) {
17+
val failureNames = failures.map { case ((t1, _)) => t1.name }
18+
println("Failures:")
19+
failureNames.foreach(name => println(s"* $name"))
20+
println(s"functional-tests/Test/run ${failureNames.mkString(" ")}")
21+
}
22+
failures.foldLeft(Try(())) {
23+
case (res @ Failure(e1), (_, Failure(e2))) => e1.addSuppressed(e2); res
24+
case (res @ Failure(_), _) => res
25+
case (_, (_, res)) => res
1826
}
1927
}
2028

@@ -24,17 +32,6 @@ object Test {
2432
case res @ Failure(ex) => println(s"- $fail $label: $ex"); res
2533
}
2634
}
27-
28-
implicit class TestOps(private val t: Test) extends AnyVal {
29-
def tests: List[Test1] = t match {
30-
case t1: Test1 => List(t1)
31-
case Tests(tests) => tests
32-
}
33-
34-
def munitTests: List[GenericTest[Unit]] = for {
35-
test <- t.tests
36-
} yield new GenericTest(test.label, () => test.unsafeRunTest(), Set.empty, Location.empty)
37-
}
3835
}
3936

4037
sealed trait Test {
@@ -51,5 +48,22 @@ sealed trait Test {
5148
}
5249
}
5350

51+
object Test1 {
52+
implicit class Ops(private val t: Test1) extends AnyVal {
53+
def name: String = t.label.indexOf(" / ") match {
54+
case -1 => t.label
55+
case idx => t.label.drop(idx + 3)
56+
}
57+
}
58+
}
59+
60+
object Tests {
61+
implicit class Ops(private val t: Tests) extends AnyVal {
62+
def munitTests: List[GenericTest[Unit]] = for {
63+
test <- t.tests
64+
} yield new GenericTest(test.label, () => test.unsafeRunTest(), Set.empty, Location.empty)
65+
}
66+
}
67+
5468
case class Test1(label: String, action: () => Try[Unit]) extends Test
5569
case class Tests(tests: List[Test1]) extends Test

functional-tests/src/main/scala/com/typesafe/tools/mima/lib/TestCase.scala

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import java.io.{ ByteArrayOutputStream, PrintStream }
44
import java.net.{ URI, URLClassLoader }
55
import javax.tools._
66

7+
import scala.annotation.tailrec
78
import scala.collection.JavaConverters._
89
import scala.collection.mutable
910
import scala.reflect.internal.util.BatchSourceFile
@@ -14,7 +15,7 @@ import com.typesafe.tools.mima.core.ClassPath
1415

1516
final class TestCase(val baseDir: Directory, val scalaCompiler: ScalaCompiler, val javaCompiler: JavaCompiler) {
1617
def name = baseDir.name
17-
def scalaBinaryVersion = scalaCompiler.version.take(4)
18+
def scalaBinaryVersion = if (scalaCompiler.isScala3) "3" else scalaCompiler.version.take(4)
1819
def scalaJars = scalaCompiler.jars
1920

2021
val srcV1 = (baseDir / "v1").toDirectory
@@ -42,7 +43,7 @@ final class TestCase(val baseDir: Directory, val scalaCompiler: ScalaCompiler, v
4243
val sourceFiles = lsSrcs(srcDir)
4344
if (sourceFiles.forall(_.isJava)) return Success(())
4445
val bootcp = ClassPath.join(scalaJars.map(_.getPath))
45-
val cpOpt = if (cp.isEmpty) Nil else List("-cp", ClassPath.join(cp.map(_.path)))
46+
val cpOpt = if (cp.isEmpty) Nil else List("-classpath", ClassPath.join(cp.map(_.path)))
4647
val paths = sourceFiles.map(_.path)
4748
val args = "-bootclasspath" :: bootcp :: cpOpt ::: "-d" :: s"$out" :: paths
4849
scalaCompiler.compile(args)
@@ -78,7 +79,16 @@ final class TestCase(val baseDir: Directory, val scalaCompiler: ScalaCompiler, v
7879
System.setErr(printStream)
7980
Console.withErr(printStream) {
8081
Console.withOut(printStream) {
81-
Try(meth.invoke(null, new Array[String](0)): Unit)
82+
try {
83+
meth.invoke(null, new Array[String](0))
84+
Success(())
85+
} catch {
86+
case e: VirtualMachineError => throw e
87+
case e: ThreadDeath => throw e
88+
case e: InterruptedException => throw e
89+
case e: scala.util.control.ControlThrowable => throw e // don't rethrow LinkageError
90+
case e: Throwable => Failure(rootCause(e))
91+
}
8292
}
8393
}
8494
} finally {
@@ -98,9 +108,11 @@ final class TestCase(val baseDir: Directory, val scalaCompiler: ScalaCompiler, v
98108
val p = baseDir.resolve(path).toFile
99109
val p211 = (p.parent / (s"${p.stripExtension}-2.11")).addExtension(p.extension).toFile
100110
val p212 = (p.parent / (s"${p.stripExtension}-2.12")).addExtension(p.extension).toFile
111+
val p3 = (p.parent / (s"${p.stripExtension}-3" )).addExtension(p.extension).toFile
101112
scalaBinaryVersion match {
102113
case "2.11" => if (p211.exists) p211 else if (p212.exists) p212 else p
103114
case "2.12" => if (p212.exists) p212 else p
115+
case "3" => if (p3.exists) p3 else p
104116
case _ => p
105117
}
106118
}
@@ -112,5 +124,15 @@ final class TestCase(val baseDir: Directory, val scalaCompiler: ScalaCompiler, v
112124
()
113125
}
114126

127+
@tailrec private def rootCause(x: Throwable): Throwable = x match {
128+
case _: ExceptionInInitializerError |
129+
_: java.lang.reflect.InvocationTargetException |
130+
_: java.lang.reflect.UndeclaredThrowableException |
131+
_: java.util.concurrent.ExecutionException
132+
if x.getCause != null =>
133+
rootCause(x.getCause)
134+
case _ => x
135+
}
136+
115137
override def toString = s"TestCase(baseDir=${baseDir.name}, scalaVersion=${scalaCompiler.version})"
116138
}

functional-tests/src/main/scala/com/typesafe/tools/mima/lib/TestCli.scala

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import scala.util.{ Properties => StdLibProps }
99

1010
object TestCli {
1111
val scala211 = "2.11.12"
12-
val scala212 = "2.12.11"
13-
val scala213 = "2.13.2"
12+
val scala212 = "2.12.12"
13+
val scala213 = "2.13.4"
14+
val scala3 = "3.0.0-M3"
1415
val hostScalaVersion = StdLibProps.scalaPropOrNone("maven.version.number").get
15-
val allScalaVersions = List(scala211, scala212, scala213)
16+
val allScalaVersions = List(scala211, scala212, scala213, scala3)
1617
val testsDir = Directory("functional-tests/src/test")
1718

1819
def argsToTests(args: List[String], runTestCase: TestCase => Try[Unit]): Tests =
@@ -24,7 +25,7 @@ object TestCli {
2425
def testCaseToTest1(tc: TestCase, runTestCase: TestCase => Try[Unit]): Test1 =
2526
Test(s"${tc.scalaBinaryVersion} / ${tc.name}", runTestCase(tc))
2627

27-
def fromArgs(args: List[String]): List[TestCase] = fromConf(go(args, Conf(Nil, Nil)))
28+
def fromArgs(args: List[String]): List[TestCase] = fromConf(readArgs(args, Conf(Nil, Nil)))
2829

2930
@tailrec def postProcessConf(conf: Conf): Conf = conf match {
3031
case Conf(Nil, _) => postProcessConf(conf.copy(scalaVersions = List(hostScalaVersion)))
@@ -49,13 +50,14 @@ object TestCli {
4950

5051
final case class Conf(scalaVersions: List[String], dirs: List[Directory])
5152

52-
@tailrec private def go(argv: List[String], conf: Conf): Conf = argv match {
53-
case "-213" :: xs => go(xs, conf.copy(scalaVersions = scala213 :: conf.scalaVersions))
54-
case "-212" :: xs => go(xs, conf.copy(scalaVersions = scala212 :: conf.scalaVersions))
55-
case "-211" :: xs => go(xs, conf.copy(scalaVersions = scala211 :: conf.scalaVersions))
56-
case "--scala-version" :: sv :: xs => go(xs, conf.copy(scalaVersions = sv :: conf.scalaVersions))
57-
case "--cross" :: xs => go(xs, conf.copy(scalaVersions = List(scala211, scala212, scala213)))
58-
case s :: xs => go(xs, conf.copy(dirs = testDirs(s) ::: conf.dirs))
53+
@tailrec private def readArgs(args: List[String], conf: Conf): Conf = args match {
54+
case "-3" :: xs => readArgs(xs, conf.copy(scalaVersions = scala3 :: conf.scalaVersions))
55+
case "-213" :: xs => readArgs(xs, conf.copy(scalaVersions = scala213 :: conf.scalaVersions))
56+
case "-212" :: xs => readArgs(xs, conf.copy(scalaVersions = scala212 :: conf.scalaVersions))
57+
case "-211" :: xs => readArgs(xs, conf.copy(scalaVersions = scala211 :: conf.scalaVersions))
58+
case "--scala-version" :: sv :: xs => readArgs(xs, conf.copy(scalaVersions = sv :: conf.scalaVersions))
59+
case "--cross" :: xs => readArgs(xs, conf.copy(scalaVersions = allScalaVersions))
60+
case s :: xs => readArgs(xs, conf.copy(dirs = testDirs(s) ::: conf.dirs))
5961
case Nil => conf
6062
}
6163

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
object App {
22
def main(args: Array[String]): Unit = {
3-
println(new A { def foo = () }.foo)
3+
object a extends A { def foo() = () }
4+
println(a.foo())
45
}
56
}

functional-tests/src/test/abstract-class-extending-new-trait-with-abstract-method-ok/v2/A.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
abstract class A extends B {
2-
def foo: Unit
2+
def foo(): Unit
33
}
44

55
trait B {

0 commit comments

Comments
 (0)