=str #16935 revive dsl factories consistency spec

I fought the dragons, and I won...
This commit is contained in:
Martynas Mickevičius 2015-06-26 09:51:08 +03:00
parent 911943fc92
commit 27201206f6
2 changed files with 63 additions and 37 deletions

View file

@ -3,8 +3,6 @@
*/
package akka.stream
import java.lang.reflect.Method
import org.scalatest.Matchers
import org.scalatest.WordSpec
@ -13,7 +11,7 @@ class DslFactoriesConsistencySpec extends WordSpec with Matchers {
// configuration //
val scalaIgnore =
Set("equals", "hashCode", "notify", "notifyAll", "wait", "toString", "getClass")
Set("equals", "hashCode", "notify", "notifyAll", "wait", "toString", "getClass", "shape")
val javaIgnore =
Set("adapt") // the scaladsl -> javadsl bridge
@ -23,6 +21,7 @@ class DslFactoriesConsistencySpec extends WordSpec with Matchers {
("apply" "of") ::
("apply" "from") ::
("apply" -> "fromGraph") ::
("apply" -> "fromIterator") ::
Nil
// format: OFF
@ -31,15 +30,14 @@ class DslFactoriesConsistencySpec extends WordSpec with Matchers {
(classOf[scala.collection.Iterator[_]], classOf[java.util.Iterator[_]]) ::
(classOf[scala.Function0[_]], classOf[akka.japi.function.Creator[_]]) ::
(classOf[scala.Function0[_]], classOf[java.util.concurrent.Callable[_]]) ::
(classOf[scala.Function0[_]], classOf[akka.japi.function.Creator[_]]) ::
(classOf[scala.Function1[_, Unit]], classOf[akka.japi.function.Procedure[_]]) ::
(classOf[scala.Function1[_, _]], classOf[akka.japi.function.Function[_, _]]) ::
(classOf[scala.Function1[_, _]], classOf[akka.japi.function.Creator[_]]) ::
(classOf[scala.Function2[_, _, _]], classOf[akka.japi.function.Function2[_, _, _]]) ::
(classOf[akka.stream.scaladsl.Source[_, _]], classOf[akka.stream.javadsl.Source[_, _]]) ::
(classOf[akka.stream.scaladsl.Sink[_, _]], classOf[akka.stream.javadsl.Sink[_, _]]) ::
(classOf[akka.stream.scaladsl.Flow[_, _, _]], classOf[akka.stream.javadsl.Flow[_, _, _]]) ::
(classOf[akka.stream.scaladsl.RunnableGraph[_]], classOf[akka.stream.javadsl.RunnableGraph[_]]) ::
Nil
((2 to 22) map { i => (Class.forName(s"scala.Function$i"), Class.forName(s"akka.japi.function.Function$i")) }).toList
// format: ON
val sSource = classOf[scaladsl.Source[_, _]]
@ -54,52 +52,69 @@ class DslFactoriesConsistencySpec extends WordSpec with Matchers {
"Java DSL" must provide {
"Source" which {
"allows creating the same Sources as Scala DSL" in {
pending
val sClass = akka.stream.scaladsl.Source.getClass
val jClass = akka.stream.javadsl.Source.getClass
val jFactory = classOf[akka.stream.javadsl.SourceCreate]
runSpec(sClass, jClass)
runSpec(getSMethods(sClass), getJMethods(jClass) ++ getJMethods(jFactory).map(adaptCreate))
}
}
"Flow" which {
"allows creating the same Sources as Scala DSL" in {
pending
val sClass = akka.stream.scaladsl.Flow.getClass
val jClass = akka.stream.javadsl.Flow.getClass
val jFactory = classOf[akka.stream.javadsl.FlowCreate]
runSpec(sClass, jClass)
runSpec(getSMethods(sClass), getJMethods(jClass) ++ getJMethods(jFactory).map(adaptCreate))
}
}
"Sink" which {
"allows creating the same Sources as Scala DSL" in {
pending
val sClass = akka.stream.scaladsl.Sink.getClass
val jClass = akka.stream.javadsl.Sink.getClass
val jFactory = classOf[akka.stream.javadsl.SinkCreate]
runSpec(sClass, jClass)
runSpec(getSMethods(sClass), getJMethods(jClass) ++ getJMethods(jFactory).map(adaptCreate))
}
}
}
// here be dragons...
private def getJMethods(jClass: Class[_]): Array[Method] = jClass.getDeclaredMethods.filterNot(javaIgnore contains _.getName).filter(include)
private def getSMethods(sClass: Class[_]): Array[Method] = sClass.getMethods.filterNot(scalaIgnore contains _.getName).filter(include)
private def getJMethods(jClass: Class[_]): Array[Method] = jClass.getDeclaredMethods.filterNot(javaIgnore contains _.getName).map(toMethod).filterNot(ignore)
private def getSMethods(sClass: Class[_]): Array[Method] = sClass.getMethods.filterNot(scalaIgnore contains _.getName).map(toMethod).filterNot(ignore)
private def include(m: Method): Boolean = {
if (m.getDeclaringClass == akka.stream.scaladsl.Source.getClass
&& m.getName == "apply"
&& m.getParameterTypes.length == 1
&& m.getParameterTypes()(0) == classOf[scala.Function1[_, _]])
false // conflict between two Source.apply(Function1)
else
true
private def toMethod(m: java.lang.reflect.Method): Method =
Method(m.getName, List(m.getParameterTypes: _*), m.getReturnType, m.getDeclaringClass)
private case class Ignore(cls: Class[_] Boolean, name: String Boolean, parameters: Int Boolean, paramTypes: List[Class[_]] Boolean)
private def ignore(m: Method): Boolean = {
val ignores = Seq(
// private scaladsl method
Ignore(_ == akka.stream.scaladsl.Source.getClass, _ == "apply", _ == 1, _ == List(classOf[akka.stream.impl.SourceModule[_, _]])),
// corresponding matches on java side would need to have Function23
Ignore(_ == akka.stream.scaladsl.Source.getClass, _ == "apply", _ == 24, _ true),
Ignore(_ == akka.stream.scaladsl.Flow.getClass, _ == "apply", _ == 24, _ true),
Ignore(_ == akka.stream.scaladsl.Sink.getClass, _ == "apply", _ == 24, _ true),
// all generated methods like scaladsl.Sink$.akka$stream$scaladsl$Sink$$newOnCompleteStage$1
Ignore(_ true, _.contains("$"), _ true, _ true))
ignores.foldLeft(false) {
case (acc, i)
acc || (i.cls(m.declaringClass) && i.name(m.name) && i.parameters(m.parameterTypes.length) && i.paramTypes(m.parameterTypes))
}
}
def runSpec(sClass: Class[_], jClass: Class[_]) {
val jMethods = getJMethods(jClass)
val sMethods = getSMethods(sClass)
private val adaptCreate: PartialFunction[Method, Method] = {
case m if m.parameterTypes.size > 1
// rename createN => create
// and adapt java side non curried functions to scala side like
m.copy(name = "create", parameterTypes = m.parameterTypes.dropRight(1) :+ classOf[akka.japi.function.Function[_, _]])
case m m
}
def runSpec(sMethods: Array[Method], jMethods: Array[Method]) {
var warnings = 0
val results = for {
@ -117,15 +132,15 @@ class DslFactoriesConsistencySpec extends WordSpec with Matchers {
if (matches.length == 0) {
warnings += 1
alert("No match for " + row._1)
row._2 foreach { m alert(" > " + m.toString) }
row._2 foreach { m alert(s" > ${m.j.toString}: ${m.reason}") }
} else if (matches.length == 1) {
info("Matched: Scala:" + row._1.getName + "(" + row._1.getParameterTypes.map(_.getName).mkString(",") + "): " + returnTypeString(row._1) +
info("Matched: Scala:" + row._1.name + "(" + row._1.parameterTypes.map(_.getName).mkString(",") + "): " + returnTypeString(row._1) +
" == " +
"Java:" + matches.head.j.getName + "(" + matches.head.j.getParameterTypes.map(_.getName).mkString(",") + "): " + returnTypeString(matches.head.j))
"Java:" + matches.head.j.name + "(" + matches.head.j.parameterTypes.map(_.getName).mkString(",") + "): " + returnTypeString(matches.head.j))
} else {
warnings += 1
alert("Multiple matches for " + row._1 + "!")
matches foreach { m alert(m.toString) }
matches foreach { m alert(s" > ${m.j.toString}") }
}
}
@ -137,24 +152,27 @@ class DslFactoriesConsistencySpec extends WordSpec with Matchers {
}
def returnTypeString(m: Method): String =
m.getReturnType.getName.drop("akka.stream.".length)
m.returnType.getName.drop("akka.stream.".length)
case class Method(name: String, parameterTypes: List[Class[_]], returnType: Class[_], declaringClass: Class[_])
sealed trait MatchResult {
def j: Method
def s: Method
def reason: String
def matches: Boolean
}
case class MatchFailure(s: Method, j: Method, reason: String = "") extends MatchResult { val matches = false }
case class Match(s: Method, j: Method, reason: String = "") extends MatchResult { val matches = true }
def delegationCheck(s: Method, j: Method): MatchResult = {
if (nameMatch(s.getName, j.getName)) {
if (s.getParameterTypes.length == j.getParameterTypes.length)
if (typeMatch(s.getParameterTypes, j.getParameterTypes))
if (returnTypeMatch(s.getReturnType, j.getReturnType))
if (nameMatch(s.name, j.name)) {
if (s.parameterTypes.length == j.parameterTypes.length)
if (typeMatch(s.parameterTypes, j.parameterTypes))
if (returnTypeMatch(s.returnType, j.returnType))
Match(s, j)
else
MatchFailure(s, j, "Return types don't match! " + s.getReturnType + ", " + j.getReturnType)
MatchFailure(s, j, "Return types don't match! " + s.returnType + ", " + j.returnType)
else
MatchFailure(s, j, "Types of parameters don't match!")
else
@ -180,7 +198,7 @@ class DslFactoriesConsistencySpec extends WordSpec with Matchers {
(sSink.isAssignableFrom(s) && jSink.isAssignableFrom(j)) ||
(sFlow.isAssignableFrom(s) && jFlow.isAssignableFrom(j))
def typeMatch(scalaParams: Array[Class[_]], javaParams: Array[Class[_]]): Boolean =
def typeMatch(scalaParams: List[Class[_]], javaParams: List[Class[_]]): Boolean =
(scalaParams.toList, javaParams.toList) match {
case (s, j) if s == j true
case (s, j) if s.zip(j).forall(typeMatch) true

View file

@ -213,6 +213,14 @@ object Source {
def concat[T, M1, M2](first: Graph[SourceShape[T], M1], second: Graph[SourceShape[T], M2]): Source[T, (M1, M2)] =
new Source(scaladsl.Source.concat(first, second))
/**
* Concatenates two sources so that the first element
* emitted by the second source is emitted after the last element of the first
* source.
*/
def concatMat[T, M1, M2, M3](first: Graph[SourceShape[T], M1], second: Graph[SourceShape[T], M2], combine: function.Function2[M1, M2, M3]): Source[T, M3] =
new Source(scaladsl.Source.concatMat(first, second)(combinerToScala(combine)))
/**
* A graph with the shape of a source logically is a source, this method makes
* it so also in type.