summaryrefslogtreecommitdiff
path: root/core/src/test
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2017-11-10 05:03:35 -0800
committerLi Haoyi <haoyi.sg@gmail.com>2017-11-10 05:03:35 -0800
commit79bfb324a231bb3f46a180039b6d68ca042f181b (patch)
treefa0b12f98d2f2b66e49dd2b46c7d3ccf6743cf8e /core/src/test
parentb0e32c659e334b334e3f1e1a835328b32f20e759 (diff)
downloadmill-79bfb324a231bb3f46a180039b6d68ca042f181b.tar.gz
mill-79bfb324a231bb3f46a180039b6d68ca042f181b.tar.bz2
mill-79bfb324a231bb3f46a180039b6d68ca042f181b.zip
Add basic tests for un-cached evaluation of `Task`s
Diffstat (limited to 'core/src/test')
-rw-r--r--core/src/test/scala/forge/EvaluationTests.scala132
-rw-r--r--core/src/test/scala/forge/JavaCompileJarTests.scala3
-rw-r--r--core/src/test/scala/forge/TestUtil.scala4
3 files changed, 103 insertions, 36 deletions
diff --git a/core/src/test/scala/forge/EvaluationTests.scala b/core/src/test/scala/forge/EvaluationTests.scala
index 72b7deff..5fe29446 100644
--- a/core/src/test/scala/forge/EvaluationTests.scala
+++ b/core/src/test/scala/forge/EvaluationTests.scala
@@ -1,7 +1,9 @@
package forge
-import forge.define.Task
+import forge.TestUtil.{Test, test}
+import forge.define.{Target, Task}
+import forge.define.Task.Cacher
import forge.discover.Discovered
import forge.eval.Evaluator
import forge.util.OSet
@@ -9,43 +11,45 @@ import utest._
import utest.framework.TestPath
object EvaluationTests extends TestSuite{
+ class Checker[T: Discovered](base: T)(implicit tp: TestPath) {
+ val workspace = ammonite.ops.pwd / 'target / 'workspace / tp.value
+ ammonite.ops.rm(ammonite.ops.Path(workspace, ammonite.ops.pwd))
+ // Make sure data is persisted even if we re-create the evaluator each time
+ def evaluator = new Evaluator(workspace, Discovered.mapping(base))
+
+ def apply(target: Task[_], expValue: Any,
+ expEvaled: OSet[Task[_]],
+ extraEvaled: Int = 0,
+ secondRun: Boolean = true) = {
+
+ val Evaluator.Results(returnedValues, returnedEvaluated) = evaluator.evaluate(OSet(target))
+
+ val (matchingReturnedEvaled, extra) = returnedEvaluated.items.partition(expEvaled.contains)
+
+ assert(
+ returnedValues == Seq(expValue),
+ matchingReturnedEvaled.toSet == expEvaled.toSet,
+ extra.length == extraEvaled
+ )
+
+ // Second time the value is already cached, so no evaluation needed
+ if (secondRun){
+ val Evaluator.Results(returnedValues2, returnedEvaluated2) = evaluator.evaluate(OSet(target))
+ val expecteSecondRunEvaluated = OSet()
+ assert(
+ returnedValues2 == returnedValues,
+ returnedEvaluated2 == expecteSecondRunEvaluated
+ )
+ }
+ }
+ }
+
val tests = Tests{
val graphs = new TestGraphs()
import graphs._
'evaluateSingle - {
- class Checker[T: Discovered](base: T)(implicit tp: TestPath) {
- val workspace = ammonite.ops.pwd / 'target / 'workspace / tp.value
- ammonite.ops.rm(ammonite.ops.Path(workspace, ammonite.ops.pwd))
- // Make sure data is persisted even if we re-create the evaluator each time
- def evaluator = new Evaluator(
- workspace,
-
- Discovered.mapping(base)
- )
- def apply(target: Task[_], expValue: Any,
- expEvaled: OSet[Task[_]],
- extraEvaled: Int = 0) = {
-
- val Evaluator.Results(returnedValues, returnedEvaluated) = evaluator.evaluate(OSet(target))
-
- val (matchingReturnedEvaled, extra) = returnedEvaluated.items.partition(expEvaled.contains)
-
- assert(
- returnedValues == Seq(expValue),
- matchingReturnedEvaled.toSet == expEvaled.toSet,
- extra.length == extraEvaled
- )
-
- // Second time the value is already cached, so no evaluation needed
- val Evaluator.Results(returnedValues2, returnedEvaluated2) = evaluator.evaluate(OSet(target))
- assert(
- returnedValues2 == returnedValues,
- returnedEvaluated2 == OSet()
- )
- }
- }
'singleton - {
import singleton._
@@ -142,6 +146,70 @@ object EvaluationTests extends TestSuite{
}
}
+ 'evaluateMixed - {
+ 'tasksAreUncached - {
+ // ___ left ___
+ // / \
+ // up middle -- down
+ // /
+ // right
+ object taskDiamond extends Cacher{
+ var leftCount = 0
+ var rightCount = 0
+ var middleCount = 0
+ def up = T{ test() }
+ def left = T.task{ leftCount += 1; up() + 1 }
+ def middle = T.task{ middleCount += 1; 100 }
+ def right = T{ rightCount += 1; 10000 }
+ def down = T{ left() + middle() + right() }
+ }
+
+ import taskDiamond._
+
+ // Ensure task objects themselves are not cached, and recomputed each time
+ assert(
+ up eq up,
+ left ne left,
+ middle ne middle,
+ right eq right,
+ down eq down
+ )
+
+ // During the first evaluation, they get computed normally like any
+ // cached target
+ val check = new Checker(taskDiamond)
+ assert(leftCount == 0, rightCount == 0)
+ check(down, expValue = 10101, expEvaled = OSet(up, right, down), extraEvaled = 8)
+ assert(leftCount == 1, middleCount == 1, rightCount == 1)
+
+ // If the upstream `up` doesn't change, the entire block of tasks
+ // doesn't need to recompute
+ check(down, expValue = 10101, expEvaled = OSet())
+ assert(leftCount == 1, middleCount == 1, rightCount == 1)
+
+ // But if `up` changes, the entire block of downstream tasks needs to
+ // recompute together, including `middle` which doesn't depend on `up`,
+ // because tasks have no cached value that can be used. `right`, which
+ // is a cached Target, does not recompute
+ up.inputs(0).asInstanceOf[Test].counter += 1
+ check(down, expValue = 10102, expEvaled = OSet(up, down), extraEvaled = 6)
+ assert(leftCount == 2, middleCount == 2, rightCount == 1)
+
+ // Running the tasks themselves results in them being recomputed every
+ // single time, even if nothing changes
+ check(left, expValue = 2, expEvaled = OSet(), extraEvaled = 1, secondRun = false)
+ assert(leftCount == 3, middleCount == 2, rightCount == 1)
+ check(left, expValue = 2, expEvaled = OSet(), extraEvaled = 1, secondRun = false)
+ assert(leftCount == 4, middleCount == 2, rightCount == 1)
+
+ check(middle, expValue = 100, expEvaled = OSet(), extraEvaled = 2, secondRun = false)
+ assert(leftCount == 4, middleCount == 3, rightCount == 1)
+ check(middle, expValue = 100, expEvaled = OSet(), extraEvaled = 2, secondRun = false)
+ assert(leftCount == 4, middleCount == 4, rightCount == 1)
+ }
+
+
+ }
}
}
diff --git a/core/src/test/scala/forge/JavaCompileJarTests.scala b/core/src/test/scala/forge/JavaCompileJarTests.scala
index fcd81588..1c9bf9b8 100644
--- a/core/src/test/scala/forge/JavaCompileJarTests.scala
+++ b/core/src/test/scala/forge/JavaCompileJarTests.scala
@@ -44,8 +44,7 @@ object JavaCompileJarTests extends TestSuite{
def classFiles = T{ compileAll(Task.ctx().dest, allSources()) }
def jar = T{ jarUp(resourceRoot, classFiles) }
- @forge.discover.Router.main
- def run(mainClsName: String): Task[CommandResult] = T.cmd{
+ def run(mainClsName: String) = T.command{
%%('java, "-cp", classFiles().path, mainClsName)
}
}
diff --git a/core/src/test/scala/forge/TestUtil.scala b/core/src/test/scala/forge/TestUtil.scala
index 91dc3da9..1ae60e6d 100644
--- a/core/src/test/scala/forge/TestUtil.scala
+++ b/core/src/test/scala/forge/TestUtil.scala
@@ -16,10 +16,10 @@ object TestUtil {
* controlled externally, so you can construct arbitrary dataflow graphs and
* test how changes propagate.
*/
- class Test(val inputs: Seq[Task[Int]],
+ class Test(override val inputs: Seq[Task[Int]],
val pure: Boolean) extends Task[Int]{
var counter = 0
- def evaluate(args: Args) = {
+ override def evaluate(args: Args) = {
counter + args.args.map(_.asInstanceOf[Int]).sum
}