Skip to content

Commit d9f98c2

Browse files
committed
sbt-bridge: Synchronize unit tests with sbt 0.13.14
Also fix a bug where the compiler output for the tests ended up in the wrong directory, causing some new tests from 0.13.14 to fail.
1 parent 6099195 commit d9f98c2

File tree

4 files changed

+148
-20
lines changed

4 files changed

+148
-20
lines changed

sbt-bridge/test/xsbt/ExtractAPISpecification.scala

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
package xsbt
33

44
import org.junit.runner.RunWith
5-
import xsbti.api.ClassLike
6-
import xsbti.api.Def
7-
import xsbt.api.ShowAPI
5+
import xsbti.api._
6+
import xsbt.api.DefaultShowAPI
87
import org.specs2.mutable.Specification
98
import org.specs2.runner.JUnitRunner
109

@@ -17,7 +16,7 @@ class ExtractAPISpecification extends Specification {
1716

1817
def stableExistentialNames: Boolean = {
1918
def compileAndGetFooMethodApi(src: String): Def = {
20-
val compilerForTesting = new ScalaCompilerForUnitTesting
19+
val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = false)
2120
val sourceApi = compilerForTesting.extractApiFromSrc(src)
2221
val FooApi = sourceApi.definitions().find(_.name() == "Foo").get.asInstanceOf[ClassLike]
2322
val fooMethodApi = FooApi.structure().declared().find(_.name == "foo").get
@@ -38,8 +37,81 @@ class ExtractAPISpecification extends Specification {
3837
|
3938
}""".stripMargin
4039
val fooMethodApi2 = compileAndGetFooMethodApi(src2)
40+
4141
fooMethodApi1 == fooMethodApi2
4242
// Fails because xsbt.api is compiled with Scala 2.10
4343
// SameAPI.apply(fooMethodApi1, fooMethodApi2)
4444
}
45+
46+
/**
47+
* Checks if representation of the inherited Namer class (with a declared self variable) in Global.Foo
48+
* is stable between compiling from source and unpickling. We compare extracted APIs of Global when Global
49+
* is compiled together with Namers or Namers is compiled first and then Global refers
50+
* to Namers by unpickling types from class files.
51+
*
52+
* See https://github.com/sbt/sbt/issues/2504
53+
*/
54+
"Self variable and no self type" in {
55+
def selectNamer(api: SourceAPI): ClassLike = {
56+
def selectClass(defs: Iterable[Definition], name: String): ClassLike = defs.collectFirst {
57+
case cls: ClassLike if cls.name == name => cls
58+
}.get
59+
val global = selectClass(api.definitions, "Global")
60+
val foo = selectClass(global.structure.declared, "Global.Foo")
61+
selectClass(foo.structure.inherited, "Namers.Namer")
62+
}
63+
val src1 =
64+
"""|class Namers {
65+
| class Namer { thisNamer => }
66+
|}
67+
|""".stripMargin
68+
val src2 =
69+
"""|class Global {
70+
| class Foo extends Namers
71+
|}
72+
|""".stripMargin
73+
val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = false)
74+
val apis = compilerForTesting.extractApisFromSrcs(reuseCompilerInstance = false)(List(src1, src2), List(src2))
75+
val _ :: src2Api1 :: src2Api2 :: Nil = apis.toList
76+
val namerApi1 = selectNamer(src2Api1)
77+
val namerApi2 = selectNamer(src2Api2)
78+
79+
DefaultShowAPI(namerApi1) == DefaultShowAPI(namerApi2)
80+
// Fails because xsbt.api is compiled with Scala 2.10
81+
// SameAPI(namerApi1, namerApi2)
82+
}
83+
84+
/**
85+
* Checks if self type is properly extracted in various cases of declaring a self type
86+
* with our without a self variable.
87+
*/
88+
"Self type" in {
89+
def collectFirstClass(defs: Array[Definition]): ClassLike = defs.collectFirst {
90+
case c: ClassLike => c
91+
}.get
92+
val srcX = "trait X"
93+
val srcY = "trait Y"
94+
val srcC1 = "class C1 { this: C1 => }"
95+
val srcC2 = "class C2 { thisC: C2 => }"
96+
val srcC3 = "class C3 { this: X => }"
97+
val srcC4 = "class C4 { thisC: X => }"
98+
val srcC5 = "class C5 extends AnyRef with X with Y { self: X with Y => }"
99+
val srcC6 = "class C6 extends AnyRef with X { self: X with Y => }"
100+
// val srcC7 = "class C7 { _ => }" // DOTTY: Syntax not supported
101+
val srcC8 = "class C8 { self => }"
102+
val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = false)
103+
val apis = compilerForTesting.extractApisFromSrcs(reuseCompilerInstance = true)(
104+
List(srcX, srcY, srcC1, srcC2, srcC3, srcC4, srcC5, srcC6, srcC8)
105+
).map(x => collectFirstClass(x.definitions))
106+
val emptyType = new EmptyType
107+
def hasSelfType(c: ClassLike): Boolean =
108+
c.selfType != emptyType
109+
val (withSelfType, withoutSelfType) = apis.partition(hasSelfType)
110+
// DOTTY: In the scalac ExtractAPI phase, the self-type is only
111+
// extracted if it differs from the type of the class for stability
112+
// reasons. This isn't necessary in dotty because we always pickle
113+
// the self type.
114+
withSelfType.map(_.name).toSet === Set("C1", "C2", "C3", "C4", "C5", "C6", "C8")
115+
withoutSelfType.map(_.name).toSet === Set("X", "Y")
116+
}
45117
}

sbt-bridge/test/xsbt/ExtractUsedNamesSpecification.scala

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class ExtractUsedNamesSpecification extends Specification {
7575
|}""".stripMargin
7676
val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = true)
7777
val usedNames = compilerForTesting.extractUsedNamesFromSrc(srcA, srcB)
78+
7879
// DOTTY TODO: "Int" is not actually used, but we collect it because
7980
// it's the inferred return type so it appears in a TypeTree
8081
// We could avoid this by checking if the untyped tree has a return type
@@ -84,6 +85,44 @@ class ExtractUsedNamesSpecification extends Specification {
8485
usedNames === expectedNames
8586
}
8687

88+
"extract names in the types of trees" in {
89+
val src1 = """|class X0
90+
|class X1 extends X0
91+
|class Y
92+
|class A {
93+
| type T >: X1 <: X0
94+
|}
95+
|class M
96+
|class N
97+
|class P0
98+
|class P1 extends P0
99+
|object B {
100+
| type S = Y
101+
| val lista: List[A] = ???
102+
| val at: A#T = ???
103+
| val as: S = ???
104+
| def foo(m: M): N = ???
105+
| def bar[Param >: P1 <: P0](p: Param): Param = ???
106+
|}""".stripMargin
107+
val src2 = """|object Test {
108+
| val x = B.lista
109+
| val y = B.at
110+
| val z = B.as
111+
| B.foo(???)
112+
| B.bar(???)
113+
|}""".stripMargin
114+
val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = true)
115+
val usedNames = compilerForTesting.extractUsedNamesFromSrc(src1, src2)
116+
val expectedNames = standardNames ++ Set("Test", "Test$", "B", "B$",
117+
"Predef", "Predef$", "$qmark$qmark$qmark", "Nothing",
118+
"lista", "List", "A",
119+
"at", "T", "X1", "X0",
120+
"as", "S", "Y",
121+
"foo", "M", "N",
122+
"bar", "P1", "P0")
123+
usedNames === expectedNames
124+
}
125+
87126
// test for https://github.com/gkossakowski/sbt/issues/3
88127
"used names from the same compilation unit" in {
89128
val src = "class A { def foo: Int = 0; def bar: Int = foo }"

sbt-bridge/test/xsbt/ScalaCompilerForUnitTesting.scala

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import _root_.scala.tools.nsc.reporters.ConsoleReporter
77
import _root_.scala.tools.nsc.Settings
88
import xsbti._
99
import xsbti.api.SourceAPI
10-
import sbt.IO.withTemporaryDirectory
10+
import sbt.IO._
1111
import xsbti.api.ClassLike
1212
import xsbti.api.Definition
1313
import xsbti.api.Def
@@ -21,7 +21,7 @@ import ScalaCompilerForUnitTesting.ExtractedSourceDependencies
2121
* Provides common functionality needed for unit tests that require compiling
2222
* source code using Scala compiler.
2323
*/
24-
class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) {
24+
class ScalaCompilerForUnitTesting(nameHashing: Boolean, includeSynthToNameHashing: Boolean = false) {
2525
import scala.language.reflectiveCalls
2626

2727
/**
@@ -33,6 +33,15 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) {
3333
analysisCallback.apis(tempSrcFile)
3434
}
3535

36+
/**
37+
* Compiles given source code using Scala compiler and returns API representation
38+
* extracted by ExtractAPI class.
39+
*/
40+
def extractApisFromSrcs(reuseCompilerInstance: Boolean)(srcs: List[String]*): Seq[SourceAPI] = {
41+
val (tempSrcFiles, analysisCallback) = compileSrcs(srcs.toList, reuseCompilerInstance)
42+
tempSrcFiles.map(analysisCallback.apis)
43+
}
44+
3645
def extractUsedNamesFromSrc(src: String): Set[String] = {
3746
val (Seq(tempSrcFile), analysisCallback) = compileSrcs(src)
3847
analysisCallback.usedNames(tempSrcFile)
@@ -66,7 +75,7 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) {
6675
def extractDependenciesFromSrcs(srcs: List[Map[Symbol, String]]): ExtractedSourceDependencies = {
6776
val rawGroupedSrcs = srcs.map(_.values.toList)
6877
val symbols = srcs.flatMap(_.keys)
69-
val (tempSrcFiles, testCallback) = compileSrcs(rawGroupedSrcs)
78+
val (tempSrcFiles, testCallback) = compileSrcs(rawGroupedSrcs, reuseCompilerInstance = true)
7079
val fileToSymbol = (tempSrcFiles zip symbols).toMap
7180

7281
val memberRefFileDeps = testCallback.sourceDependencies collect {
@@ -109,19 +118,31 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) {
109118
* useful to compile macros, which cannot be used in the same compilation run that
110119
* defines them.
111120
*
121+
* The `reuseCompilerInstance` parameter controls whether the same Scala compiler instance
122+
* is reused between compiling source groups. Separate compiler instances can be used to
123+
* test stability of API representation (with respect to pickling) or to test handling of
124+
* binary dependencies.
125+
*
112126
* The sequence of temporary files corresponding to passed snippets and analysis
113127
* callback is returned as a result.
114128
*/
115-
private def compileSrcs(groupedSrcs: List[List[String]]): (Seq[File], TestCallback) = {
116-
withTemporaryDirectory { temp =>
117-
val analysisCallback = new TestCallback(nameHashing)
129+
private def compileSrcs(groupedSrcs: List[List[String]],
130+
reuseCompilerInstance: Boolean): (Seq[File], TestCallback) = {
131+
// withTemporaryDirectory { temp =>
132+
{
133+
val temp = createTemporaryDirectory
134+
val analysisCallback = new TestCallback(nameHashing, includeSynthToNameHashing)
118135
val classesDir = new File(temp, "classes")
119136
classesDir.mkdir()
120137

121-
// val (compiler, ctx) = prepareCompiler(classesDir, analysisCallback, classesDir.toString)
138+
lazy val commonCompilerInstanceAndCtx = prepareCompiler(classesDir, analysisCallback, classesDir.toString)
122139

123140
val files = for ((compilationUnit, unitId) <- groupedSrcs.zipWithIndex) yield {
124-
val (compiler, ctx) = prepareCompiler(classesDir, analysisCallback, classesDir.toString)
141+
// use a separate instance of the compiler for each group of sources to
142+
// have an ability to test for bugs in instability between source and pickled
143+
// representation of types
144+
val (compiler, ctx) = if (reuseCompilerInstance) commonCompilerInstanceAndCtx else
145+
prepareCompiler(classesDir, analysisCallback, classesDir.toString)
125146
val run = compiler.newRun(ctx)
126147
val srcFiles = compilationUnit.toSeq.zipWithIndex map {
127148
case (src, i) =>
@@ -132,15 +153,15 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) {
132153

133154
run.compile(srcFilePaths)
134155

135-
srcFilePaths.foreach(f => new File(f).delete)
156+
// srcFilePaths.foreach(f => new File(f).delete)
136157
srcFiles
137158
}
138159
(files.flatten.toSeq, analysisCallback)
139160
}
140161
}
141162

142163
private def compileSrcs(srcs: String*): (Seq[File], TestCallback) = {
143-
compileSrcs(List(srcs.toList))
164+
compileSrcs(List(srcs.toList), reuseCompilerInstance = true)
144165
}
145166

146167
private def prepareSrcFile(baseDir: File, fileName: String, src: String): File = {
@@ -151,10 +172,6 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) {
151172

152173
private def prepareCompiler(outputDir: File, analysisCallback: AnalysisCallback, classpath: String = ".") = {
153174
val args = Array.empty[String]
154-
object output extends SingleOutput {
155-
def outputDirectory: File = outputDir
156-
override def toString = s"SingleOutput($outputDirectory)"
157-
}
158175

159176
import dotty.tools.dotc._
160177
import dotty.tools.dotc.core.Contexts._
@@ -171,7 +188,7 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) {
171188
}
172189
}
173190
val ctx = (new ContextBase).initialCtx.fresh.setSbtCallback(analysisCallback)
174-
driver.getCompiler(Array("-classpath", classpath, "-usejavacp"), ctx)
191+
driver.getCompiler(Array("-classpath", classpath, "-usejavacp", "-d", outputDir.getAbsolutePath), ctx)
175192
}
176193

177194
private object ConsoleReporter extends Reporter {

sbt-bridge/test/xsbti/TestCallback.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import scala.collection.mutable.ArrayBuffer
66
import xsbti.api.SourceAPI
77
import xsbti.DependencyContext._
88

9-
class TestCallback(override val nameHashing: Boolean = false) extends AnalysisCallback
9+
class TestCallback(override val nameHashing: Boolean, override val includeSynthToNameHashing: Boolean) extends AnalysisCallback
1010
{
1111
val sourceDependencies = new ArrayBuffer[(File, File, DependencyContext)]
1212
val binaryDependencies = new ArrayBuffer[(File, String, File, DependencyContext)]

0 commit comments

Comments
 (0)