Skip to content

Commit 61ff944

Browse files
completeness test for instance methods in Scala adaptor
1 parent a9178d8 commit 61ff944

File tree

1 file changed

+144
-42
lines changed

1 file changed

+144
-42
lines changed

language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/CompletenessTest.scala

Lines changed: 144 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,65 +4,167 @@ import scala.reflect.runtime.universe._
44
import org.scalatest.junit.JUnitSuite
55
import org.junit.Test
66
import rx.util.functions._
7+
import scala.collection.SortedSet
8+
import scala.collection.SortedMap
9+
import org.junit.Ignore
710

811
class CompletenessTest extends JUnitSuite {
912

10-
case class Op(name: String, argTypes: Type*) {
11-
override def toString = name + argTypes.mkString("(", ", ", ")")
13+
val unnecessary = "[considered unnecessary in Scala land]"
14+
15+
val correspondence = defaultInstanceMethodCorrespondence ++ Map(
16+
// manually added entries
17+
"aggregate(Func2[T, T, T])" -> "reduce((U, U) => U)",
18+
"aggregate(R, Func2[R, _ >: T, R])" -> "fold(R)((R, T) => R)",
19+
"all(Func1[_ >: T, Boolean])" -> "forall(T => Boolean)",
20+
"buffer(Long, Long, TimeUnit)" -> "buffer(Duration, Duration)",
21+
"buffer(Long, Long, TimeUnit, Scheduler)" -> "buffer(Duration, Duration, Scheduler)",
22+
"dematerialize()" -> "dematerialize(<:<[T, Notification[U]])",
23+
"groupBy(Func1[_ >: T, _ <: K], Func1[_ >: T, _ <: R])" -> "groupBy(T => K)",
24+
"mapMany(Func1[_ >: T, _ <: Observable[_ <: R]])" -> "flatMap(T => Observable[R])",
25+
"onErrorResumeNext(Func1[Throwable, _ <: Observable[_ <: T]])" -> "onErrorResumeNext(Throwable => Observable[U])",
26+
"onErrorResumeNext(Observable[_ <: T])" -> "onErrorResumeNext(Observable[U])",
27+
"onErrorReturn(Func1[Throwable, _ <: T])" -> "onErrorReturn(Throwable => U)",
28+
"onExceptionResumeNext(Observable[_ <: T])" -> "onExceptionResumeNext(Observable[U])",
29+
"reduce(Func2[T, T, T])" -> "reduce((U, U) => U)",
30+
"reduce(R, Func2[R, _ >: T, R])" -> "fold(R)((R, T) => R)",
31+
"scan(Func2[T, T, T])" -> unnecessary,
32+
"scan(R, Func2[R, _ >: T, R])" -> "scan(R)((R, T) => R)",
33+
"skip(Int)" -> "drop(Int)",
34+
"skipWhile(Func1[_ >: T, Boolean])" -> "dropWhile(T => Boolean)",
35+
"skipWhileWithIndex(Func2[_ >: T, Integer, Boolean])" -> unnecessary,
36+
"startWith(Iterable[T])" -> "[unnecessary because we can just use ++ instead]",
37+
"toList()" -> "toSeq",
38+
"toSortedList()" -> unnecessary,
39+
"toSortedList(Func2[_ >: T, _ >: T, Integer])" -> unnecessary,
40+
"where(Func1[_ >: T, Boolean])" -> "filter(T => Boolean)",
41+
"window(Long, Long, TimeUnit)" -> "window(Duration, Duration)",
42+
"window(Long, Long, TimeUnit, Scheduler)" -> "window(Duration, Duration, Scheduler)"
43+
) ++ List.iterate("T", 9)(s => s + ", T").map(
44+
// all 9 overloads of startWith:
45+
"startWith(" + _ + ")" -> "[unnecessary because we can just use ++ instead]"
46+
).toMap
47+
48+
def removePackage(s: String) = s.replaceAll("(\\w+\\.)+(\\w+)", "$2")
49+
50+
def methodMembersToMethodStrings(members: Iterable[Symbol]): Iterable[String] = {
51+
for (member <- members; alt <- member.asTerm.alternatives) yield {
52+
val m = alt.asMethod
53+
// multiple parameter lists in case of curried functions
54+
val paramListStrs = for (paramList <- m.paramss) yield {
55+
paramList.map(
56+
symb => removePackage(symb.typeSignature.toString.replaceAll(",(\\S)", ", $1"))
57+
).mkString("(", ", ", ")")
58+
}
59+
val name = alt.asMethod.name.decoded
60+
name + paramListStrs.mkString("")
61+
}
62+
}
63+
64+
def getPublicInstanceMethods(tp: Type): Iterable[String] = {
65+
// declarations: => only those declared in Observable
66+
// members => also those of superclasses
67+
methodMembersToMethodStrings(tp.declarations.filter(m => m.isMethod && m.isPublic))
1268
}
1369

14-
val correspondence = Map(
15-
Op("toList") -> Op("toSeq"),
16-
Op("window", typeOf[Int]) -> Op("window", typeOf[Int])
17-
)
70+
def getStaticMethods(tp: Type): Iterable[String] = {
71+
???
72+
}
1873

19-
def check(cond: Boolean, ifGood: String, ifBad: String): Unit = {
20-
if (cond) println(ifGood) else println(ifBad)
74+
@Test def printJavaInstanceMethods: Unit = {
75+
println("\nInstance methods of rx.Observable")
76+
println( "---------------------------------\n")
77+
getPublicInstanceMethods(typeOf[rx.Observable[_]]).toList.sorted.foreach(println(_))
2178
}
2279

23-
def checkOperatorPresence(op: Op, tp: Type): Unit = {
24-
val paramTypeLists = for (alt <- tp.member(newTermName(op.name)).asTerm.alternatives)
25-
yield alt.asMethod.paramss.headOption match {
26-
case Some(paramList) => paramList.map(symb => symb.typeSignature)
27-
case None => List()
28-
}
29-
30-
println(paramTypeLists)
31-
32-
check(paramTypeLists.contains(op.argTypes.toList),
33-
s"$op is present in $tp", s"$op is NOT present in $tp")
80+
@Test def printScalaInstanceMethods: Unit = {
81+
println("\nInstance methods of rx.lang.scala.Observable")
82+
println( "--------------------------------------------\n")
83+
getPublicInstanceMethods(typeOf[rx.lang.scala.Observable[_]]).toList.sorted.foreach(s => println(s))
3484
}
3585

36-
@Test def test3() {
37-
val javaObs = typeOf[rx.Observable[_]]
38-
val scalaObs = typeOf[rx.lang.scala.Observable[_]]
39-
40-
for ((javaOp, scalaOp) <- correspondence) {
41-
checkOperatorPresence(javaOp, javaObs)
42-
checkOperatorPresence(scalaOp, scalaObs)
86+
def javaMethodSignatureToScala(s: String): String = {
87+
s.replaceAllLiterally("Long, TimeUnit", "Duration")
88+
.replaceAll("Action0", "() => Unit")
89+
// nested [] can't be parsed with regex, but we still do it, please forgive us ;-)
90+
.replaceAll("Action1\\[([^]]*)\\]", "$1 => Unit")
91+
.replaceAll("Action2\\[([^]]*), ([^]]*)\\]", "($1, $2) => Unit")
92+
.replaceAll("Func0\\[([^]]*)\\]", "() => $1")
93+
.replaceAll("Func1\\[([^]]*), ([^]]*)\\]", "$1 => $2")
94+
.replaceAll("Func2\\[([^]]*), ([^]]*), ([^]]*)\\]", "($1, $2) => $3")
95+
.replaceAllLiterally("_ <: ", "")
96+
.replaceAllLiterally("_ >: ", "")
97+
.replaceAll("(\\w+)\\(\\)", "$1")
98+
}
99+
100+
def defaultInstanceMethodCorrespondence: Map[String, String] = {
101+
val instanceMethods = getPublicInstanceMethods(typeOf[rx.Observable[_]]).toList.sorted
102+
val tuples = for (javaM <- instanceMethods) yield (javaM, javaMethodSignatureToScala(javaM))
103+
tuples.toMap
104+
}
105+
106+
@Test def printDefaultInstanceMethodCorrespondence: Unit = {
107+
println("\nDefault Instance Method Correspondence")
108+
println( "--------------------------------------\n")
109+
val c = SortedMap(defaultInstanceMethodCorrespondence.toSeq : _*)
110+
val len = c.keys.map(_.length).max + 2
111+
for ((javaM, scalaM) <- c) {
112+
println(s""" %-${len}s -> %s,""".format("\"" + javaM + "\"", "\"" + scalaM + "\""))
43113
}
44114
}
45115

46-
47-
@Test def test1() {
48-
val c = Class.forName("rx.Observable")
49-
for (method <- c.getMethods()) {
50-
println(method.getName())
116+
@Ignore // Does not yet work
117+
@Test def printJavaStaticMethods: Unit = {
118+
println("\nStatic methods of rx.Observable")
119+
println( "-------------------------------\n")
120+
getStaticMethods(typeOf[rx.Observable[_]]).toList.sorted.foreach(println(_))
121+
}
122+
123+
def checkMethodPresence(expectedMethods: Iterable[String], tp: Type): Unit = {
124+
val actualMethods = getPublicInstanceMethods(tp).toSet
125+
val expMethodsSorted = expectedMethods.toList.sorted
126+
var good = 0
127+
var bad = 0
128+
for (m <- expMethodsSorted) if (actualMethods.contains(m) || m.charAt(0) == '[') {
129+
good += 1
130+
} else {
131+
bad += 1
132+
println(s"Warning: $m is NOT present in $tp")
51133
}
52-
134+
val status = if (bad == 0) "SUCCESS" else "BAD"
135+
println(s"$status: $bad out of ${bad+good} methods were not found in $tp")
53136
}
54137

55-
@Test def test2() {
56-
val tp = typeOf[rx.Observable[_]]
57-
for (member <- tp.members) println(member)
58-
println("00000")
59-
//println(tp.member(stringToTermName("all")))
60-
//println(tp.member("all"))
61-
62-
val methodAll = tp.member(newTermName("all")).asMethod
63-
println(methodAll.fullName)
138+
@Test def checkScalaMethodPresenceVerbose: Unit = {
139+
println("\nTesting that all mentioned Scala methods exist")
140+
println( "----------------------------------------------\n")
64141

65-
val methodBufferAlts = tp.member(newTermName("buffer")).asTerm.alternatives
142+
val actualMethods = getPublicInstanceMethods(typeOf[rx.lang.scala.Observable[_]]).toSet
143+
var good = 0
144+
var bad = 0
145+
for ((javaM, scalaM) <- SortedMap(correspondence.toSeq :_*)) {
146+
if (actualMethods.contains(scalaM) || scalaM.charAt(0) == '[') {
147+
good += 1
148+
} else {
149+
bad += 1
150+
println(s"Warning:")
151+
println(s"$scalaM is NOT present in Scala Observable")
152+
println(s"$javaM is the method in Java Observable generating this warning")
153+
}
154+
}
155+
val status = if (bad == 0) "SUCCESS" else "BAD"
156+
println(s"$status: $bad out of ${bad+good} methods were not found in Scala Observable")
157+
}
158+
159+
@Ignore // because we prefer the verbose version
160+
@Test def checkScalaMethodPresence: Unit = {
161+
checkMethodPresence(correspondence.values, typeOf[rx.lang.scala.Observable[_]])
162+
}
163+
164+
@Test def checkJavaMethodPresence: Unit = {
165+
println("\nTesting that all mentioned Java methods exist")
166+
println( "---------------------------------------------\n")
167+
checkMethodPresence(correspondence.keys, typeOf[rx.Observable[_]])
66168
}
67169

68170
}

0 commit comments

Comments
 (0)