-
Notifications
You must be signed in to change notification settings - Fork 265
Add a DagOptimizer test #745
base: develop
Are you sure you want to change the base?
Changes from all commits
8894914
3f21029
39fa04c
7be4298
3e7b47b
996a15e
05c39c6
6048196
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package com.twitter.summingbird.planner | ||
|
||
import com.twitter.algebird.Semigroup | ||
import com.twitter.summingbird._ | ||
import com.twitter.summingbird.graph.{DependantGraph, Rule} | ||
import com.twitter.summingbird.memory._ | ||
|
||
import org.scalatest.FunSuite | ||
import org.scalacheck.{Arbitrary, Gen} | ||
import Gen.oneOf | ||
|
||
import scala.collection.mutable | ||
import org.scalatest.prop.GeneratorDrivenPropertyChecks._ | ||
|
||
class DagOptimizerTest extends FunSuite { | ||
|
||
implicit val generatorDrivenConfig = | ||
PropertyCheckConfig(minSuccessful = 1000, maxDiscarded = 1000) // the producer generator uses filter, I think | ||
//PropertyCheckConfig(minSuccessful = 100, maxDiscarded = 1000) // the producer generator uses filter, I think | ||
|
||
import TestGraphGenerators._ | ||
import MemoryArbitraries._ | ||
implicit def testStore: Memory#Store[Int, Int] = mutable.Map[Int, Int]() | ||
implicit def testService: Memory#Service[Int, Int] = new mutable.HashMap[Int, Int]() with MemoryService[Int, Int] | ||
implicit def sink1: Memory#Sink[Int] = ((_) => Unit) | ||
implicit def sink2: Memory#Sink[(Int, Int)] = ((_) => Unit) | ||
|
||
def genProducer: Gen[Producer[Memory, _]] = oneOf(genProd1, genProd2, summed) | ||
|
||
test("DagOptimizer round trips") { | ||
forAll { p: Producer[Memory, Int] => | ||
val dagOpt = new DagOptimizer[Memory] { } | ||
|
||
assert(dagOpt.toLiteral(p).evaluate == p) | ||
} | ||
} | ||
|
||
val dagOpt = new DagOptimizer[Memory] { } | ||
|
||
test("ExpressionDag fanOut matches DependantGraph") { | ||
forAll(genProducer) { p: Producer[Memory, _] => | ||
val expDag = dagOpt.expressionDag(p)._1 | ||
|
||
// the expression considers an also a fanout, so | ||
// we can't use the standard Dependants, we need to | ||
// us parentsOf as the edge function | ||
val deps = new DependantGraph[Producer[Memory, Any]] { | ||
override lazy val nodes: List[Producer[Memory, Any]] = Producer.entireGraphOf(p) | ||
override def dependenciesOf(p: Producer[Memory, Any]) = Producer.parentsOf(p) | ||
} | ||
|
||
deps.nodes.foreach { n => | ||
deps.fanOut(n) match { | ||
case Some(fo) => assert(expDag.fanOut(n) == fo) | ||
case None => fail(s"node $n has no fanOut value") | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
val allRules = { | ||
import dagOpt._ | ||
|
||
List(RemoveNames, | ||
RemoveIdentityKeyed, | ||
FlatMapFusion, | ||
OptionMapFusion, | ||
OptionToFlatMap, | ||
KeyFlatMapToFlatMap, | ||
FlatMapKeyFusion, | ||
ValueFlatMapToFlatMap, | ||
FlatMapValuesFusion, | ||
FlatThenOptionFusion, | ||
DiamondToFlatMap, | ||
MergePullUp, | ||
AlsoPullUp) | ||
} | ||
|
||
val genRule: Gen[Rule[dagOpt.Prod]] = | ||
for { | ||
n <- Gen.choose(1, allRules.size) | ||
rs <- Gen.pick(n, allRules) // get n randomly selected | ||
} yield rs.reduce(_.orElse(_)) | ||
|
||
test("Rules are idempotent") { | ||
forAll(genProducer, genRule) { (p, r) => | ||
val once = dagOpt.optimize(p, r) | ||
val twice = dagOpt.optimize(once, r) | ||
assert(once == twice) | ||
} | ||
} | ||
|
||
test("fanOut matches after optimization") { | ||
|
||
forAll(genProducer, genRule) { (p, r) => | ||
|
||
val once = dagOpt.optimize(p, r) | ||
|
||
val expDag = dagOpt.expressionDag(once)._1 | ||
// the expression considers an also a fanout, so | ||
// we can't use the standard Dependants, we need to | ||
// us parentsOf as the edge function | ||
val deps = new DependantGraph[Producer[Memory, Any]] { | ||
override lazy val nodes: List[Producer[Memory, Any]] = Producer.entireGraphOf(once) | ||
override def dependenciesOf(p: Producer[Memory, Any]) = Producer.parentsOf(p) | ||
} | ||
|
||
deps.nodes.foreach { n => | ||
deps.fanOut(n) match { | ||
case Some(fo) => assert(expDag.fanOut(n) == fo, s"node: $n, in optimized: $once") | ||
case None => fail(s"node $n has no fanOut value") | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
test("test some idempotency specific past failures") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe nice to add a comment above on the details of the past failure? might be hard for folks reading the code to know? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know what to say. This was a hand minimized example (it took me about an hour) from a failure case found by scalacheck. Since the failures were quite rare, it took a long time to even find a failure with scalacheck, so once I found it, I wanted to test that failure every time. That's what I mean by "specific past failures". I understand (somewhat) why this failed now, but I couldn't easily generate another that would also show the bug. Can you suggest some specific text you would like to see me add? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I guess reading the title it doesn't give you a sense of what the issue is and what it's testing. Would it make sense to add a tldr of your understanding of why it failed? |
||
val list = List(-483916215) | ||
val list2 = list | ||
|
||
val map1 = new MemoryService[Int, Int] { | ||
val map = Map(1122506458 -> -422595330) | ||
def get(i: Int) = map.get(i) | ||
} | ||
|
||
val fn1 = { (i: Int) => List((i, i)) } | ||
val fn2 = { (tup: (Int, Int)) => Option(tup) } | ||
val fn3 = { i: (Int, (Int, Option[Int])) => List((i._1, i._2._1)) } | ||
val fn4 = fn1 | ||
val fn5 = fn2 | ||
val mmap: Memory#Store[Int, Int] = collection.mutable.Map.empty[Int, Int] | ||
|
||
val arg0: Producer[Memory, (Int, (Option[Int], Int))] = | ||
Summer[Memory, Int, Int](IdentityKeyedProducer(NamedProducer(IdentityKeyedProducer(MergedProducer(IdentityKeyedProducer(FlatMappedProducer(LeftJoinedProducer[Memory, Int, Int, Int](IdentityKeyedProducer(NamedProducer(IdentityKeyedProducer(NamedProducer(IdentityKeyedProducer(OptionMappedProducer(IdentityKeyedProducer(FlatMappedProducer(Source[Memory, Int](list), fn1)), fn2)),"tjiposzOlkplcu")),"tvpwpdyScehGnwcaVjjWvlfuwxatxhdjhozscucpbq")), map1), fn3)),IdentityKeyedProducer(OptionMappedProducer(IdentityKeyedProducer(FlatMappedProducer(Source[Memory, Int](list2), fn4)),fn5)))),"ncn")),mmap, implicitly[Semigroup[Int]]) | ||
|
||
val dagOpt = new DagOptimizer[Memory] { } | ||
|
||
val rule = { | ||
import dagOpt._ | ||
List[Rule[Prod]]( | ||
RemoveNames, | ||
RemoveIdentityKeyed, | ||
FlatMapFusion, | ||
OptionToFlatMap | ||
).reduce(_ orElse _) | ||
} | ||
val once = dagOpt.optimize(arg0, rule) | ||
val twice = dagOpt.optimize(once, rule) | ||
assert(twice == once) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove?