Skip to content

Commit 039f658

Browse files
committed
add remaining tests
1 parent 9e58f22 commit 039f658

File tree

7 files changed

+1014
-7
lines changed

7 files changed

+1014
-7
lines changed

test/junit/scala/tools/xsbt/BridgeTesting.scala

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package scala.tools.xsbt
22

3+
import xsbti.api.ClassLike
34
import xsbti.api.DependencyContext._
45
import xsbti.compile.{CompileProgress, DependencyChanges}
5-
import xsbti.{BasicVirtualFileRef, Logger, Position, Problem, Severity, VirtualFile, VirtualFileRef, Reporter => XReporter}
6+
import xsbti.{BasicVirtualFileRef, InteractiveConsoleInterface, Logger, Position, Problem, Severity, VirtualFile, VirtualFileRef, Reporter => XReporter}
67

78
import java.io.{ByteArrayInputStream, File, InputStream}
89
import java.nio.file.{Files, Path}
10+
import java.util.Optional
911
import java.util.function.Supplier
1012
import scala.collection.mutable.ListBuffer
1113
import scala.tools.nsc.io.AbstractFile
@@ -24,6 +26,8 @@ class BridgeTesting {
2426

2527
def mkCompiler: CompilerBridge = new CompilerBridge()
2628

29+
def mkConsole: InteractiveConsoleBridgeFactory = new InteractiveConsoleBridgeFactory()
30+
2731
def mkScaladoc: ScaladocBridge = new ScaladocBridge()
2832

2933
private val emptyChanges: DependencyChanges = new DependencyChanges {
@@ -104,6 +108,67 @@ class BridgeTesting {
104108
},
105109
)
106110
}
111+
112+
/**
113+
* Compiles given source code using Scala compiler and returns API representation
114+
* extracted by ExtractAPI class.
115+
*/
116+
def extractApisFromSrc(src: String): Set[ClassLike] = withTemporaryDirectory { tmpDir =>
117+
val (Seq(tempSrcFile), analysisCallback) = compileSrcs(tmpDir, src)
118+
analysisCallback.apis(tempSrcFile)
119+
}
120+
121+
/**
122+
* Compiles given source code using Scala compiler and returns API representation
123+
* extracted by ExtractAPI class.
124+
*/
125+
def extractApisFromSrcs(srcs: List[String]*): Seq[Set[ClassLike]] = withTemporaryDirectory { tmpDir =>
126+
val (tempSrcFiles, analysisCallback) = compileSrcss(tmpDir, mkReporter, srcs.toList)
127+
tempSrcFiles.map(analysisCallback.apis)
128+
}
129+
130+
def extractUsedNamesFromSrc(src: String): Map[String, Set[String]] = withTemporaryDirectory { tmpDir =>
131+
val (_, analysisCallback) = compileSrcs(tmpDir, src)
132+
analysisCallback.usedNames.toMap
133+
}
134+
135+
/**
136+
* Extract used names from the last source file in `sources`.
137+
*
138+
* The previous source files are provided to successfully compile examples.
139+
* Only the names used in the last src file are returned.
140+
*/
141+
def extractUsedNamesFromSrc(sources: String*): Map[String, Set[String]] = withTemporaryDirectory { tmpDir =>
142+
val (srcFiles, analysisCallback) = compileSrcs(tmpDir, sources: _*)
143+
srcFiles
144+
.map { srcFile =>
145+
val classesInSrc = analysisCallback.classNames(srcFile).map(_._1)
146+
classesInSrc.map(className => className -> analysisCallback.usedNames(className)).toMap
147+
}
148+
.reduce(_ ++ _)
149+
}
150+
151+
def interactiveConsole(baseDir: Path)(args: String*): InteractiveConsoleInterface = {
152+
val targetDir = baseDir / "target"
153+
Files.createDirectory(targetDir)
154+
val sc = mkConsole
155+
sc.createConsole(
156+
args = Array("-usejavacp") ++ args,
157+
bootClasspathString = "",
158+
classpathString = "",
159+
initialCommands = "",
160+
cleanupCommands = "",
161+
loader = Optional.empty,
162+
bindNames = Array(),
163+
bindValues = Array(),
164+
log = TestLogger)
165+
}
166+
167+
def withInteractiveConsole[A](f: InteractiveConsoleInterface => A): A = withTemporaryDirectory { tmpDir =>
168+
val repl = interactiveConsole(tmpDir)()
169+
try f(repl)
170+
finally repl.close()
171+
}
107172
}
108173

109174
class TestingReporter extends XReporter {
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package scala.tools.xsbt
2+
3+
import org.junit.Test
4+
import org.junit.Assert._
5+
import xsbti.api._
6+
7+
class ExtractAPITest extends BridgeTesting {
8+
9+
@Test
10+
def `ExtractAPI should extract children of a sealed class`(): Unit = {
11+
def compileAndGetFooClassApi(src: String): ClassLike = {
12+
val apis = extractApisFromSrc(src)
13+
val FooApi = apis.find(_.name() == "Foo").get
14+
FooApi
15+
}
16+
17+
val src1 =
18+
"""|sealed abstract class Foo
19+
|case class C1(x: Int) extends Foo
20+
|""".stripMargin
21+
val fooClassApi1 = compileAndGetFooClassApi(src1)
22+
val src2 =
23+
"""|sealed abstract class Foo
24+
|case class C1(x: Int) extends Foo
25+
|case class C2(x: Int) extends Foo
26+
|""".stripMargin
27+
val fooClassApi2 = compileAndGetFooClassApi(src2)
28+
assertFalse(SameAPI(fooClassApi1, fooClassApi2))
29+
}
30+
31+
@Test
32+
def `ExtractAPI should extract correctly the definition type of a package object`(): Unit = {
33+
val src = "package object foo".stripMargin
34+
val apis = extractApisFromSrc(src)
35+
val Seq(fooClassApi) = apis.toSeq
36+
assertEquals(fooClassApi.definitionType, DefinitionType.PackageModule)
37+
}
38+
39+
@Test
40+
def `ExtractAPI should extract nested classes`(): Unit = {
41+
val src =
42+
"""class A {
43+
| class B
44+
|}""".stripMargin
45+
val apis = extractApisFromSrc(src).map(c => c.name -> c).toMap
46+
assertEquals(apis.keys, Set("A", "A.B"))
47+
}
48+
49+
@Test
50+
def `ExtractAPI should not extract local classes`(): Unit = {
51+
val src =
52+
"""class A
53+
|class B
54+
|class C { def foo: Unit = { class Inner2 extends B } }
55+
|class D { def foo: Unit = { new B {} } }""".stripMargin
56+
val apis = extractApisFromSrc(src).map(c => c.name -> c).toMap
57+
assertEquals(apis.keys, Set("A", "B", "C", "D"))
58+
}
59+
60+
@Test
61+
def `ExtractAPI should extract flat (without members) api for a nested class`(): Unit = {
62+
def compileAndGetFooClassApi(src: String): ClassLike = {
63+
val apis = extractApisFromSrc(src)
64+
val FooApi = apis.find(_.name() == "Foo").get
65+
FooApi
66+
}
67+
68+
val src1 =
69+
"""class Foo {
70+
| class A
71+
|}""".stripMargin
72+
val fooClassApi1 = compileAndGetFooClassApi(src1)
73+
val src2 =
74+
"""class Foo {
75+
| class A {
76+
| def foo: Int = 123
77+
| }
78+
|}""".stripMargin
79+
val fooClassApi2 = compileAndGetFooClassApi(src2)
80+
assertTrue(SameAPI(fooClassApi1, fooClassApi2))
81+
}
82+
83+
@Test
84+
def `ExtractAPI should extract private classes`(): Unit = {
85+
val src =
86+
"""private class A
87+
|class B { private class Inner1 extends A }
88+
|""".stripMargin
89+
val apis = extractApisFromSrc(src).map(c => c.name -> c).toMap
90+
assertEquals(apis.keys, Set("A", "B", "B.Inner1"))
91+
}
92+
93+
@Test
94+
def `ExtractAPI should give stable names to members of existential types in method signatures`(): Unit = {
95+
def compileAndGetFooMethodApi(src: String): Def = {
96+
val sourceApi = extractApisFromSrc(src)
97+
val FooApi = sourceApi.find(_.name() == "Foo").get
98+
val fooMethodApi = FooApi.structure().declared().find(_.name == "foo").get
99+
fooMethodApi.asInstanceOf[Def]
100+
}
101+
102+
val src1 =
103+
"""class Box[T]
104+
|class Foo {
105+
| def foo: Box[_] = null
106+
|}""".stripMargin
107+
val fooMethodApi1 = compileAndGetFooMethodApi(src1)
108+
val src2 =
109+
"""class Box[T]
110+
|class Foo {
111+
| def bar: Box[_] = null
112+
| def foo: Box[_] = null
113+
|}""".stripMargin
114+
val fooMethodApi2 = compileAndGetFooMethodApi(src2)
115+
assertTrue("APIs are not the same.", SameAPI.apply(fooMethodApi1, fooMethodApi2))
116+
}
117+
118+
/**
119+
* Checks if representation of the inherited Namer class (with a declared self variable) in Global.Foo
120+
* is stable between compiling from source and unpickling. We compare extracted APIs of Global when Global
121+
* is compiled together with Namers or Namers is compiled first and then Global refers
122+
* to Namers by unpickling types from class files.
123+
*/
124+
@Test
125+
def `ExtractAPI should make a stable representation of a self variable that has no self type`(): Unit = {
126+
def selectNamer(apis: Set[ClassLike]): ClassLike = {
127+
// TODO: this doesn't work yet because inherited classes are not extracted
128+
apis.find(_.name == "Global.Foo.Namer").get
129+
}
130+
131+
val src1 =
132+
"""|class Namers {
133+
| class Namer { thisNamer => }
134+
|}
135+
|""".stripMargin
136+
val src2 =
137+
"""|class Global {
138+
| class Foo extends Namers
139+
|}
140+
|""".stripMargin
141+
val apis =
142+
extractApisFromSrcs(List(src1, src2), List(src2))
143+
val _ :: src2Api1 :: src2Api2 :: Nil = apis.toList
144+
val namerApi1 = selectNamer(src2Api1)
145+
val namerApi2 = selectNamer(src2Api2)
146+
assertTrue(SameAPI(namerApi1, namerApi2))
147+
}
148+
149+
@Test
150+
def `ExtractAPI should make a different representation for an inherited class`(): Unit = {
151+
val src =
152+
"""|class A[T] {
153+
| abstract class AA { def t: T }
154+
|}
155+
|class B extends A[Int]
156+
""".stripMargin
157+
val apis = extractApisFromSrc(src).map(a => a.name -> a).toMap
158+
assertEquals(apis.keySet, Set("A", "A.AA", "B", "B.AA"))
159+
assertNotEquals(apis("A.AA"), apis("B.AA"))
160+
}
161+
162+
@Test
163+
def `ExtractAPI should handle package objects and type companions`(): Unit = {
164+
val src =
165+
"""|package object abc {
166+
| type BuildInfoKey = BuildInfoKey.Entry[_]
167+
| object BuildInfoKey {
168+
| sealed trait Entry[A]
169+
| }
170+
|}
171+
""".stripMargin
172+
val apis = extractApisFromSrc(src).map(a => a.name -> a).toMap
173+
assertEquals(apis.keySet, Set("abc.package", "abc.BuildInfoKey", "abc.BuildInfoKey.Entry"))
174+
}
175+
176+
/**
177+
* Checks if self type is properly extracted in various cases of declaring a self type
178+
* with our without a self variable.
179+
*/
180+
@Test
181+
def `ExtractAPI should represent a self type correctly`(): Unit = {
182+
val srcX = "trait X"
183+
val srcY = "trait Y"
184+
val srcC1 = "class C1 { this: C1 => }"
185+
val srcC2 = "class C2 { thisC: C2 => }"
186+
val srcC3 = "class C3 { this: X => }"
187+
val srcC4 = "class C4 { thisC: X => }"
188+
val srcC5 = "class C5 extends AnyRef with X with Y { self: X with Y => }"
189+
val srcC6 = "class C6 extends AnyRef with X { self: X with Y => }"
190+
val srcC7 = "class C7 { _ => }"
191+
val srcC8 = "class C8 { self => }"
192+
val apis = extractApisFromSrcs(
193+
List(srcX, srcY, srcC1, srcC2, srcC3, srcC4, srcC5, srcC6, srcC7, srcC8)
194+
).map(_.head)
195+
val emptyType = EmptyType.of()
196+
197+
def hasSelfType(c: ClassLike): Boolean =
198+
c.selfType != emptyType
199+
200+
val (withSelfType, withoutSelfType) = apis.partition(hasSelfType)
201+
assertEquals(withSelfType.map(_.name).toSet, Set("C3", "C4", "C5", "C6"))
202+
assertEquals(withoutSelfType.map(_.name).toSet, Set("X", "Y", "C1", "C2", "C7", "C8"))
203+
}
204+
}

0 commit comments

Comments
 (0)