summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/src/main/scala/forge/define/Task.scala17
-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
-rw-r--r--scalaplugin/src/main/scala/forge/scalaplugin/Subproject.scala4
5 files changed, 116 insertions, 44 deletions
diff --git a/core/src/main/scala/forge/define/Task.scala b/core/src/main/scala/forge/define/Task.scala
index a78fcaa7..298815d5 100644
--- a/core/src/main/scala/forge/define/Task.scala
+++ b/core/src/main/scala/forge/define/Task.scala
@@ -44,19 +44,24 @@ object Task extends Applicative.Applyer[Task, Task, Args]{
val inputs = Nil
def evaluate(args: Args) = t0
}
+
+ def apply[T](t: T): Target[T] = macro targetCachedImpl[T]
def apply[T](t: Task[T]): Target[T] = macro Cacher.impl0[Task, T]
- def cmd[T](t: T): Command[T] = macro targetCommandImpl[T]
+ def command[T](t: T): Command[T] = macro targetCommandImpl[T]
+ def command[T](t: Task[T]): Command[T] = new Command(t)
+
+ def task[T](t: T): Task[T] = macro Applicative.impl[Task, T, Args]
+ def task[T](t: Task[T]): Task[T] = t
+
+
def targetCommandImpl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T]): c.Expr[Command[T]] = {
import c.universe._
+
c.Expr[Command[T]](
- q"new forge.define.Command(${Applicative.impl[Task, T, Args](c)(t).tree})"
+ q"new ${weakTypeOf[Command[T]]}(${Applicative.impl[Task, T, Args](c)(t).tree})"
)
}
- def task[T](t: T): Task[T] = macro Applicative.impl[Task, T, Args]
-
- def apply[T](t: T): Target[T] = macro targetCachedImpl[T]
-
def targetCachedImpl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T]): c.Expr[Target[T]] = {
c.Expr[Target[T]](
forge.define.Cacher.wrapCached(c)(
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
}
diff --git a/scalaplugin/src/main/scala/forge/scalaplugin/Subproject.scala b/scalaplugin/src/main/scala/forge/scalaplugin/Subproject.scala
index 0ec22614..a94dac6b 100644
--- a/scalaplugin/src/main/scala/forge/scalaplugin/Subproject.scala
+++ b/scalaplugin/src/main/scala/forge/scalaplugin/Subproject.scala
@@ -183,12 +183,12 @@ abstract class Subproject extends Cacher{
def classpath = T{ Seq(resources(), compiled()) }
def jar = T{ modules.Jvm.jarUp(resources, compiled) }
- def run(mainClass: String) = T.cmd{
+ def run(mainClass: String) = T.command{
import ammonite.ops._, ImplicitWd._
%('java, "-cp", (runDepClasspath().map(_.path) :+ compiled()).mkString(":"), mainClass)
}
- def console() = T.cmd{
+ def console() = T.command{
import ammonite.ops._, ImplicitWd._
%('java,
"-cp",