From afd5de935cb11394848c5040909f2f02fc26335f Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 26 Oct 2017 22:38:03 -0700 Subject: - Split out `ForgeTests` into `EvaluatioNTests` and `GraphTests` - Added a non-trivial test graph to our test suite EvaluationTests have broken, need to fix --- src/test/scala/forge/EvaluationTests.scala | 122 ++++++++++++ src/test/scala/forge/ForgeTests.scala | 309 ----------------------------- src/test/scala/forge/GraphTests.scala | 203 +++++++++++++++++++ src/test/scala/forge/TestUtil.scala | 78 ++++++++ 4 files changed, 403 insertions(+), 309 deletions(-) create mode 100644 src/test/scala/forge/EvaluationTests.scala delete mode 100644 src/test/scala/forge/ForgeTests.scala create mode 100644 src/test/scala/forge/GraphTests.scala create mode 100644 src/test/scala/forge/TestUtil.scala (limited to 'src/test') diff --git a/src/test/scala/forge/EvaluationTests.scala b/src/test/scala/forge/EvaluationTests.scala new file mode 100644 index 00000000..34ebd684 --- /dev/null +++ b/src/test/scala/forge/EvaluationTests.scala @@ -0,0 +1,122 @@ +package forge + +import java.nio.{file => jnio} + +import forge.Target.test +import utest._ + +object EvaluationTests extends TestSuite{ + + val tests = Tests{ + val baseCtx = DefCtx("forge.ForgeTests.tests ", None) + + val (singleton, pair, anonTriple, diamond, anonDiamond, bigSingleTerminal) = TestUtil.makeGraphs() + 'evaluateSingle - { + val evaluator = new Evaluator(jnio.Paths.get("target/workspace"), baseCtx) + + def check(target: Target[_], expValue: Any, expEvaled: Seq[Target[_]]) = { + val Evaluator.Results(returnedValues, returnedEvaluated) = evaluator.evaluate(Seq(target)) + assert( + returnedValues == Seq(expValue), + returnedEvaluated == expEvaled + ) + // Second time the value is already cached, so no evaluation needed + val Evaluator.Results(returnedValues2, returnedEvaluated2) = evaluator.evaluate(Seq(target)) + assert( + returnedValues2 == returnedValues, + returnedEvaluated2 == Nil + ) + } + + 'singleton - { + import singleton._ + // First time the target is evaluated + check(single, expValue = 0, expEvaled = Seq(single)) + + single.counter += 1 + // After incrementing the counter, it forces re-evaluation + check(single, expValue = 1, expEvaled = Seq(single)) + } + 'pair - { + import pair._ + check(down, expValue = 0, expEvaled = Seq(up, down)) + + down.counter += 1 + check(down, expValue = 1, expEvaled = Seq(down)) + + up.counter += 1 + check(down, expValue = 2, expEvaled = Seq(up, down)) + } + 'anonTriple - { + import anonTriple._ + val middle = down.inputs(0) + check(down, expValue = 0, expEvaled = Seq(up, middle, down)) + + down.counter += 1 + check(down, expValue = 1, expEvaled = Seq(down)) + + up.counter += 1 + check(down, expValue = 2, expEvaled = Seq(up, middle, down)) + + middle.asInstanceOf[Target.Test].counter += 1 + + check(down, expValue = 3, expEvaled = Seq(middle, down)) + } + 'diamond - { + import diamond._ + check(down, expValue = 0, expEvaled = Seq(up, left, right, down)) + + down.counter += 1 + check(down, expValue = 1, expEvaled = Seq(down)) + + up.counter += 1 + // Increment by 2 because up is referenced twice: once by left once by right + check(down, expValue = 3, expEvaled = Seq(up, left, right, down)) + + left.counter += 1 + check(down, expValue = 4, expEvaled = Seq(left, down)) + + right.counter += 1 + check(down, expValue = 5, expEvaled = Seq(right, down)) + } + 'anonDiamond - { + import anonDiamond._ + val left = down.inputs(0).asInstanceOf[Target.Test] + val right = down.inputs(1).asInstanceOf[Target.Test] + check(down, expValue = 0, expEvaled = Seq(up, left, right, down)) + + down.counter += 1 + check(down, expValue = 1, expEvaled = Seq(down)) + + up.counter += 1 + // Increment by 2 because up is referenced twice: once by left once by right + check(down, expValue = 3, expEvaled = Seq(up, left, right, down)) + + left.counter += 1 + check(down, expValue = 4, expEvaled = Seq(left, down)) + + right.counter += 1 + check(down, expValue = 5, expEvaled = Seq(right, down)) + } +// 'anonImpureDiamond - { +// import AnonImpureDiamond._ +// val left = down.inputs(0).asInstanceOf[Target.Test] +// val right = down.inputs(1).asInstanceOf[Target.Test] +// check(down, expValue = 0, expEvaled = Seq(up, left, right, down)) +// +// down.counter += 1 +// check(down, expValue = 1, expEvaled = Seq(left, down)) +// } + } + + +// 'full - { +// val sourceRoot = Target.path(jnio.Paths.get("src/test/resources/example/src")) +// val resourceRoot = Target.path(jnio.Paths.get("src/test/resources/example/resources")) +// val allSources = list(sourceRoot) +// val classFiles = compileAll(allSources) +// val jar = jarUp(resourceRoot, classFiles) +// Evaluator.apply(jar, jnio.Paths.get("target/workspace")) +// } + } +} diff --git a/src/test/scala/forge/ForgeTests.scala b/src/test/scala/forge/ForgeTests.scala deleted file mode 100644 index 5e9d8e37..00000000 --- a/src/test/scala/forge/ForgeTests.scala +++ /dev/null @@ -1,309 +0,0 @@ -package forge - -import utest._ -import Target.test -import java.nio.{file => jnio} -object ForgeTests extends TestSuite{ - - val tests = Tests{ - val baseCtx = DefCtx("forge.ForgeTests.tests ", None) - - object Singleton { - val single = T{ test() } - } - object Pair { - val up = T{ test() } - val down = T{ test(up) } - } - - object AnonTriple{ - val up = T{ test() } - val down = T{ test(test(up)) } - } - object Diamond{ - val up = T{ test() } - val left = T{ test(up) } - val right = T{ test(up) } - val down = T{ test(left, right) } - } - object AnonDiamond{ - val up = T{ test() } - val down = T{ test(test(up), test(up)) } - } - - object AnonImpureDiamond{ - val up = T{ test() } - val down = T{ test(test(up), test(up)) } - } - - - 'syntaxLimits - { - // Make sure that we properly prohibit cases where a `test()` target can - // be created more than once with the same `DefCtx`, while still allowing - // cases where the `test()` target is created exactly one time, or even - // zero-or-one times (since that's ok, as long as it's not more than once) - - 'neg - { - 'nakedTest - { - compileError("test()") - () - } - 'notFunctionCall - { - compileError("T{ 123 }") - () - } - 'functionCallWithoutImplicit - { - compileError("T{ println() }") - () - } - // Make sure the snippets without `test()`s compile, but the same snippets - // *with* the `test()` calls do not (presumably due to the `@compileTimeOnly` - // annotation) - // - // For some reason, `if(false)` isn't good enough because scalac constant - // folds the conditional, eliminates the entire code block, and makes any - // `@compileTimeOnly`s annotations disappear... - - - 'canEvaluateMoreThanOnce - { - if (math.random() > 10) T{ Seq(1, 2).map(_ => ???); test() } - compileError("T{ Seq(1, 2).map(_ => test()); test() }") - - if (math.random() > 10) T{ class Foo{ ??? }; test() } - compileError("T{ class Foo{ test() }; test() }") - - if (math.random() > 10) T{ test({while(true){ }; ???}) } - compileError("T{ test({while(true){ test() }; ???}) }") - - if (math.random() > 10) T{ do{ } while(true); test() } - compileError("T{ do{ test() } while(true); test() }") - - if (math.random() > 10) T{ def foo() = ???; test() } - compileError("T{ def foo() = test(); test() }") - - if (math.random() > 10) T{ None.getOrElse(???); test() } - if (math.random() > 10) T{ None.contains(test()); test() } - compileError("T{ None.getOrElse(test()); test() }") - - () - } - } - 'pos - { - T{ test({val x = test(); x}) } - T{ test({lazy val x = test(); x}) } - T { object foo {val x = test()}; test(foo.x) } - T{ test({val x = if (math.random() > 0.5) test() else test(); x}) } - - () - } - } - - - 'topoSortedTransitiveTargets - { - def check(targets: Seq[Target[_]], expected: Seq[Target[_]]) = { - val result = Evaluator.topoSortedTransitiveTargets(targets).values - assert(result == expected) - } - - 'singleton - check( - targets = Seq(Singleton.single), - expected = Seq(Singleton.single) - ) - 'pair - check( - targets = Seq(Pair.down), - expected = Seq(Pair.up, Pair.down) - ) - 'anonTriple - check( - targets = Seq(AnonTriple.down), - expected = Seq(AnonTriple.up, AnonTriple.down.inputs(0), AnonTriple.down) - ) - 'diamond - check( - targets = Seq(Diamond.down), - expected = Seq(Diamond.up, Diamond.left, Diamond.right, Diamond.down) - ) - 'anonDiamond - check( - targets = Seq(Diamond.down), - expected = Seq( - Diamond.up, - Diamond.down.inputs(0), - Diamond.down.inputs(1), - Diamond.down - ) - ) - } - - 'groupAroundNamedTargets - { - def check(target: Target[_], expected: Seq[Seq[Target[_]]]) = { - val grouped = Evaluator.groupAroundNamedTargets( - Evaluator.topoSortedTransitiveTargets(Seq(target)) - ) - assert(grouped == expected) - } - 'singleton - check( - Singleton.single, - Seq(Seq(Singleton.single)) - ) - 'pair - check( - Pair.down, - Seq(Seq(Pair.up), Seq(Pair.down)) - ) - 'anonTriple - check( - AnonTriple.down, - Seq(Seq(AnonTriple.up), Seq(AnonTriple.down.inputs(0), AnonTriple.down)) - ) - 'diamond - check( - Diamond.down, - Seq(Seq(Diamond.up), Seq(Diamond.left), Seq(Diamond.right), Seq(Diamond.down)) - ) - 'anonDiamond - check( - AnonDiamond.down, - Seq( - Seq(AnonDiamond.up), - Seq(AnonDiamond.down.inputs(1), AnonDiamond.down.inputs(0), AnonDiamond.down) - ) - ) - } - - 'labeling - { - - def check(t: Target[_], relPath: String) = { - val targetLabel = t.defCtx.label - val expectedLabel = baseCtx.label + relPath - assert(targetLabel == expectedLabel) - } - 'singleton - check(Singleton.single, "Singleton.single") - 'pair - { - check(Pair.up, "Pair.up") - check(Pair.down, "Pair.down") - } - - 'anonTriple - { - check(AnonTriple.up, "AnonTriple.up") - check(AnonTriple.down.inputs(0), "AnonTriple.down1") - check(AnonTriple.down, "AnonTriple.down") - } - - 'diamond - { - check(Diamond.up, "Diamond.up") - check(Diamond.left, "Diamond.left") - check(Diamond.right, "Diamond.right") - check(Diamond.down, "Diamond.down") - } - - 'anonDiamond - { - check(AnonDiamond.up, "AnonDiamond.up") - check(AnonDiamond.down.inputs(0), "AnonDiamond.down1") - check(AnonDiamond.down.inputs(1), "AnonDiamond.down2") - check(AnonDiamond.down, "AnonDiamond.down") - } - - } - 'evaluateSingle - { - val evaluator = new Evaluator(jnio.Paths.get("target/workspace"), baseCtx) - - def check(target: Target[_], expValue: Any, expEvaled: Seq[Target[_]]) = { - val Evaluator.Results(returnedValues, returnedEvaluated) = evaluator.evaluate(Seq(target)) - assert( - returnedValues == Seq(expValue), - returnedEvaluated == expEvaled - ) - // Second time the value is already cached, so no evaluation needed - val Evaluator.Results(returnedValues2, returnedEvaluated2) = evaluator.evaluate(Seq(target)) - assert( - returnedValues2 == returnedValues, - returnedEvaluated2 == Nil - ) - } - - 'singleton - { - import Singleton._ - // First time the target is evaluated - check(single, expValue = 0, expEvaled = Seq(single)) - - single.counter += 1 - // After incrementing the counter, it forces re-evaluation - check(single, expValue = 1, expEvaled = Seq(single)) - } - 'pair - { - import Pair._ - check(down, expValue = 0, expEvaled = Seq(up, down)) - - down.counter += 1 - check(down, expValue = 1, expEvaled = Seq(down)) - - up.counter += 1 - check(down, expValue = 2, expEvaled = Seq(up, down)) - } - 'anonTriple - { - import AnonTriple._ - val middle = down.inputs(0) - check(down, expValue = 0, expEvaled = Seq(up, middle, down)) - - down.counter += 1 - check(down, expValue = 1, expEvaled = Seq(down)) - - up.counter += 1 - check(down, expValue = 2, expEvaled = Seq(up, middle, down)) - - middle.asInstanceOf[Target.Test].counter += 1 - - check(down, expValue = 3, expEvaled = Seq(middle, down)) - } - 'diamond - { - import Diamond._ - check(down, expValue = 0, expEvaled = Seq(up, left, right, down)) - - down.counter += 1 - check(down, expValue = 1, expEvaled = Seq(down)) - - up.counter += 1 - // Increment by 2 because up is referenced twice: once by left once by right - check(down, expValue = 3, expEvaled = Seq(up, left, right, down)) - - left.counter += 1 - check(down, expValue = 4, expEvaled = Seq(left, down)) - - right.counter += 1 - check(down, expValue = 5, expEvaled = Seq(right, down)) - } - 'anonDiamond - { - import AnonDiamond._ - val left = down.inputs(0).asInstanceOf[Target.Test] - val right = down.inputs(1).asInstanceOf[Target.Test] - check(down, expValue = 0, expEvaled = Seq(up, left, right, down)) - - down.counter += 1 - check(down, expValue = 1, expEvaled = Seq(down)) - - up.counter += 1 - // Increment by 2 because up is referenced twice: once by left once by right - check(down, expValue = 3, expEvaled = Seq(up, left, right, down)) - - left.counter += 1 - check(down, expValue = 4, expEvaled = Seq(left, down)) - - right.counter += 1 - check(down, expValue = 5, expEvaled = Seq(right, down)) - } -// 'anonImpureDiamond - { -// import AnonImpureDiamond._ -// val left = down.inputs(0).asInstanceOf[Target.Test] -// val right = down.inputs(1).asInstanceOf[Target.Test] -// check(down, expValue = 0, expEvaled = Seq(up, left, right, down)) -// -// down.counter += 1 -// check(down, expValue = 1, expEvaled = Seq(left, down)) -// } - } - - -// 'full - { -// val sourceRoot = Target.path(jnio.Paths.get("src/test/resources/example/src")) -// val resourceRoot = Target.path(jnio.Paths.get("src/test/resources/example/resources")) -// val allSources = list(sourceRoot) -// val classFiles = compileAll(allSources) -// val jar = jarUp(resourceRoot, classFiles) -// Evaluator.apply(jar, jnio.Paths.get("target/workspace")) -// } - } -} diff --git a/src/test/scala/forge/GraphTests.scala b/src/test/scala/forge/GraphTests.scala new file mode 100644 index 00000000..85b63131 --- /dev/null +++ b/src/test/scala/forge/GraphTests.scala @@ -0,0 +1,203 @@ +package forge + +import utest._ +import Target.test +import java.nio.{file => jnio} + +object GraphTests extends TestSuite{ + + val tests = Tests{ + + + val (singleton, pair, anonTriple, diamond, anonDiamond, bigSingleTerminal) = TestUtil.makeGraphs() + + + 'syntaxLimits - { + // Make sure that we properly prohibit cases where a `test()` target can + // be created more than once with the same `DefCtx`, while still allowing + // cases where the `test()` target is created exactly one time, or even + // zero-or-one times (since that's ok, as long as it's not more than once) + + 'neg - { + 'nakedTest - { + compileError("test()") + () + } + 'notFunctionCall - { + compileError("T{ 123 }") + () + } + 'functionCallWithoutImplicit - { + compileError("T{ println() }") + () + } + // Make sure the snippets without `test()`s compile, but the same snippets + // *with* the `test()` calls do not (presumably due to the `@compileTimeOnly` + // annotation) + // + // For some reason, `if(false)` isn't good enough because scalac constant + // folds the conditional, eliminates the entire code block, and makes any + // `@compileTimeOnly`s annotations disappear... + + + 'canEvaluateMoreThanOnce - { + if (math.random() > 10) T{ Seq(1, 2).map(_ => ???); test() } + compileError("T{ Seq(1, 2).map(_ => test()); test() }") + + if (math.random() > 10) T{ class Foo{ ??? }; test() } + compileError("T{ class Foo{ test() }; test() }") + + if (math.random() > 10) T{ test({while(true){ }; ???}) } + compileError("T{ test({while(true){ test() }; ???}) }") + + if (math.random() > 10) T{ do{ } while(true); test() } + compileError("T{ do{ test() } while(true); test() }") + + if (math.random() > 10) T{ def foo() = ???; test() } + compileError("T{ def foo() = test(); test() }") + + if (math.random() > 10) T{ None.getOrElse(???); test() } + if (math.random() > 10) T{ None.contains(test()); test() } + compileError("T{ None.getOrElse(test()); test() }") + + () + } + } + 'pos - { + T{ test({val x = test(); x}) } + T{ test({lazy val x = test(); x}) } + T { object foo {val x = test()}; test(foo.x) } + T{ test({val x = if (math.random() > 0.5) test() else test(); x}) } + + () + } + } + + + 'topoSortedTransitiveTargets - { + def check(targets: Seq[Target[_]], expected: Set[Target[_]]) = { + val result = Evaluator.topoSortedTransitiveTargets(targets).values + TestUtil.checkTopological(result) + assert(result.toSet == expected) + } + + 'singleton - check( + targets = Seq(singleton.single), + expected = Set(singleton.single) + ) + 'pair - check( + targets = Seq(pair.down), + expected = Set(pair.up, pair.down) + ) + 'anonTriple - check( + targets = Seq(anonTriple.down), + expected = Set(anonTriple.up, anonTriple.down.inputs(0), anonTriple.down) + ) + 'diamond - check( + targets = Seq(diamond.down), + expected = Set(diamond.up, diamond.left, diamond.right, diamond.down) + ) + 'anonDiamond - check( + targets = Seq(diamond.down), + expected = Set( + diamond.up, + diamond.down.inputs(0), + diamond.down.inputs(1), + diamond.down + ) + ) + 'bigSingleTerminal - { + val result = Evaluator.topoSortedTransitiveTargets(Seq(bigSingleTerminal.j)).values + TestUtil.checkTopological(result) + assert(result.distinct.length == 28) + } + } + + 'groupAroundNamedTargets - { + def check(target: Target[_], expected: Seq[Set[String]]) = { + val grouped = Evaluator.groupAroundNamedTargets( + Evaluator.topoSortedTransitiveTargets(Seq(target)) + ) + TestUtil.checkTopological(grouped.flatten) + val stringified = grouped.map(_.map(_.toString).toSet) + assert(stringified == expected.map(_.toSet)) + } + 'singleton - check( + singleton.single, + Seq(Set("single")) + ) + 'pair - check( + pair.down, + Seq(Set("up"), Set("down")) + ) + 'anonTriple - check( + anonTriple.down, + Seq(Set("up"), Set("down1", "down")) + ) + 'diamond - check( + diamond.down, + Seq(Set("up"), Set("left"), Set("right"), Set("down")) + ) + 'anonDiamond - check( + anonDiamond.down, + Seq( + Set("up"), + Set("down2", "down1", "down") + ) + ) + 'bigSingleTerminal - check( + bigSingleTerminal.j, + Seq( + Set("i1"), + Set("e4"), + Set("a1"), + Set("a2"), + Set("a"), + Set("b1"), + Set("b"), + Set("e5", "e2", "e8", "e1", "e7", "e6", "e3", "e"), + Set("i2", "i5", "i4", "i3", "i"), + Set("f2"), + Set("f3", "f1", "f"), + Set("j3", "j2", "j1", "j") + ) + ) + } + + 'labeling - { + + def check(t: Target[_], relPath: String) = { + val targetLabel = t.defCtx.label.split(' ').last + + assert(targetLabel == relPath) + } + 'singleton - check(singleton.single, "singleton.single") + 'pair - { + check(pair.up, "pair.up") + check(pair.down, "pair.down") + } + + 'anonTriple - { + check(anonTriple.up, "anonTriple.up") + check(anonTriple.down.inputs(0), "anonTriple.down1") + check(anonTriple.down, "anonTriple.down") + } + + 'diamond - { + check(diamond.up, "diamond.up") + check(diamond.left, "diamond.left") + check(diamond.right, "diamond.right") + check(diamond.down, "diamond.down") + } + + 'anonDiamond - { + check(anonDiamond.up, "anonDiamond.up") + check(anonDiamond.down.inputs(0), "anonDiamond.down1") + check(anonDiamond.down.inputs(1), "anonDiamond.down2") + check(anonDiamond.down, "anonDiamond.down") + } + + } + + } +} diff --git a/src/test/scala/forge/TestUtil.scala b/src/test/scala/forge/TestUtil.scala new file mode 100644 index 00000000..5a328d87 --- /dev/null +++ b/src/test/scala/forge/TestUtil.scala @@ -0,0 +1,78 @@ +package forge + +import forge.Target.test +import utest.assert + +import scala.collection.mutable + +object TestUtil { + + def checkTopological(targets: Seq[Target[_]]) = { + val seen = mutable.Set.empty[Target[_]] + for(t <- targets.reverseIterator){ + seen.add(t) + for(upstream <- t.inputs){ + assert(!seen(upstream)) + } + } + } + + def makeGraphs() = { + object singleton { + val single = T{ test() } + } + object pair { + val up = T{ test() } + val down = T{ test(up) } + } + + object anonTriple{ + val up = T{ test() } + val down = T{ test(test(up)) } + } + object diamond{ + val up = T{ test() } + val left = T{ test(up) } + val right = T{ test(up) } + val down = T{ test(left, right) } + } + object anonDiamond{ + val up = T{ test() } + val down = T{ test(test(up), test(up)) } + } + + // x g-----o + // \ \ \ + // x o h-----I---o + // \ / \ / \ / \ \ + // A---c--o E o-o \ \ + // / \ / \ / \ o---J + // x d o--o o / / + // \ / \ / / + // o o---F---o + // / / + // x--B x + object bigSingleTerminal{ + val a = T{ test(test(), test()) } + val b = T{ test(test()) } + val e = T{ + val c = test(a) + val d = test(a) + test(test(test(), test(c)), test(test(c, test(d, b)))) + } + val f = T{ + test(test(test(), test(e))) + } + val i = T{ + val g = test() + val h = test(g, e) + test(test(g), test(test(h))) + } + val j = T{ + test(test(i), test(i, f), test(f)) + } + } + + (singleton, pair, anonTriple, diamond, anonDiamond, bigSingleTerminal) + } +} -- cgit v1.2.3