From 9ba4cb69331386dfde9bac69dc2d5b22401face3 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 12 Dec 2018 16:56:02 -0800 Subject: collapse boilerplate folder structure within src/ folders (#505) * collapse boilerplate folder structure within src/ folders * . --- main/test/src/TestMain.scala | 6 + main/test/src/UTestFramework.scala | 11 + main/test/src/define/ApplicativeTests.scala | 125 ++++++++ main/test/src/define/BasePathTests.scala | 73 +++++ main/test/src/define/CacherTests.scala | 75 +++++ main/test/src/define/DiscoverTests.scala | 63 ++++ main/test/src/define/GraphTests.scala | 211 +++++++++++++ main/test/src/define/MacroErrorTests.scala | 145 +++++++++ main/test/src/eval/CrossTests.scala | 56 ++++ main/test/src/eval/EvaluationTests.scala | 354 ++++++++++++++++++++++ main/test/src/eval/FailureTests.scala | 132 ++++++++ main/test/src/eval/JavaCompileJarTests.scala | 164 ++++++++++ main/test/src/eval/ModuleTests.scala | 45 +++ main/test/src/eval/TarjanTests.scala | 91 ++++++ main/test/src/eval/TaskTests.scala | 95 ++++++ main/test/src/main/ClientServerTests.scala | 214 +++++++++++++ main/test/src/main/ForeignBuildsTest.scala | 30 ++ main/test/src/main/ForeignConflictTest.scala | 25 ++ main/test/src/main/JavaCompileJarTests.scala | 67 ++++ main/test/src/main/MainTests.scala | 272 +++++++++++++++++ main/test/src/mill/TestMain.scala | 6 - main/test/src/mill/UTestFramework.scala | 11 - main/test/src/mill/define/ApplicativeTests.scala | 125 -------- main/test/src/mill/define/BasePathTests.scala | 73 ----- main/test/src/mill/define/CacherTests.scala | 75 ----- main/test/src/mill/define/DiscoverTests.scala | 63 ---- main/test/src/mill/define/GraphTests.scala | 211 ------------- main/test/src/mill/define/MacroErrorTests.scala | 145 --------- main/test/src/mill/eval/CrossTests.scala | 56 ---- main/test/src/mill/eval/EvaluationTests.scala | 354 ---------------------- main/test/src/mill/eval/FailureTests.scala | 132 -------- main/test/src/mill/eval/JavaCompileJarTests.scala | 164 ---------- main/test/src/mill/eval/ModuleTests.scala | 45 --- main/test/src/mill/eval/TarjanTests.scala | 91 ------ main/test/src/mill/eval/TaskTests.scala | 95 ------ main/test/src/mill/main/ClientServerTests.scala | 214 ------------- main/test/src/mill/main/ForeignBuildsTest.scala | 30 -- main/test/src/mill/main/ForeignConflictTest.scala | 25 -- main/test/src/mill/main/JavaCompileJarTests.scala | 67 ---- main/test/src/mill/main/MainTests.scala | 272 ----------------- main/test/src/mill/util/ParseArgsTest.scala | 254 ---------------- main/test/src/mill/util/ScriptTestSuite.scala | 53 ---- main/test/src/mill/util/TestEvaluator.scala | 80 ----- main/test/src/mill/util/TestGraphs.scala | 271 ----------------- main/test/src/mill/util/TestUtil.scala | 86 ------ main/test/src/util/ParseArgsTest.scala | 254 ++++++++++++++++ main/test/src/util/ScriptTestSuite.scala | 53 ++++ main/test/src/util/TestEvaluator.scala | 80 +++++ main/test/src/util/TestGraphs.scala | 271 +++++++++++++++++ main/test/src/util/TestUtil.scala | 86 ++++++ 50 files changed, 2998 insertions(+), 2998 deletions(-) create mode 100644 main/test/src/TestMain.scala create mode 100644 main/test/src/UTestFramework.scala create mode 100644 main/test/src/define/ApplicativeTests.scala create mode 100644 main/test/src/define/BasePathTests.scala create mode 100644 main/test/src/define/CacherTests.scala create mode 100644 main/test/src/define/DiscoverTests.scala create mode 100644 main/test/src/define/GraphTests.scala create mode 100644 main/test/src/define/MacroErrorTests.scala create mode 100644 main/test/src/eval/CrossTests.scala create mode 100644 main/test/src/eval/EvaluationTests.scala create mode 100644 main/test/src/eval/FailureTests.scala create mode 100644 main/test/src/eval/JavaCompileJarTests.scala create mode 100644 main/test/src/eval/ModuleTests.scala create mode 100644 main/test/src/eval/TarjanTests.scala create mode 100644 main/test/src/eval/TaskTests.scala create mode 100644 main/test/src/main/ClientServerTests.scala create mode 100644 main/test/src/main/ForeignBuildsTest.scala create mode 100644 main/test/src/main/ForeignConflictTest.scala create mode 100644 main/test/src/main/JavaCompileJarTests.scala create mode 100644 main/test/src/main/MainTests.scala delete mode 100644 main/test/src/mill/TestMain.scala delete mode 100644 main/test/src/mill/UTestFramework.scala delete mode 100644 main/test/src/mill/define/ApplicativeTests.scala delete mode 100644 main/test/src/mill/define/BasePathTests.scala delete mode 100644 main/test/src/mill/define/CacherTests.scala delete mode 100644 main/test/src/mill/define/DiscoverTests.scala delete mode 100644 main/test/src/mill/define/GraphTests.scala delete mode 100644 main/test/src/mill/define/MacroErrorTests.scala delete mode 100644 main/test/src/mill/eval/CrossTests.scala delete mode 100644 main/test/src/mill/eval/EvaluationTests.scala delete mode 100644 main/test/src/mill/eval/FailureTests.scala delete mode 100644 main/test/src/mill/eval/JavaCompileJarTests.scala delete mode 100644 main/test/src/mill/eval/ModuleTests.scala delete mode 100644 main/test/src/mill/eval/TarjanTests.scala delete mode 100644 main/test/src/mill/eval/TaskTests.scala delete mode 100644 main/test/src/mill/main/ClientServerTests.scala delete mode 100644 main/test/src/mill/main/ForeignBuildsTest.scala delete mode 100644 main/test/src/mill/main/ForeignConflictTest.scala delete mode 100644 main/test/src/mill/main/JavaCompileJarTests.scala delete mode 100644 main/test/src/mill/main/MainTests.scala delete mode 100644 main/test/src/mill/util/ParseArgsTest.scala delete mode 100644 main/test/src/mill/util/ScriptTestSuite.scala delete mode 100644 main/test/src/mill/util/TestEvaluator.scala delete mode 100644 main/test/src/mill/util/TestGraphs.scala delete mode 100644 main/test/src/mill/util/TestUtil.scala create mode 100644 main/test/src/util/ParseArgsTest.scala create mode 100644 main/test/src/util/ScriptTestSuite.scala create mode 100644 main/test/src/util/TestEvaluator.scala create mode 100644 main/test/src/util/TestGraphs.scala create mode 100644 main/test/src/util/TestUtil.scala (limited to 'main/test') diff --git a/main/test/src/TestMain.scala b/main/test/src/TestMain.scala new file mode 100644 index 00000000..80e7e627 --- /dev/null +++ b/main/test/src/TestMain.scala @@ -0,0 +1,6 @@ +package mill + +object TestMain { + def main(args: Array[String]): Unit = { + } +} diff --git a/main/test/src/UTestFramework.scala b/main/test/src/UTestFramework.scala new file mode 100644 index 00000000..c234151b --- /dev/null +++ b/main/test/src/UTestFramework.scala @@ -0,0 +1,11 @@ +package mill + +class UTestFramework extends utest.runner.Framework { + override def exceptionStackFrameHighlighter(s: StackTraceElement) = { + s.getClassName.startsWith("mill.") + } + override def setup() = { + + os.remove.all(os.pwd / 'target / 'workspace) + } +} diff --git a/main/test/src/define/ApplicativeTests.scala b/main/test/src/define/ApplicativeTests.scala new file mode 100644 index 00000000..9dd2132f --- /dev/null +++ b/main/test/src/define/ApplicativeTests.scala @@ -0,0 +1,125 @@ +package mill.define + +import mill.api.Ctx.ImplicitStub +import utest._ + +import scala.annotation.compileTimeOnly +import scala.language.experimental.macros + + +object ApplicativeTests extends TestSuite { + implicit def optionToOpt[T](o: Option[T]): Opt[T] = new Opt(o) + class Opt[T](val self: Option[T]) extends Applicative.Applyable[Option, T] + object Opt extends OptGenerated with Applicative.Applyer[Opt, Option, Applicative.Id, String]{ + + val injectedCtx = "helloooo" + def underlying[A](v: Opt[A]) = v.self + def apply[T](t: T): Option[T] = macro Applicative.impl[Option, T, String] + + def mapCtx[A, B](a: Option[A])(f: (A, String) => B): Option[B] = a.map(f(_, injectedCtx)) + def zip() = Some(()) + def zip[A](a: Option[A]) = a.map(Tuple1(_)) + } + class Counter{ + var value = 0 + def apply() = { + value += 1 + value + } + } + @compileTimeOnly("Target.ctx() can only be used with a T{...} block") + @ImplicitStub + implicit def taskCtx: String = ??? + + val tests = Tests{ + + 'selfContained - { + + 'simple - assert(Opt("lol " + 1) == Some("lol 1")) + 'singleSome - assert(Opt("lol " + Some("hello")()) == Some("lol hello")) + 'twoSomes - assert(Opt(Some("lol ")() + Some("hello")()) == Some("lol hello")) + 'singleNone - assert(Opt("lol " + None()) == None) + 'twoNones - assert(Opt("lol " + None() + None()) == None) + } + 'context - { + assert(Opt(Opt.ctx() + Some("World")()) == Some("hellooooWorld")) + } + 'capturing - { + val lol = "lol " + def hell(o: String) = "hell" + o + 'simple - assert(Opt(lol + 1) == Some("lol 1")) + 'singleSome - assert(Opt(lol + Some(hell("o"))()) == Some("lol hello")) + 'twoSomes - assert(Opt(Some(lol)() + Some(hell("o"))()) == Some("lol hello")) + 'singleNone - assert(Opt(lol + None()) == None) + 'twoNones - assert(Opt(lol + None() + None()) == None) + } + 'allowedLocalDef - { + // Although x is defined inside the Opt{...} block, it is also defined + // within the LHS of the Applyable#apply call, so it is safe to life it + // out into the `zipMap` arguments list. + val res = Opt{ "lol " + Some("hello").flatMap(x => Some(x)).apply() } + assert(res == Some("lol hello")) + } + 'upstreamAlwaysEvaluated - { + // Whether or not control-flow reaches the Applyable#apply call inside an + // Opt{...} block, we always evaluate the LHS of the Applyable#apply + // because it gets lifted out of any control flow statements + val counter = new Counter() + def up = Opt{ "lol " + counter() } + val down = Opt{ if ("lol".length > 10) up() else "fail" } + assert( + down == Some("fail"), + counter.value == 1 + ) + } + 'upstreamEvaluatedOnlyOnce - { + // Even if control-flow reaches the Applyable#apply call more than once, + // it only gets evaluated once due to its lifting out of the Opt{...} block + val counter = new Counter() + def up = Opt{ "lol " + counter() } + def runTwice[T](t: => T) = (t, t) + val down = Opt{ runTwice(up()) } + assert( + down == Some(("lol 1", "lol 1")), + counter.value == 1 + ) + } + 'evaluationsInsideLambdasWork - { + // This required some fiddling with owner chains inside the macro to get + // working, so ensure it doesn't regress + val counter = new Counter() + def up = Opt{ "hello" + counter() } + val down1 = Opt{ (() => up())() } + val down2 = Opt{ Seq(1, 2, 3).map(n => up() * n) } + assert( + down1 == Some("hello1"), + down2 == Some(Seq("hello2", "hello2hello2", "hello2hello2hello2")) + ) + } + 'appliesEvaluatedOncePerLexicalCallsite - { + // If you have multiple Applyable#apply() lexically in the source code of + // your Opt{...} call, each one gets evaluated once, even if the LHS of each + // apply() call is identical. It's up to the downstream zipMap() + // implementation to decide if it wants to dedup them or do other things. + val counter = new Counter() + def up = Opt{ "hello" + counter() } + val down = Opt{ Seq(1, 2, 3).map(n => n + up() + up()) } + assert(down == Some(Seq("1hello1hello2", "2hello1hello2", "3hello1hello2"))) + } + 'appliesEvaluateBeforehand - { + // Every Applyable#apply() within a Opt{...} block evaluates before any + // other logic within that block, even if they would happen first in the + // normal Scala evaluation order + val counter = new Counter() + def up = Opt{ counter() } + val down = Opt{ + val res = counter() + val one = up() + val two = up() + val three = up() + (res, one, two, three) + } + assert(down == Some((4, 1, 2, 3))) + } + } +} diff --git a/main/test/src/define/BasePathTests.scala b/main/test/src/define/BasePathTests.scala new file mode 100644 index 00000000..b8a653c8 --- /dev/null +++ b/main/test/src/define/BasePathTests.scala @@ -0,0 +1,73 @@ +package mill.define + +import mill.util.{TestGraphs, TestUtil} +import utest._ +import mill.{Module, T} +object BasePathTests extends TestSuite{ + val testGraphs = new TestGraphs + val tests = Tests{ + def check[T <: Module](m: T)(f: T => Module, segments: String*) = { + val remaining = f(m).millSourcePath.relativeTo(m.millSourcePath).segments + assert(remaining == segments) + } + 'singleton - { + check(testGraphs.singleton)(identity) + } + 'backtickIdentifiers - { + check(testGraphs.bactickIdentifiers)( + _.`nested-module`, + "nested-module" + ) + } + 'separateGroups - { + check(TestGraphs.triangleTask)(identity) + } + 'TraitWithModuleObject - { + check(TestGraphs.TraitWithModuleObject)( + _.TraitModule, + "TraitModule" + ) + } + 'nestedModuleNested - { + check(TestGraphs.nestedModule)(_.nested, "nested") + } + 'nestedModuleInstance - { + check(TestGraphs.nestedModule)(_.classInstance, "classInstance") + } + 'singleCross - { + check(TestGraphs.singleCross)(_.cross, "cross") + check(TestGraphs.singleCross)(_.cross("210"), "cross", "210") + check(TestGraphs.singleCross)(_.cross("211"), "cross", "211") + } + 'doubleCross - { + check(TestGraphs.doubleCross)(_.cross, "cross") + check(TestGraphs.doubleCross)(_.cross("210", "jvm"), "cross", "210", "jvm") + check(TestGraphs.doubleCross)(_.cross("212", "js"), "cross", "212", "js") + } + 'nestedCrosses - { + check(TestGraphs.nestedCrosses)(_.cross, "cross") + check(TestGraphs.nestedCrosses)( + _.cross("210").cross2("js"), + "cross", "210", "cross2", "js" + ) + } + 'overriden - { + object overridenBasePath extends TestUtil.BaseModule { + override def millSourcePath = os.pwd / 'overridenBasePathRootValue + object nested extends Module{ + override def millSourcePath = super.millSourcePath / 'overridenBasePathNested + object nested extends Module{ + override def millSourcePath = super.millSourcePath / 'overridenBasePathDoubleNested + } + } + } + assert( + overridenBasePath.millSourcePath == os.pwd / 'overridenBasePathRootValue, + overridenBasePath.nested.millSourcePath == os.pwd / 'overridenBasePathRootValue / 'nested / 'overridenBasePathNested, + overridenBasePath.nested.nested.millSourcePath == os.pwd / 'overridenBasePathRootValue / 'nested / 'overridenBasePathNested / 'nested / 'overridenBasePathDoubleNested + ) + } + + } +} + diff --git a/main/test/src/define/CacherTests.scala b/main/test/src/define/CacherTests.scala new file mode 100644 index 00000000..59ebf3f6 --- /dev/null +++ b/main/test/src/define/CacherTests.scala @@ -0,0 +1,75 @@ +package mill.define + +import mill.util.{DummyLogger, TestEvaluator, TestUtil} +import mill.util.Strict.Agg +import mill.T +import mill.api.Result.Success +import utest._ +import utest.framework.TestPath + + +object CacherTests extends TestSuite{ + object Base extends Base + trait Base extends TestUtil.BaseModule{ + def value = T{ 1 } + def result = T{ Success(1) } + } + object Middle extends Middle + trait Middle extends Base{ + override def value = T{ super.value() + 2} + def overriden = T{ super.value()} + } + object Terminal extends Terminal + trait Terminal extends Middle{ + override def value = T{ super.value() + 4} + } + + val tests = Tests{ + def eval[T <: TestUtil.BaseModule, V](mapping: T, v: Task[V]) + (implicit tp: TestPath) = { + val evaluator = new TestEvaluator(mapping) + evaluator(v).right.get._1 + } + def check(x: Any, y: Any) = assert(x == y) + + 'simpleDefIsCached - { + Predef.assert(Base.value eq Base.value) + Predef.assert(eval(Base, Base.value) == 1) + } + + 'resultDefIsCached - { + Predef.assert(Base.result eq Base.result) + Predef.assert(eval(Base, Base.result) == 1) + } + + + 'overridingDefIsAlsoCached - { + Predef.assert(eval(Middle, Middle.value) == 3) + Predef.assert(Middle.value eq Middle.value) + } + + 'overridenDefRemainsAvailable - { + Predef.assert(eval(Middle, Middle.overriden) == 1) + } + + + 'multipleOverridesWork- { + Predef.assert(eval(Terminal, Terminal.value) == 7) + Predef.assert(eval(Terminal, Terminal.overriden) == 1) + } + // Doesn't fail, presumably compileError doesn't go far enough in the + // compilation pipeline to hit the override checks + // + // 'overrideOutsideModuleFails - { + // compileError(""" + // trait Foo{ + // def x = 1 + // } + // object Bar extends Foo{ + // def x = 2 + // } + // """) + // } + } +} + diff --git a/main/test/src/define/DiscoverTests.scala b/main/test/src/define/DiscoverTests.scala new file mode 100644 index 00000000..248d6afe --- /dev/null +++ b/main/test/src/define/DiscoverTests.scala @@ -0,0 +1,63 @@ +package mill.define + +import mill.util.TestGraphs +import utest._ + +object DiscoverTests extends TestSuite{ + val testGraphs = new TestGraphs + val tests = Tests{ + def check[T <: Module](m: T)(targets: (T => Target[_])*) = { + val discovered = m.millInternal.targets + val expected = targets.map(_(m)).toSet + assert(discovered == expected) + } + 'singleton - { + check(testGraphs.singleton)(_.single) + } + 'backtickIdentifiers { + check(testGraphs.bactickIdentifiers)(_.`up-target`, _.`a-down-target`, _.`nested-module`.`nested-target`) + } + 'separateGroups - { + check(TestGraphs.triangleTask)(_.left, _.right) + } + 'TraitWithModuleObject - { + check(TestGraphs.TraitWithModuleObject)(_.TraitModule.testFrameworks) + } + 'nestedModule - { + check(TestGraphs.nestedModule)(_.single, _.nested.single, _.classInstance.single) + } + 'singleCross - { + check(TestGraphs.singleCross)( + _.cross("210").suffix, + _.cross("211").suffix, + _.cross("212").suffix + ) + } + 'doubleCross - { + check(TestGraphs.doubleCross)( + _.cross("210", "jvm").suffix, + _.cross("210", "js").suffix, + _.cross("211", "jvm").suffix, + _.cross("211", "js").suffix, + _.cross("212", "jvm").suffix, + _.cross("212", "js").suffix, + _.cross("212", "native").suffix + ) + } + 'nestedCrosses - { + check(TestGraphs.nestedCrosses)( + _.cross("210").cross2("jvm").suffix, + _.cross("210").cross2("js").suffix, + _.cross("210").cross2("native").suffix, + _.cross("211").cross2("jvm").suffix, + _.cross("211").cross2("js").suffix, + _.cross("211").cross2("native").suffix, + _.cross("212").cross2("jvm").suffix, + _.cross("212").cross2("js").suffix, + _.cross("212").cross2("native").suffix + ) + } + + } +} + diff --git a/main/test/src/define/GraphTests.scala b/main/test/src/define/GraphTests.scala new file mode 100644 index 00000000..224ce59f --- /dev/null +++ b/main/test/src/define/GraphTests.scala @@ -0,0 +1,211 @@ +package mill.define + + +import mill.eval.Evaluator +import mill.util.{TestGraphs, TestUtil} +import utest._ +import mill.util.Strict.Agg +object GraphTests extends TestSuite{ + + val tests = Tests{ + + + val graphs = new TestGraphs() + import graphs._ + import TestGraphs._ + + 'topoSortedTransitiveTargets - { + def check(targets: Agg[Task[_]], expected: Agg[Task[_]]) = { + val result = Graph.topoSorted(Graph.transitiveTargets(targets)).values + TestUtil.checkTopological(result) + assert(result == expected) + } + + 'singleton - check( + targets = Agg(singleton.single), + expected = Agg(singleton.single) + ) + 'backtickIdentifiers - check( + targets = Agg(bactickIdentifiers.`a-down-target`), + expected = Agg(bactickIdentifiers.`up-target`, bactickIdentifiers.`a-down-target`) + ) + 'pair - check( + targets = Agg(pair.down), + expected = Agg(pair.up, pair.down) + ) + 'anonTriple - check( + targets = Agg(anonTriple.down), + expected = Agg(anonTriple.up, anonTriple.down.inputs(0), anonTriple.down) + ) + 'diamond - check( + targets = Agg(diamond.down), + expected = Agg(diamond.up, diamond.left, diamond.right, diamond.down) + ) + 'anonDiamond - check( + targets = Agg(diamond.down), + expected = Agg( + diamond.up, + diamond.down.inputs(0), + diamond.down.inputs(1), + diamond.down + ) + ) + 'defCachedDiamond - check( + targets = Agg(defCachedDiamond.down), + expected = Agg( + defCachedDiamond.up.inputs(0), + defCachedDiamond.up, + defCachedDiamond.down.inputs(0).inputs(0).inputs(0), + defCachedDiamond.down.inputs(0).inputs(0), + defCachedDiamond.down.inputs(0).inputs(1).inputs(0), + defCachedDiamond.down.inputs(0).inputs(1), + defCachedDiamond.down.inputs(0), + defCachedDiamond.down + ) + ) + 'bigSingleTerminal - { + val result = Graph.topoSorted(Graph.transitiveTargets(Agg(bigSingleTerminal.j))).values + TestUtil.checkTopological(result) + assert(result.size == 28) + } + } + + 'groupAroundNamedTargets - { + def check[T, R <: Target[Int]](base: T) + (target: T => R, + important0: Agg[T => Target[_]], + expected: Agg[(R, Int)]) = { + + val topoSorted = Graph.topoSorted(Graph.transitiveTargets(Agg(target(base)))) + + val important = important0.map(_ (base)) + val grouped = Graph.groupAroundImportantTargets(topoSorted) { + case t: Target[_] if important.contains(t) => t + } + val flattened = Agg.from(grouped.values().flatMap(_.items)) + + TestUtil.checkTopological(flattened) + for ((terminal, expectedSize) <- expected) { + val grouping = grouped.lookupKey(terminal) + assert( + grouping.size == expectedSize, + grouping.flatMap(_.asTarget: Option[Target[_]]).filter(important.contains) == Agg(terminal) + ) + } + } + + 'singleton - check(singleton)( + _.single, + Agg(_.single), + Agg(singleton.single -> 1) + ) + 'backtickIdentifiers - check(bactickIdentifiers)( + _.`a-down-target`, + Agg(_.`up-target`, _.`a-down-target`), + Agg( + bactickIdentifiers.`up-target` -> 1, + bactickIdentifiers.`a-down-target` -> 1 + ) + ) + 'pair - check(pair)( + _.down, + Agg(_.up, _.down), + Agg(pair.up -> 1, pair.down -> 1) + ) + 'anonTriple - check(anonTriple)( + _.down, + Agg(_.up, _.down), + Agg(anonTriple.up -> 1, anonTriple.down -> 2) + ) + 'diamond - check(diamond)( + _.down, + Agg(_.up, _.left, _.right, _.down), + Agg( + diamond.up -> 1, + diamond.left -> 1, + diamond.right -> 1, + diamond.down -> 1 + ) + ) + + 'defCachedDiamond - check(defCachedDiamond)( + _.down, + Agg(_.up, _.left, _.right, _.down), + Agg( + defCachedDiamond.up -> 2, + defCachedDiamond.left -> 2, + defCachedDiamond.right -> 2, + defCachedDiamond.down -> 2 + ) + ) + + 'anonDiamond - check(anonDiamond)( + _.down, + Agg(_.down, _.up), + Agg( + anonDiamond.up -> 1, + anonDiamond.down -> 3 + ) + ) + 'bigSingleTerminal - check(bigSingleTerminal)( + _.j, + Agg(_.a, _.b, _.e, _.f, _.i, _.j), + Agg( + bigSingleTerminal.a -> 3, + bigSingleTerminal.b -> 2, + bigSingleTerminal.e -> 9, + bigSingleTerminal.i -> 6, + bigSingleTerminal.f -> 4, + bigSingleTerminal.j -> 4 + ) + ) + } + 'multiTerminalGroupCounts - { + def countGroups(goals: Task[_]*) = { + + val topoSorted = Graph.topoSorted( + Graph.transitiveTargets(Agg.from(goals)) + ) + val grouped = Graph.groupAroundImportantTargets(topoSorted) { + case t: NamedTask[Any] => t + case t if goals.contains(t) => t + } + grouped.keyCount + } + + 'separateGroups - { + import separateGroups._ + val groupCount = countGroups(right, left) + assert(groupCount == 3) + } + + 'triangleTask - { + // Make sure the following graph ends up as a single group, since although + // `right` depends on `left`, both of them depend on the un-cached `task` + // which would force them both to re-compute every time `task` changes + import triangleTask._ + val groupCount = countGroups(right, left) + assert(groupCount == 2) + } + + + 'multiTerminalGroup - { + // Make sure the following graph ends up as two groups + import multiTerminalGroup._ + val groupCount = countGroups(right, left) + assert(groupCount == 2) + } + + + 'multiTerminalBoundary - { + // Make sure the following graph ends up as a three groups: one for + // each cached target, and one for the downstream task we are running + import multiTerminalBoundary._ + val groupCount = countGroups(task2) + assert(groupCount == 3) + } + } + + + } +} diff --git a/main/test/src/define/MacroErrorTests.scala b/main/test/src/define/MacroErrorTests.scala new file mode 100644 index 00000000..c8b140fa --- /dev/null +++ b/main/test/src/define/MacroErrorTests.scala @@ -0,0 +1,145 @@ +package mill.define + +import utest._ +import mill.{T, Module} +import mill.util.TestUtil +object MacroErrorTests extends TestSuite{ + + val tests = Tests{ + + 'errors{ + val expectedMsg = + "T{} members must be defs defined in a Cacher class/trait/object body" + + val err = compileError("object Foo extends TestUtil.BaseModule{ val x = T{1} }") + assert(err.msg == expectedMsg) + } + + 'badParameterSets - { + 'command - { + val e = compileError(""" + object foo extends mill.util.TestUtil.BaseModule{ + def w = T.command{1} + } + mill.define.Discover[foo.type] + """) + assert( + e.msg.contains("`T.command` definitions must have 1 parameter list"), + e.pos.contains("def w = ") + ) + } + 'target - { + val e = compileError(""" + object foo extends mill.util.TestUtil.BaseModule{ + def x() = T{1} + } + mill.define.Discover[foo.type] + """) + assert( + e.msg.contains("`T{...}` definitions must have 0 parameter lists"), + e.pos.contains("def x() = ") + ) + } + 'input - { + val e = compileError(""" + object foo extends mill.util.TestUtil.BaseModule{ + def y() = T.input{1} + } + mill.define.Discover[foo.type] + """) + assert( + e.msg.contains("`T.input` definitions must have 0 parameter lists"), + e.pos.contains("def y() = ") + ) + } + 'sources - { + val e = compileError(""" + object foo extends mill.util.TestUtil.BaseModule{ + def z() = T.sources{ammonite.ops.pwd} + } + mill.define.Discover[foo.type] + """) + assert( + e.msg.contains("`T.sources` definitions must have 0 parameter lists"), + e.pos.contains("def z() = ") + ) + } + 'persistent - { + val e = compileError(""" + object foo extends mill.util.TestUtil.BaseModule{ + def a() = T.persistent{1} + } + mill.define.Discover[foo.type] + """) + assert( + e.msg.contains("`T.persistent` definitions must have 0 parameter lists"), + e.pos.contains("def a() = ") + ) + } + } + 'badTmacro - { + // Make sure we can reference values from outside the T{...} block as part + // of our `Target#apply()` calls, but we cannot reference any values that + // come from inside the T{...} block + 'pos - { + val e = compileError(""" + val a = T{ 1 } + val arr = Array(a) + val b = { + val c = 0 + T{ + arr(c)() + } + } + """) + assert(e.msg.contains( + "Modules, Targets and Commands can only be defined within a mill Module") + ) + } + 'neg - { + + val expectedMsg = + "Target#apply() call cannot use `value n` defined within the T{...} block" + val err = compileError("""new Module{ + def a = T{ 1 } + val arr = Array(a) + def b = { + T{ + val n = 0 + arr(n)() + } + } + }""") + assert(err.msg == expectedMsg) + } + 'neg2 - { + + val expectedMsg = + "Target#apply() call cannot use `value x` defined within the T{...} block" + val err = compileError("""new Module{ + def a = T{ 1 } + val arr = Array(a) + def b = { + T{ + arr.map{x => x()} + } + } + }""") + assert(err.msg == expectedMsg) + } + 'neg3{ + val borkedCachedDiamond1 = utest.compileError(""" + object borkedCachedDiamond1 { + def up = T{ TestUtil.test() } + def left = T{ TestUtil.test(up) } + def right = T{ TestUtil.test(up) } + def down = T{ TestUtil.test(left, right) } + } + """) + assert(borkedCachedDiamond1.msg.contains( + "Modules, Targets and Commands can only be defined within a mill Module") + ) + } + } + } +} diff --git a/main/test/src/eval/CrossTests.scala b/main/test/src/eval/CrossTests.scala new file mode 100644 index 00000000..f194924e --- /dev/null +++ b/main/test/src/eval/CrossTests.scala @@ -0,0 +1,56 @@ +package mill.eval + + +import mill.define.Discover +import mill.util.TestEvaluator + +import mill.util.TestGraphs.{crossResolved, doubleCross, nestedCrosses, singleCross} +import utest._ +object CrossTests extends TestSuite{ + val tests = Tests{ + 'singleCross - { + val check = new TestEvaluator(singleCross) + + val Right(("210", 1)) = check.apply(singleCross.cross("210").suffix) + val Right(("211", 1)) = check.apply(singleCross.cross("211").suffix) + val Right(("212", 1)) = check.apply(singleCross.cross("212").suffix) + } + + 'crossResolved - { + val check = new TestEvaluator(crossResolved) + + val Right(("2.10", 1)) = check.apply(crossResolved.foo("2.10").suffix) + val Right(("2.11", 1)) = check.apply(crossResolved.foo("2.11").suffix) + val Right(("2.12", 1)) = check.apply(crossResolved.foo("2.12").suffix) + + val Right(("_2.10", 1)) = check.apply(crossResolved.bar("2.10").longSuffix) + val Right(("_2.11", 1)) = check.apply(crossResolved.bar("2.11").longSuffix) + val Right(("_2.12", 1)) = check.apply(crossResolved.bar("2.12").longSuffix) + } + + + 'doubleCross - { + val check = new TestEvaluator(doubleCross) + + val Right(("210_jvm", 1)) = check.apply(doubleCross.cross("210", "jvm").suffix) + val Right(("210_js", 1)) = check.apply(doubleCross.cross("210", "js").suffix) + val Right(("211_jvm", 1)) = check.apply(doubleCross.cross("211", "jvm").suffix) + val Right(("211_js", 1)) = check.apply(doubleCross.cross("211", "js").suffix) + val Right(("212_jvm", 1)) = check.apply(doubleCross.cross("212", "jvm").suffix) + val Right(("212_js", 1)) = check.apply(doubleCross.cross("212", "js").suffix) + val Right(("212_native", 1)) = check.apply(doubleCross.cross("212", "native").suffix) + } + + 'nestedCrosses - { + val check = new TestEvaluator(nestedCrosses) + + val Right(("210_jvm", 1)) = check.apply(nestedCrosses.cross("210").cross2("jvm").suffix) + val Right(("210_js", 1)) = check.apply(nestedCrosses.cross("210").cross2("js").suffix) + val Right(("211_jvm", 1)) = check.apply(nestedCrosses.cross("211").cross2("jvm").suffix) + val Right(("211_js", 1)) = check.apply(nestedCrosses.cross("211").cross2("js").suffix) + val Right(("212_jvm", 1)) = check.apply(nestedCrosses.cross("212").cross2("jvm").suffix) + val Right(("212_js", 1)) = check.apply(nestedCrosses.cross("212").cross2("js").suffix) + val Right(("212_native", 1)) = check.apply(nestedCrosses.cross("212").cross2("native").suffix) + } + } +} diff --git a/main/test/src/eval/EvaluationTests.scala b/main/test/src/eval/EvaluationTests.scala new file mode 100644 index 00000000..74f9088c --- /dev/null +++ b/main/test/src/eval/EvaluationTests.scala @@ -0,0 +1,354 @@ +package mill.eval + + +import mill.util.TestUtil.{Test, test} +import mill.define.{Discover, Graph, Target, Task} +import mill.{Module, T} +import mill.util.{DummyLogger, TestEvaluator, TestGraphs, TestUtil} +import mill.util.Strict.Agg +import utest._ +import utest.framework.TestPath + + + +object EvaluationTests extends TestSuite{ + class Checker[T <: TestUtil.BaseModule](module: T)(implicit tp: TestPath) { + // Make sure data is persisted even if we re-create the evaluator each time + + def evaluator = new TestEvaluator(module).evaluator + + def apply(target: Task[_], expValue: Any, + expEvaled: Agg[Task[_]], + // How many "other" tasks were evaluated other than those listed above. + // Pass in -1 to skip the check entirely + extraEvaled: Int = 0, + // Perform a second evaluation of the same tasks, and make sure the + // outputs are the same but nothing was evaluated. Disable this if you + // are directly evaluating tasks which need to re-evaluate every time + secondRunNoOp: Boolean = true) = { + + val evaled = evaluator.evaluate(Agg(target)) + + val (matchingReturnedEvaled, extra) = + evaled.evaluated.indexed.partition(expEvaled.contains) + + assert( + evaled.values == Seq(expValue), + matchingReturnedEvaled.toSet == expEvaled.toSet, + extraEvaled == -1 || extra.length == extraEvaled + ) + + // Second time the value is already cached, so no evaluation needed + if (secondRunNoOp){ + val evaled2 = evaluator.evaluate(Agg(target)) + val expecteSecondRunEvaluated = Agg() + assert( + evaled2.values == evaled.values, + evaled2.evaluated == expecteSecondRunEvaluated + ) + } + } + } + + + val tests = Tests{ + object graphs extends TestGraphs() + import graphs._ + import TestGraphs._ + 'evaluateSingle - { + + 'singleton - { + import singleton._ + val check = new Checker(singleton) + // First time the target is evaluated + check(single, expValue = 0, expEvaled = Agg(single)) + + single.counter += 1 + // After incrementing the counter, it forces re-evaluation + check(single, expValue = 1, expEvaled = Agg(single)) + } + 'backtickIdentifiers - { + import graphs.bactickIdentifiers._ + val check = new Checker(bactickIdentifiers) + + check(`a-down-target`, expValue = 0, expEvaled = Agg(`up-target`, `a-down-target`)) + + `a-down-target`.counter += 1 + check(`a-down-target`, expValue = 1, expEvaled = Agg(`a-down-target`)) + + `up-target`.counter += 1 + check(`a-down-target`, expValue = 2, expEvaled = Agg(`up-target`, `a-down-target`)) + } + 'pair - { + import pair._ + val check = new Checker(pair) + check(down, expValue = 0, expEvaled = Agg(up, down)) + + down.counter += 1 + check(down, expValue = 1, expEvaled = Agg(down)) + + up.counter += 1 + check(down, expValue = 2, expEvaled = Agg(up, down)) + } + 'anonTriple - { + import anonTriple._ + val check = new Checker(anonTriple) + val middle = down.inputs(0) + check(down, expValue = 0, expEvaled = Agg(up, middle, down)) + + down.counter += 1 + check(down, expValue = 1, expEvaled = Agg(middle, down)) + + up.counter += 1 + check(down, expValue = 2, expEvaled = Agg(up, middle, down)) + + middle.asInstanceOf[TestUtil.Test].counter += 1 + + check(down, expValue = 3, expEvaled = Agg(middle, down)) + } + 'diamond - { + import diamond._ + val check = new Checker(diamond) + check(down, expValue = 0, expEvaled = Agg(up, left, right, down)) + + down.counter += 1 + check(down, expValue = 1, expEvaled = Agg(down)) + + up.counter += 1 + // Increment by 2 because up is referenced twice: once by left once by right + check(down, expValue = 3, expEvaled = Agg(up, left, right, down)) + + left.counter += 1 + check(down, expValue = 4, expEvaled = Agg(left, down)) + + right.counter += 1 + check(down, expValue = 5, expEvaled = Agg(right, down)) + } + 'anonDiamond - { + import anonDiamond._ + val check = new Checker(anonDiamond) + val left = down.inputs(0).asInstanceOf[TestUtil.Test] + val right = down.inputs(1).asInstanceOf[TestUtil.Test] + check(down, expValue = 0, expEvaled = Agg(up, left, right, down)) + + down.counter += 1 + check(down, expValue = 1, expEvaled = Agg(left, right, down)) + + up.counter += 1 + // Increment by 2 because up is referenced twice: once by left once by right + check(down, expValue = 3, expEvaled = Agg(up, left, right, down)) + + left.counter += 1 + check(down, expValue = 4, expEvaled = Agg(left, right, down)) + + right.counter += 1 + check(down, expValue = 5, expEvaled = Agg(left, right, down)) + } + + 'bigSingleTerminal - { + import bigSingleTerminal._ + val check = new Checker(bigSingleTerminal) + + check(j, expValue = 0, expEvaled = Agg(a, b, e, f, i, j), extraEvaled = 22) + + j.counter += 1 + check(j, expValue = 1, expEvaled = Agg(j), extraEvaled = 3) + + i.counter += 1 + // increment value by 2 because `i` is used twice on the way to `j` + check(j, expValue = 3, expEvaled = Agg(j, i), extraEvaled = 8) + + b.counter += 1 + // increment value by 4 because `b` is used four times on the way to `j` + check(j, expValue = 7, expEvaled = Agg(b, e, f, i, j), extraEvaled = 20) + } + } + + 'evaluateMixed - { + 'separateGroups - { + // Make sure that `left` and `right` are able to recompute separately, + // even though one depends on the other + + import separateGroups._ + val checker = new Checker(separateGroups) + val evaled1 = checker.evaluator.evaluate(Agg(right, left)) + val filtered1 = evaled1.evaluated.filter(_.isInstanceOf[Target[_]]) + assert(filtered1 == Agg(change, left, right)) + val evaled2 = checker.evaluator.evaluate(Agg(right, left)) + val filtered2 = evaled2.evaluated.filter(_.isInstanceOf[Target[_]]) + assert(filtered2 == Agg()) + change.counter += 1 + val evaled3 = checker.evaluator.evaluate(Agg(right, left)) + val filtered3 = evaled3.evaluated.filter(_.isInstanceOf[Target[_]]) + assert(filtered3 == Agg(change, right)) + + + } + 'triangleTask - { + + import triangleTask._ + val checker = new Checker(triangleTask) + checker(right, 3, Agg(left, right), extraEvaled = -1) + checker(left, 1, Agg(), extraEvaled = -1) + + } + 'multiTerminalGroup - { + import multiTerminalGroup._ + + val checker = new Checker(multiTerminalGroup) + checker(right, 1, Agg(right), extraEvaled = -1) + checker(left, 1, Agg(left), extraEvaled = -1) + } + + 'multiTerminalBoundary - { + + import multiTerminalBoundary._ + + val checker = new Checker(multiTerminalBoundary) + checker(task2, 4, Agg(right, left), extraEvaled = -1, secondRunNoOp = false) + checker(task2, 4, Agg(), extraEvaled = -1, secondRunNoOp = false) + } + + 'overrideSuperTask - { + // Make sure you can override targets, call their supers, and have the + // overriden target be allocated a spot within the overriden/ folder of + // the main publically-available target + import canOverrideSuper._ + + val checker = new Checker(canOverrideSuper) + checker(foo, Seq("base", "object"), Agg(foo), extraEvaled = -1) + + + val public = ammonite.ops.read(checker.evaluator.outPath / 'foo / "meta.json") + val overriden = ammonite.ops.read( + checker.evaluator.outPath / 'foo / + 'overriden / "mill" / "util" / "TestGraphs" / "BaseModule" / "foo" / "meta.json" + ) + assert( + public.contains("base"), + public.contains("object"), + overriden.contains("base"), + !overriden.contains("object") + ) + } + 'overrideSuperCommand - { + // Make sure you can override commands, call their supers, and have the + // overriden command be allocated a spot within the overriden/ folder of + // the main publically-available command + import canOverrideSuper._ + + val checker = new Checker(canOverrideSuper) + val runCmd = cmd(1) + checker( + runCmd, + Seq("base1", "object1"), + Agg(runCmd), + extraEvaled = -1, + secondRunNoOp = false + ) + + val public = ammonite.ops.read(checker.evaluator.outPath / 'cmd / "meta.json") + val overriden = ammonite.ops.read( + checker.evaluator.outPath / 'cmd / + 'overriden / "mill" / "util" / "TestGraphs" / "BaseModule"/ "cmd" / "meta.json" + ) + assert( + public.contains("base1"), + public.contains("object1"), + overriden.contains("base1"), + !overriden.contains("object1") + ) + } + 'nullTasks - { + import nullTasks._ + val checker = new Checker(nullTasks) + checker(nullTarget1, null, Agg(nullTarget1), extraEvaled = -1) + checker(nullTarget1, null, Agg(), extraEvaled = -1) + checker(nullTarget2, null, Agg(nullTarget2), extraEvaled = -1) + checker(nullTarget2, null, Agg(), extraEvaled = -1) + checker(nullTarget3, null, Agg(nullTarget3), extraEvaled = -1) + checker(nullTarget3, null, Agg(), extraEvaled = -1) + checker(nullTarget4, null, Agg(nullTarget4), extraEvaled = -1) + checker(nullTarget4, null, Agg(), extraEvaled = -1) + + val nc1 = nullCommand1() + val nc2 = nullCommand2() + val nc3 = nullCommand3() + val nc4 = nullCommand4() + + checker(nc1, null, Agg(nc1), extraEvaled = -1, secondRunNoOp = false) + checker(nc1, null, Agg(nc1), extraEvaled = -1, secondRunNoOp = false) + checker(nc2, null, Agg(nc2), extraEvaled = -1, secondRunNoOp = false) + checker(nc2, null, Agg(nc2), extraEvaled = -1, secondRunNoOp = false) + checker(nc3, null, Agg(nc3), extraEvaled = -1, secondRunNoOp = false) + checker(nc3, null, Agg(nc3), extraEvaled = -1, secondRunNoOp = false) + checker(nc4, null, Agg(nc4), extraEvaled = -1, secondRunNoOp = false) + checker(nc4, null, Agg(nc4), extraEvaled = -1, secondRunNoOp = false) + } + + 'tasksAreUncached - { + // Make sure the tasks `left` and `middle` re-compute every time, while + // the target `right` does not + // + // ___ left ___ + // / \ + // up middle -- down + // / + // right + object build extends TestUtil.BaseModule{ + var leftCount = 0 + var rightCount = 0 + var middleCount = 0 + def up = T{ test.anon() } + 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 build._ + + // 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(build) + assert(leftCount == 0, rightCount == 0) + check(down, expValue = 10101, expEvaled = Agg(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 = Agg()) + 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 = Agg(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 = Agg(), extraEvaled = 1, secondRunNoOp = false) + assert(leftCount == 3, middleCount == 2, rightCount == 1) + check(left, expValue = 2, expEvaled = Agg(), extraEvaled = 1, secondRunNoOp = false) + assert(leftCount == 4, middleCount == 2, rightCount == 1) + + check(middle, expValue = 100, expEvaled = Agg(), extraEvaled = 2, secondRunNoOp = false) + assert(leftCount == 4, middleCount == 3, rightCount == 1) + check(middle, expValue = 100, expEvaled = Agg(), extraEvaled = 2, secondRunNoOp = false) + assert(leftCount == 4, middleCount == 4, rightCount == 1) + } + } + } +} diff --git a/main/test/src/eval/FailureTests.scala b/main/test/src/eval/FailureTests.scala new file mode 100644 index 00000000..dcfbcb60 --- /dev/null +++ b/main/test/src/eval/FailureTests.scala @@ -0,0 +1,132 @@ +package mill.eval +import mill.T +import mill.util.{TestEvaluator, TestUtil} +import mill.api.Result.OuterStack +import utest._ +import utest.framework.TestPath + + +object FailureTests extends TestSuite{ + + val tests = Tests{ + val graphs = new mill.util.TestGraphs() + import graphs._ + + 'evaluateSingle - { + val check = new TestEvaluator(singleton) + check.fail( + target = singleton.single, + expectedFailCount = 0, + expectedRawValues = Seq(Result.Success(0)) + ) + + singleton.single.failure = Some("lols") + + check.fail( + target = singleton.single, + expectedFailCount = 1, + expectedRawValues = Seq(Result.Failure("lols")) + ) + + singleton.single.failure = None + + check.fail( + target = singleton.single, + expectedFailCount = 0, + expectedRawValues = Seq(Result.Success(0)) + ) + + + val ex = new IndexOutOfBoundsException() + singleton.single.exception = Some(ex) + + + check.fail( + target = singleton.single, + expectedFailCount = 1, + expectedRawValues = Seq(Result.Exception(ex, new OuterStack(Nil))) + ) + } + 'evaluatePair - { + val check = new TestEvaluator(pair) + check.fail( + pair.down, + expectedFailCount = 0, + expectedRawValues = Seq(Result.Success(0)) + ) + + pair.up.failure = Some("lols") + + check.fail( + pair.down, + expectedFailCount = 1, + expectedRawValues = Seq(Result.Skipped) + ) + + pair.up.failure = None + + check.fail( + pair.down, + expectedFailCount = 0, + expectedRawValues = Seq(Result.Success(0)) + ) + + pair.up.exception = Some(new IndexOutOfBoundsException()) + + check.fail( + pair.down, + expectedFailCount = 1, + expectedRawValues = Seq(Result.Skipped) + ) + } + 'evaluateBacktickIdentifiers - { + val check = new TestEvaluator(bactickIdentifiers) + import bactickIdentifiers._ + check.fail( + `a-down-target`, + expectedFailCount = 0, + expectedRawValues = Seq(Result.Success(0)) + ) + + `up-target`.failure = Some("lols") + + check.fail( + `a-down-target`, + expectedFailCount = 1, + expectedRawValues = Seq(Result.Skipped) + ) + + `up-target`.failure = None + + check.fail( + `a-down-target`, + expectedFailCount = 0, + expectedRawValues = Seq(Result.Success(0)) + ) + + `up-target`.exception = Some(new IndexOutOfBoundsException()) + + check.fail( + `a-down-target`, + expectedFailCount = 1, + expectedRawValues = Seq(Result.Skipped) + ) + } + 'multipleUsesOfDest - { + object build extends TestUtil.BaseModule { + // Using `T.ctx( ).dest` twice in a single task is ok + def left = T{ + T.ctx().dest.toString.length + T.ctx().dest.toString.length } + + // Using `T.ctx( ).dest` once in two different tasks is not ok + val task = T.task{ T.ctx().dest.toString.length } + def right = T{ task() + left() + T.ctx().dest.toString().length } + } + + val check = new TestEvaluator(build) + val Right(_) = check(build.left) + val Left(Result.Exception(e, _)) = check(build.right) + assert(e.getMessage.contains("`dest` can only be used in one place")) + } + } +} + diff --git a/main/test/src/eval/JavaCompileJarTests.scala b/main/test/src/eval/JavaCompileJarTests.scala new file mode 100644 index 00000000..426c6ea6 --- /dev/null +++ b/main/test/src/eval/JavaCompileJarTests.scala @@ -0,0 +1,164 @@ +package mill.eval + +import mill.define.{Discover, Input, Target, Task} +import mill.modules.Jvm +import mill.api.Ctx.Dest +import mill.{Module, T} +import mill.util.{DummyLogger, Loose, TestEvaluator, TestUtil} +import mill.util.Strict.Agg +import utest._ +import mill._ +object JavaCompileJarTests extends TestSuite{ + def compileAll(sources: mill.util.Loose.Agg[PathRef])(implicit ctx: Dest) = { + os.makeDir.all(ctx.dest) + + os.proc("javac", sources.map(_.path.toString()).toSeq, "-d", ctx.dest).call(ctx.dest) + PathRef(ctx.dest) + } + + val tests = Tests{ + 'javac { + val javacSrcPath = os.pwd / 'main / 'test / 'resources / 'examples / 'javac + val javacDestPath = TestUtil.getOutPath() / 'src + + os.makeDir.all(javacDestPath / os.up) + os.copy(javacSrcPath, javacDestPath) + + object Build extends TestUtil.BaseModule{ + def sourceRootPath = javacDestPath / 'src + def resourceRootPath = javacDestPath / 'resources + + // sourceRoot -> allSources -> classFiles + // | + // v + // resourceRoot ----> jar + def sourceRoot = T.sources{ sourceRootPath } + def resourceRoot = T.sources{ resourceRootPath } + def allSources = T{ sourceRoot().flatMap(p => os.walk(p.path)).map(PathRef(_)) } + def classFiles = T{ compileAll(allSources()) } + def jar = T{ Jvm.createJar(Loose.Agg(classFiles().path) ++ resourceRoot().map(_.path)) } + // Test createJar() with optional file filter. + def filterJar(fileFilter: (os.Path, os.RelPath) => Boolean) = T{ Jvm.createJar(Loose.Agg(classFiles().path) ++ resourceRoot().map(_.path), None, fileFilter) } + + def run(mainClsName: String) = T.command{ + os.proc('java, "-Duser.language=en", "-cp", classFiles().path, mainClsName).call() + } + } + + import Build._ + + var evaluator = new TestEvaluator(Build) + def eval[T](t: Task[T]) = { + evaluator.apply(t) + } + def check(targets: Agg[Task[_]], expected: Agg[Task[_]]) = { + evaluator.check(targets, expected) + } + + def append(path: os.Path, txt: String) = ammonite.ops.write.append(path, txt) + + + check( + targets = Agg(jar), + expected = Agg(allSources, classFiles, jar) + ) + + // Re-running with no changes results in nothing being evaluated + check(targets = Agg(jar), expected = Agg()) + // Appending an empty string gets ignored due to file-content hashing + append(sourceRootPath / "Foo.java", "") + check(targets = Agg(jar), expected = Agg()) + + // Appending whitespace forces a recompile, but the classfilesend up + // exactly the same so no re-jarring. + append(sourceRootPath / "Foo.java", " ") + // Note that `sourceRoot` and `resourceRoot` never turn up in the `expected` + // list, because they are `Source`s not `Target`s + check(targets = Agg(jar), expected = Agg(/*sourceRoot, */allSources, classFiles)) + + // Appending a new class changes the classfiles, which forces us to + // re-create the final jar + append(sourceRootPath / "Foo.java", "\nclass FooTwo{}") + check(targets = Agg(jar), expected = Agg(allSources, classFiles, jar)) + + // Tweaking the resources forces rebuild of the final jar, without + // recompiling classfiles + append(resourceRootPath / "hello.txt", " ") + check(targets = Agg(jar), expected = Agg(jar)) + + // You can swap evaluators halfway without any ill effects + evaluator = new TestEvaluator(Build) + + // Asking for an intermediate target forces things to be build up to that + // target only; these are re-used for any downstream targets requested + append(sourceRootPath / "Bar.java", "\nclass BarTwo{}") + append(resourceRootPath / "hello.txt", " ") + check(targets = Agg(classFiles), expected = Agg(allSources, classFiles)) + check(targets = Agg(jar), expected = Agg(jar)) + check(targets = Agg(allSources), expected = Agg()) + + append(sourceRootPath / "Bar.java", "\nclass BarThree{}") + append(resourceRootPath / "hello.txt", " ") + check(targets = Agg(resourceRoot), expected = Agg()) + check(targets = Agg(allSources), expected = Agg(allSources)) + check(targets = Agg(jar), expected = Agg(classFiles, jar)) + + val jarContents = os.proc('jar, "-tf", evaluator.outPath/'jar/'dest/"out.jar").call(evaluator.outPath).out.string + val expectedJarContents = + """META-INF/MANIFEST.MF + |test/Bar.class + |test/BarThree.class + |test/BarTwo.class + |test/Foo.class + |test/FooTwo.class + |hello.txt + |""".stripMargin + assert(jarContents.linesIterator.toSeq == expectedJarContents.linesIterator.toSeq) + + // Create the Jar again, but this time, filter out the Foo files. + def noFoos(s: String) = !s.contains("Foo") + val filterFunc = (p: os.Path, r: os.RelPath) => noFoos(r.last) + eval(filterJar(filterFunc)) + val filteredJarContents = os.proc('jar, "-tf", evaluator.outPath/'filterJar/'dest/"out.jar").call(evaluator.outPath).out.string + assert(filteredJarContents.linesIterator.toSeq == expectedJarContents.linesIterator.filter(noFoos(_)).toSeq) + + val executed = os.proc('java, "-cp", evaluator.outPath/'jar/'dest/"out.jar", "test.Foo").call(evaluator.outPath).out.string + assert(executed == (31337 + 271828) + System.lineSeparator) + + for(i <- 0 until 3){ + // Build.run is not cached, so every time we eval it it has to + // re-evaluate + val Right((runOutput, evalCount)) = eval(Build.run("test.Foo")) + assert( + runOutput.out.string == (31337 + 271828) + System.lineSeparator, + evalCount == 1 + ) + } + + val Left(Result.Exception(ex, _)) = eval(Build.run("test.BarFour")) + + assert(ex.getMessage.contains("Could not find or load main class")) + + append( + sourceRootPath / "Bar.java", + """ + class BarFour{ + public static void main(String[] args){ + System.out.println("New Cls!"); + } + } + """ + ) + val Right((runOutput2, evalCount2)) = eval(Build.run("test.BarFour")) + assert( + runOutput2.out.string == "New Cls!" + System.lineSeparator, + evalCount2 == 3 + ) + val Right((runOutput3, evalCount3)) = eval(Build.run("test.BarFour")) + assert( + runOutput3.out.string == "New Cls!" + System.lineSeparator, + evalCount3 == 1 + ) + } + } +} diff --git a/main/test/src/eval/ModuleTests.scala b/main/test/src/eval/ModuleTests.scala new file mode 100644 index 00000000..f28fc9b6 --- /dev/null +++ b/main/test/src/eval/ModuleTests.scala @@ -0,0 +1,45 @@ +package mill.eval + + +import mill.util.{TestEvaluator, TestUtil} +import mill.T +import mill.define.Discover + +import utest._ + +object ModuleTests extends TestSuite{ + object ExternalModule extends mill.define.ExternalModule { + def x = T{13} + object inner extends mill.Module{ + def y = T{17} + } + lazy val millDiscover = Discover[this.type] + } + object Build extends TestUtil.BaseModule{ + def z = T{ ExternalModule.x() + ExternalModule.inner.y() } + } + val tests = Tests { + os.remove.all(TestEvaluator.externalOutPath) + 'externalModuleTargetsAreNamespacedByModulePackagePath - { + val check = new TestEvaluator(Build) + val zresult = check.apply(Build.z) + assert( + zresult == Right((30, 1)), + os.read(check.evaluator.outPath / 'z / "meta.json").contains("30"), + os.read(TestEvaluator.externalOutPath / 'mill / 'eval / 'ModuleTests / 'ExternalModule / 'x / "meta.json").contains("13"), + os.read(TestEvaluator.externalOutPath / 'mill / 'eval / 'ModuleTests / 'ExternalModule / 'inner / 'y / "meta.json").contains("17") + ) + } + 'externalModuleMustBeGlobalStatic - { + + + object Build extends mill.define.ExternalModule { + + def z = T{ ExternalModule.x() + ExternalModule.inner.y() } + lazy val millDiscover = Discover[this.type] + } + + intercept[java.lang.AssertionError]{ Build } + } + } +} diff --git a/main/test/src/eval/TarjanTests.scala b/main/test/src/eval/TarjanTests.scala new file mode 100644 index 00000000..2f9d0a4d --- /dev/null +++ b/main/test/src/eval/TarjanTests.scala @@ -0,0 +1,91 @@ +package mill.eval + +import utest._ + +object TarjanTests extends TestSuite{ + def check(input: Seq[Seq[Int]], expected: Seq[Seq[Int]]) = { + val result = Tarjans(input).map(_.sorted) + val sortedExpected = expected.map(_.sorted) + assert(result == sortedExpected) + } + val tests = Tests{ + // + 'empty - check(Seq(), Seq()) + + // (0) + 'singleton - check(Seq(Seq()), Seq(Seq(0))) + + + // (0)-. + // ^._/ + 'selfCycle - check(Seq(Seq(0)), Seq(Seq(0))) + + // (0) <-> (1) + 'simpleCycle- check(Seq(Seq(1), Seq(0)), Seq(Seq(1, 0))) + + // (0) (1) (2) + 'multipleSingletons - check( + Seq(Seq(), Seq(), Seq()), + Seq(Seq(0), Seq(1), Seq(2)) + ) + + // (0) -> (1) -> (2) + 'straightLineNoCycles- check( + Seq(Seq(1), Seq(2), Seq()), + Seq(Seq(2), Seq(1), Seq(0)) + ) + + // (0) <- (1) <- (2) + 'straightLineNoCyclesReversed- check( + Seq(Seq(), Seq(0), Seq(1)), + Seq(Seq(0), Seq(1), Seq(2)) + ) + + // (0) <-> (1) (2) -> (3) -> (4) + // ^.____________/ + 'independentSimpleCycles - check( + Seq(Seq(1), Seq(0), Seq(3), Seq(4), Seq(2)), + Seq(Seq(1, 0), Seq(4, 3, 2)) + ) + + // ___________________ + // v \ + // (0) <-> (1) (2) -> (3) -> (4) + // ^.____________/ + 'independentLinkedCycles - check( + Seq(Seq(1), Seq(0), Seq(3), Seq(4), Seq(2, 1)), + Seq(Seq(1, 0), Seq(4, 3, 2)) + ) + // _____________ + // / v + // (0) <-> (1) (2) -> (3) -> (4) + // ^.____________/ + 'independentLinkedCycles2 - check( + Seq(Seq(1, 2), Seq(0), Seq(3), Seq(4), Seq(2)), + Seq(Seq(4, 3, 2), Seq(1, 0)) + ) + + // _____________ + // / v + // (0) <-> (1) (2) -> (3) -> (4) + // ^. ^.____________/ + // \________________/ + 'combinedCycles - check( + Seq(Seq(1, 2), Seq(0), Seq(3), Seq(4), Seq(2, 1)), + Seq(Seq(4, 3, 2, 1, 0)) + ) + // + // (0) <-> (1) <- (2) <- (3) <-> (4) <- (5) + // ^.____________/ / / + // / / + // (6) <- (7) <-/ (8) <-' + // / / + // v / + // (9) <--------' + 'combinedCycles - check( + Seq(Seq(1), Seq(0), Seq(0, 1), Seq(2, 4, 7, 9), Seq(3), Seq(4, 8), Seq(9), Seq(6), Seq(), Seq()), + Seq(Seq(0, 1), Seq(2), Seq(9), Seq(6), Seq(7), Seq(3, 4), Seq(8), Seq(5)) + ) + + } +} \ No newline at end of file diff --git a/main/test/src/eval/TaskTests.scala b/main/test/src/eval/TaskTests.scala new file mode 100644 index 00000000..0bfd8efc --- /dev/null +++ b/main/test/src/eval/TaskTests.scala @@ -0,0 +1,95 @@ +package mill.eval + +import utest._ + +import mill.T + +import mill.util.TestEvaluator +object TaskTests extends TestSuite{ + val tests = Tests{ + object build extends mill.util.TestUtil.BaseModule{ + var count = 0 + // Explicitly instantiate `Function1` objects to make sure we get + // different instances each time + def staticWorker = T.worker{ + new Function1[Int, Int] { + def apply(v1: Int) = v1 + 1 + } + } + def noisyWorker = T.worker{ + new Function1[Int, Int] { + def apply(v1: Int) = input() + 1 + } + } + def input = T.input{ + count += 1 + count + } + def task = T.task{ + count += 1 + count + } + def taskInput = T{ input() } + def taskNoInput = T{ task() } + + def persistent = T.persistent{ + input() // force re-computation + os.makeDir.all(T.ctx().dest) + os.write.append(T.ctx().dest/'count, "hello\n") + os.read.lines(T.ctx().dest/'count).length + } + def nonPersistent = T{ + input() // force re-computation + os.makeDir.all(T.ctx().dest) + os.write.append(T.ctx().dest/'count, "hello\n") + os.read.lines(T.ctx().dest/'count).length + } + + def staticWorkerDownstream = T{ + staticWorker().apply(1) + } + def noisyWorkerDownstream = T{ + noisyWorker().apply(1) + } + } + + 'inputs - { + // Inputs always re-evaluate, including forcing downstream cached Targets + // to re-evaluate, but normal Tasks behind a Target run once then are cached + val check = new TestEvaluator(build) + + val Right((1, 1)) = check.apply(build.taskInput) + val Right((2, 1)) = check.apply(build.taskInput) + val Right((3, 1)) = check.apply(build.taskInput) + + val Right((4, 1)) = check.apply(build.taskNoInput) + val Right((4, 0)) = check.apply(build.taskNoInput) + val Right((4, 0)) = check.apply(build.taskNoInput) + } + + 'persistent - { + // Persistent tasks keep the working dir around between runs + val check = new TestEvaluator(build) + val Right((1, 1)) = check.apply(build.persistent) + val Right((2, 1)) = check.apply(build.persistent) + val Right((3, 1)) = check.apply(build.persistent) + + val Right((1, 1)) = check.apply(build.nonPersistent) + val Right((1, 1)) = check.apply(build.nonPersistent) + val Right((1, 1)) = check.apply(build.nonPersistent) + } + + 'worker - { + // Persistent task + def check = new TestEvaluator(build) + + val Right((2, 1)) = check.apply(build.noisyWorkerDownstream) + val Right((3, 1)) = check.apply(build.noisyWorkerDownstream) + val Right((4, 1)) = check.apply(build.noisyWorkerDownstream) + + val Right((2, 1)) = check.apply(build.staticWorkerDownstream) + val Right((2, 0)) = check.apply(build.staticWorkerDownstream) + val Right((2, 0)) = check.apply(build.staticWorkerDownstream) + } + } +} diff --git a/main/test/src/main/ClientServerTests.scala b/main/test/src/main/ClientServerTests.scala new file mode 100644 index 00000000..05238a5f --- /dev/null +++ b/main/test/src/main/ClientServerTests.scala @@ -0,0 +1,214 @@ +package mill.main +import java.io._ + +import mill.main.client.{Util, Locks} + +import scala.collection.JavaConverters._ +import utest._ +class EchoServer extends MillServerMain[Int]{ + def main0(args: Array[String], + stateCache: Option[Int], + mainInteractive: Boolean, + stdin: InputStream, + stdout: PrintStream, + stderr: PrintStream, + env: Map[String, String], + setIdle: Boolean => Unit) = { + + val reader = new BufferedReader(new InputStreamReader(stdin)) + val str = reader.readLine() + if (args.nonEmpty){ + stdout.println(str + args(0)) + } + env.toSeq.sortBy(_._1).foreach{ + case (key, value) => stdout.println(s"$key=$value") + } + stdout.flush() + if (args.nonEmpty){ + stderr.println(str.toUpperCase + args(0)) + } + stderr.flush() + (true, None) + } +} + +object ClientServerTests extends TestSuite{ + def initStreams() = { + val in = new ByteArrayInputStream("hello\n".getBytes()) + val out = new ByteArrayOutputStream() + val err = new ByteArrayOutputStream() + (in, out, err) + } + def init() = { + val tmpDir = java.nio.file.Files.createTempDirectory("") + val locks = Locks.memory() + + (tmpDir, locks) + } + + def spawnEchoServer(tmpDir : java.nio.file.Path, locks: Locks): Unit = { + new Thread(() => new Server( + tmpDir.toString, + new EchoServer(), + () => (), + 1000, + locks + ).run()).start() + } + + def runClientAux(tmpDir : java.nio.file.Path, locks: Locks) + (env : Map[String, String], args: Array[String]) = { + val (in, out, err) = initStreams() + Server.lockBlock(locks.clientLock){ + mill.main.client.MillClientMain.run( + tmpDir.toString, + () => spawnEchoServer(tmpDir, locks), + locks, + in, + out, + err, + args, + env.asJava + ) + Thread.sleep(100) + (new String(out.toByteArray), new String(err.toByteArray)) + } + } + + def tests = Tests{ + 'hello - { + if (!Util.isWindows){ + val (tmpDir, locks) = init() + def runClient(s: String) = runClientAux(tmpDir, locks)(Map.empty, Array(s)) + + // Make sure the simple "have the client start a server and + // exchange one message" workflow works from end to end. + + assert( + locks.clientLock.probe(), + locks.serverLock.probe(), + locks.processLock.probe() + ) + + val (out1, err1) = runClient("world") + + assert( + out1 == "helloworld\n", + err1 == "HELLOworld\n" + ) + + // Give a bit of time for the server to release the lock and + // re-acquire it to signal to the client that it's done + Thread.sleep(100) + + assert( + locks.clientLock.probe(), + !locks.serverLock.probe(), + !locks.processLock.probe() + ) + + // A seecond client in sequence connect to the same server + val (out2, err2) = runClient(" WORLD") + + assert( + out2 == "hello WORLD\n", + err2 == "HELLO WORLD\n" + ) + + // Make sure the server times out of not used for a while + Thread.sleep(2000) + assert( + locks.clientLock.probe(), + locks.serverLock.probe(), + locks.processLock.probe() + ) + + // Have a third client spawn/connect-to a new server at the same path + val (out3, err3) = runClient(" World") + assert( + out3 == "hello World\n", + err3 == "HELLO World\n" + ) + } + + 'envVars - { + if (!Util.isWindows){ + val (tmpDir, locks) = init() + + def runClient(env : Map[String, String]) = runClientAux(tmpDir, locks)(env, Array()) + + // Make sure the simple "have the client start a server and + // exchange one message" workflow works from end to end. + + assert( + locks.clientLock.probe(), + locks.serverLock.probe(), + locks.processLock.probe() + ) + + def longString(s : String) = Array.fill(1000)(s).mkString + val b1000 = longString("b") + val c1000 = longString("c") + val a1000 = longString("a") + + val env = Map( + "a" -> a1000, + "b" -> b1000, + "c" -> c1000 + ) + + + val (out1, err1) = runClient(env) + val expected = s"a=$a1000\nb=$b1000\nc=$c1000\n" + + assert( + out1 == expected, + err1 == "" + ) + + // Give a bit of time for the server to release the lock and + // re-acquire it to signal to the client that it's done + Thread.sleep(100) + + assert( + locks.clientLock.probe(), + !locks.serverLock.probe(), + !locks.processLock.probe() + ) + + val path = List( + "/Users/foo/Library/Haskell/bin", + "/usr/local/git/bin", + "/sw/bin/", + "/usr/local/bin", + "/usr/local/", + "/usr/local/sbin", + "/usr/local/mysql/bin", + "/usr/local/bin", + "/usr/bin", + "/bin", + "/usr/sbin", + "/sbin", + "/opt/X11/bin", + "/usr/local/MacGPG2/bin", + "/Library/TeX/texbin", + "/usr/local/bin/", + "/Users/foo/bin", + "/Users/foo/go/bin", + "~/.bloop" + ) + + val pathEnvVar = path.mkString(":") + val (out2, err2) = runClient(Map("PATH" -> pathEnvVar)) + + val expected2 = s"PATH=$pathEnvVar\n" + + assert( + out2 == expected2, + err2 == "" + ) + } + } + } + } +} diff --git a/main/test/src/main/ForeignBuildsTest.scala b/main/test/src/main/ForeignBuildsTest.scala new file mode 100644 index 00000000..cfc8d00c --- /dev/null +++ b/main/test/src/main/ForeignBuildsTest.scala @@ -0,0 +1,30 @@ +package mill.main + +import mill.util.ScriptTestSuite +import utest._ + +object ForeignBuildsTest extends ScriptTestSuite(fork = false) { + def workspaceSlug = "foreign-builds" + def scriptSourcePath = + os.pwd / 'main / 'test / 'resources / 'examples / 'foreign + override def buildPath = os.rel / 'project / "build.sc" + + val tests = Tests { + initWorkspace() + 'test - { + // See https://github.com/lihaoyi/mill/issues/302 + if (!ammonite.util.Util.java9OrAbove) { + assert( + eval("checkProjectPaths"), + eval("checkInnerPaths"), + eval("checkOuterPaths"), + eval("checkOuterInnerPaths"), + eval("checkProjectDests"), + eval("checkInnerDests"), + eval("checkOuterDests"), + eval("checkOuterInnerDests") + ) + } + } + } +} diff --git a/main/test/src/main/ForeignConflictTest.scala b/main/test/src/main/ForeignConflictTest.scala new file mode 100644 index 00000000..a4352bb6 --- /dev/null +++ b/main/test/src/main/ForeignConflictTest.scala @@ -0,0 +1,25 @@ +package mill.main + + +import mill.util.ScriptTestSuite +import utest._ + +object ForeignConflictTest extends ScriptTestSuite(fork = false) { + def workspaceSlug = "foreign-conflict" + def scriptSourcePath = + os.pwd / 'main / 'test / 'resources / 'examples / 'foreign + override def buildPath = os.rel / 'conflict / "build.sc" + + val tests = Tests { + initWorkspace() + 'test - { + // see https://github.com/lihaoyi/mill/issues/302 + if (!ammonite.util.Util.java9OrAbove) { + assert( + eval("checkPaths"), + eval("checkDests") + ) + } + } + } +} diff --git a/main/test/src/main/JavaCompileJarTests.scala b/main/test/src/main/JavaCompileJarTests.scala new file mode 100644 index 00000000..37c64b05 --- /dev/null +++ b/main/test/src/main/JavaCompileJarTests.scala @@ -0,0 +1,67 @@ +package mill.main + +import mill.util.ScriptTestSuite +import utest._ + +object JavaCompileJarTests extends ScriptTestSuite(fork = false) { + def workspaceSlug = "java-compile-jar" + def scriptSourcePath = os.pwd / 'main / 'test / 'resources / 'examples / 'javac + val tests = Tests{ + initWorkspace() + 'test - { + if (!ammonite.util.Util.java9OrAbove) { + // Basic target evaluation works + assert(eval("classFiles")) + assert(eval("jar")) + + val classFiles1 = meta("classFiles") + val jar1 = meta("jar") + + assert(eval("classFiles")) + assert(eval("jar")) + + // Repeated evaluation has the same results + val classFiles2 = meta("classFiles") + val jar2 = meta("jar") + + assert( + jar1 == jar2, + classFiles1 == classFiles2 + ) + + // If we update resources, classFiles are unchanged but jar changes + for(scalaFile <- os.walk(workspacePath).filter(_.ext == "txt")){ + os.write.append(scalaFile, "\n") + } + + assert(eval("classFiles")) + assert(eval("jar")) + + val classFiles3 = meta("classFiles") + val jar3 = meta("jar") + + assert( + jar2 != jar3, + classFiles2 == classFiles3 + ) + + // We can intentionally break the code, have the targets break, then + // fix the code and have them recover. + for(scalaFile <- os.walk(workspacePath).filter(_.ext == "java")){ + os.write.append(scalaFile, "\n}") + } + + assert(!eval("classFiles")) + assert(!eval("jar")) + + for(scalaFile <- os.walk(workspacePath).filter(_.ext == "java")){ + os.write.over(scalaFile, os.read(scalaFile).dropRight(2)) + } + + assert(eval("classFiles")) + assert(eval("jar")) + } + } + } +} + diff --git a/main/test/src/main/MainTests.scala b/main/test/src/main/MainTests.scala new file mode 100644 index 00000000..e836099c --- /dev/null +++ b/main/test/src/main/MainTests.scala @@ -0,0 +1,272 @@ +package mill.main + +import mill.define.{Discover, Segment, Task} +import mill.util.TestGraphs._ + +import utest._ +object MainTests extends TestSuite{ + + def check[T <: mill.define.BaseModule](module: T)( + selectorString: String, + expected0: Either[String, Seq[T => Task[_]]])= { + + val expected = expected0.map(_.map(_(module))) + val resolved = for{ + selectors <- mill.util.ParseArgs(Seq(selectorString), multiSelect = false).map(_._1.head) + val crossSelectors = selectors._2.value.map{case Segment.Cross(x) => x.toList.map(_.toString) case _ => Nil} + task <- mill.main.ResolveTasks.resolve( + selectors._2.value.toList, module, module.millDiscover, Nil, crossSelectors.toList, Nil + ) + } yield task + assert(resolved == expected) + } + val tests = Tests{ + val graphs = new mill.util.TestGraphs() + import graphs._ + 'single - { + val check = MainTests.check(singleton) _ + 'pos - check("single", Right(Seq(_.single))) + 'neg1 - check("sngle", Left("Cannot resolve sngle. Did you mean single?")) + 'neg2 - check("snigle", Left("Cannot resolve snigle. Did you mean single?")) + 'neg3 - check("nsiigle", Left("Cannot resolve nsiigle. Did you mean single?")) + 'neg4 - check("ansiigle", Left("Cannot resolve ansiigle. Try `mill resolve _` to see what's available.")) + 'neg5 - check("doesntExist", Left("Cannot resolve doesntExist. Try `mill resolve _` to see what's available.")) + 'neg6 - check("single.doesntExist", Left("Task single is not a module and has no children.")) + 'neg7 - check("", Left("Selector cannot be empty")) + } + 'backtickIdentifiers - { + val check = MainTests.check(bactickIdentifiers) _ + 'pos1 - check("up-target", Right(Seq(_.`up-target`))) + 'pos2 - check("a-down-target", Right(Seq(_.`a-down-target`))) + 'neg1 - check("uptarget", Left("Cannot resolve uptarget. Did you mean up-target?")) + 'neg2 - check("upt-arget", Left("Cannot resolve upt-arget. Did you mean up-target?")) + 'neg3 - check("up-target.doesntExist", Left("Task up-target is not a module and has no children.")) + 'neg4 - check("", Left("Selector cannot be empty")) + 'neg5 - check("invisible&", Left("Cannot resolve invisible. Try `mill resolve _` to see what's available.")) + 'nested - { + 'pos - check("nested-module.nested-target", Right(Seq(_.`nested-module`.`nested-target`))) + 'neg - check("nested-module.doesntExist", Left("Cannot resolve nested-module.doesntExist. Try `mill resolve nested-module._` to see what's available.")) + } + } + 'nested - { + val check = MainTests.check(nestedModule) _ + 'pos1 - check("single", Right(Seq(_.single))) + 'pos2 - check("nested.single", Right(Seq(_.nested.single))) + 'pos3 - check("classInstance.single", Right(Seq(_.classInstance.single))) + 'neg1 - check( + "doesntExist", + Left("Cannot resolve doesntExist. Try `mill resolve _` to see what's available.") + ) + 'neg2 - check( + "single.doesntExist", + Left("Task single is not a module and has no children.") + ) + 'neg3 - check( + "nested.doesntExist", + Left("Cannot resolve nested.doesntExist. Try `mill resolve nested._` to see what's available.") + ) + 'neg3 - check( + "nested.singel", + Left("Cannot resolve nested.singel. Did you mean nested.single?") + ) + 'neg4 - check( + "classInstance.doesntExist", + Left("Cannot resolve classInstance.doesntExist. Try `mill resolve classInstance._` to see what's available.") + ) + 'wildcard - check( + "_.single", + Right(Seq( + _.classInstance.single, + _.nested.single + )) + ) + 'wildcardNeg - check( + "_._.single", + Left("Cannot resolve _._.single. Try `mill resolve _` to see what's available") + ) + 'wildcardNeg2 - check( + "_._.__", + Left("Cannot resolve _._.__. Try `mill resolve _` to see what's available") + ) + 'wildcardNeg3 - check( + "nested._.foobar", + Left("Cannot resolve nested._.foobar. Try `mill resolve nested._` to see what's available") + ) + 'wildcard2 - check( + "__.single", + Right(Seq( + _.single, + _.classInstance.single, + _.nested.single + )) + ) + + 'wildcard3 - check( + "_.__.single", + Right(Seq( + _.classInstance.single, + _.nested.single + )) + ) + + } + 'cross - { + 'single - { + val check = MainTests.check(singleCross) _ + 'pos1 - check("cross[210].suffix", Right(Seq(_.cross("210").suffix))) + 'pos2 - check("cross[211].suffix", Right(Seq(_.cross("211").suffix))) + 'neg1 - check( + "cross[210].doesntExist", + Left("Cannot resolve cross[210].doesntExist. Try `mill resolve cross[210]._` to see what's available.") + ) + 'neg2 - check( + "cross[doesntExist].doesntExist", + Left("Cannot resolve cross[doesntExist]. Try `mill resolve cross[__]` to see what's available.") + ) + 'neg3 - check( + "cross[221].doesntExist", + Left("Cannot resolve cross[221]. Did you mean cross[211]?") + ) + 'neg4 - check( + "cross[doesntExist].suffix", + Left("Cannot resolve cross[doesntExist]. Try `mill resolve cross[__]` to see what's available.") + ) + 'wildcard - check( + "cross[_].suffix", + Right(Seq( + _.cross("210").suffix, + _.cross("211").suffix, + _.cross("212").suffix + )) + ) + 'wildcard2 - check( + "cross[__].suffix", + Right(Seq( + _.cross("210").suffix, + _.cross("211").suffix, + _.cross("212").suffix + )) + ) + } + 'double - { + val check = MainTests.check(doubleCross) _ + 'pos1 - check( + "cross[210,jvm].suffix", + Right(Seq(_.cross("210", "jvm").suffix)) + ) + 'pos2 - check( + "cross[211,jvm].suffix", + Right(Seq(_.cross("211", "jvm").suffix)) + ) + 'wildcard - { + 'labelNeg - check( + "_.suffix", + Left("Cannot resolve _.suffix. Try `mill resolve _._` to see what's available.") + ) + 'labelPos - check( + "__.suffix", + Right(Seq( + _.cross("210", "jvm").suffix, + _.cross("210", "js").suffix, + + _.cross("211", "jvm").suffix, + _.cross("211", "js").suffix, + + _.cross("212", "jvm").suffix, + _.cross("212", "js").suffix, + _.cross("212", "native").suffix + )) + ) + 'first - check( + "cross[_,jvm].suffix", + Right(Seq( + _.cross("210", "jvm").suffix, + _.cross("211", "jvm").suffix, + _.cross("212", "jvm").suffix + )) + ) + 'second - check( + "cross[210,_].suffix", + Right(Seq( + _.cross("210", "jvm").suffix, + _.cross("210", "js").suffix + )) + ) + 'both - check( + "cross[_,_].suffix", + Right(Seq( + _.cross("210", "jvm").suffix, + _.cross("210", "js").suffix, + + _.cross("211", "jvm").suffix, + _.cross("211", "js").suffix, + + _.cross("212", "jvm").suffix, + _.cross("212", "js").suffix, + _.cross("212", "native").suffix + )) + ) + 'both2 - check( + "cross[__].suffix", + Right(Seq( + _.cross("210", "jvm").suffix, + _.cross("210", "js").suffix, + + _.cross("211", "jvm").suffix, + _.cross("211", "js").suffix, + + _.cross("212", "jvm").suffix, + _.cross("212", "js").suffix, + _.cross("212", "native").suffix + )) + ) + } + } + 'nested - { + val check = MainTests.check(nestedCrosses) _ + 'pos1 - check( + "cross[210].cross2[js].suffix", + Right(Seq(_.cross("210").cross2("js").suffix)) + ) + 'pos2 - check( + "cross[211].cross2[jvm].suffix", + Right(Seq(_.cross("211").cross2("jvm").suffix)) + ) + 'wildcard - { + 'first - check( + "cross[_].cross2[jvm].suffix", + Right(Seq( + _.cross("210").cross2("jvm").suffix, + _.cross("211").cross2("jvm").suffix, + _.cross("212").cross2("jvm").suffix + )) + ) + 'second - check( + "cross[210].cross2[_].suffix", + Right(Seq( + _.cross("210").cross2("jvm").suffix, + _.cross("210").cross2("js").suffix, + _.cross("210").cross2("native").suffix + )) + ) + 'both - check( + "cross[_].cross2[_].suffix", + Right(Seq( + _.cross("210").cross2("jvm").suffix, + _.cross("210").cross2("js").suffix, + _.cross("210").cross2("native").suffix, + + _.cross("211").cross2("jvm").suffix, + _.cross("211").cross2("js").suffix, + _.cross("211").cross2("native").suffix, + + _.cross("212").cross2("jvm").suffix, + _.cross("212").cross2("js").suffix, + _.cross("212").cross2("native").suffix + )) + ) + } + } + } + } +} diff --git a/main/test/src/mill/TestMain.scala b/main/test/src/mill/TestMain.scala deleted file mode 100644 index 80e7e627..00000000 --- a/main/test/src/mill/TestMain.scala +++ /dev/null @@ -1,6 +0,0 @@ -package mill - -object TestMain { - def main(args: Array[String]): Unit = { - } -} diff --git a/main/test/src/mill/UTestFramework.scala b/main/test/src/mill/UTestFramework.scala deleted file mode 100644 index c234151b..00000000 --- a/main/test/src/mill/UTestFramework.scala +++ /dev/null @@ -1,11 +0,0 @@ -package mill - -class UTestFramework extends utest.runner.Framework { - override def exceptionStackFrameHighlighter(s: StackTraceElement) = { - s.getClassName.startsWith("mill.") - } - override def setup() = { - - os.remove.all(os.pwd / 'target / 'workspace) - } -} diff --git a/main/test/src/mill/define/ApplicativeTests.scala b/main/test/src/mill/define/ApplicativeTests.scala deleted file mode 100644 index 9dd2132f..00000000 --- a/main/test/src/mill/define/ApplicativeTests.scala +++ /dev/null @@ -1,125 +0,0 @@ -package mill.define - -import mill.api.Ctx.ImplicitStub -import utest._ - -import scala.annotation.compileTimeOnly -import scala.language.experimental.macros - - -object ApplicativeTests extends TestSuite { - implicit def optionToOpt[T](o: Option[T]): Opt[T] = new Opt(o) - class Opt[T](val self: Option[T]) extends Applicative.Applyable[Option, T] - object Opt extends OptGenerated with Applicative.Applyer[Opt, Option, Applicative.Id, String]{ - - val injectedCtx = "helloooo" - def underlying[A](v: Opt[A]) = v.self - def apply[T](t: T): Option[T] = macro Applicative.impl[Option, T, String] - - def mapCtx[A, B](a: Option[A])(f: (A, String) => B): Option[B] = a.map(f(_, injectedCtx)) - def zip() = Some(()) - def zip[A](a: Option[A]) = a.map(Tuple1(_)) - } - class Counter{ - var value = 0 - def apply() = { - value += 1 - value - } - } - @compileTimeOnly("Target.ctx() can only be used with a T{...} block") - @ImplicitStub - implicit def taskCtx: String = ??? - - val tests = Tests{ - - 'selfContained - { - - 'simple - assert(Opt("lol " + 1) == Some("lol 1")) - 'singleSome - assert(Opt("lol " + Some("hello")()) == Some("lol hello")) - 'twoSomes - assert(Opt(Some("lol ")() + Some("hello")()) == Some("lol hello")) - 'singleNone - assert(Opt("lol " + None()) == None) - 'twoNones - assert(Opt("lol " + None() + None()) == None) - } - 'context - { - assert(Opt(Opt.ctx() + Some("World")()) == Some("hellooooWorld")) - } - 'capturing - { - val lol = "lol " - def hell(o: String) = "hell" + o - 'simple - assert(Opt(lol + 1) == Some("lol 1")) - 'singleSome - assert(Opt(lol + Some(hell("o"))()) == Some("lol hello")) - 'twoSomes - assert(Opt(Some(lol)() + Some(hell("o"))()) == Some("lol hello")) - 'singleNone - assert(Opt(lol + None()) == None) - 'twoNones - assert(Opt(lol + None() + None()) == None) - } - 'allowedLocalDef - { - // Although x is defined inside the Opt{...} block, it is also defined - // within the LHS of the Applyable#apply call, so it is safe to life it - // out into the `zipMap` arguments list. - val res = Opt{ "lol " + Some("hello").flatMap(x => Some(x)).apply() } - assert(res == Some("lol hello")) - } - 'upstreamAlwaysEvaluated - { - // Whether or not control-flow reaches the Applyable#apply call inside an - // Opt{...} block, we always evaluate the LHS of the Applyable#apply - // because it gets lifted out of any control flow statements - val counter = new Counter() - def up = Opt{ "lol " + counter() } - val down = Opt{ if ("lol".length > 10) up() else "fail" } - assert( - down == Some("fail"), - counter.value == 1 - ) - } - 'upstreamEvaluatedOnlyOnce - { - // Even if control-flow reaches the Applyable#apply call more than once, - // it only gets evaluated once due to its lifting out of the Opt{...} block - val counter = new Counter() - def up = Opt{ "lol " + counter() } - def runTwice[T](t: => T) = (t, t) - val down = Opt{ runTwice(up()) } - assert( - down == Some(("lol 1", "lol 1")), - counter.value == 1 - ) - } - 'evaluationsInsideLambdasWork - { - // This required some fiddling with owner chains inside the macro to get - // working, so ensure it doesn't regress - val counter = new Counter() - def up = Opt{ "hello" + counter() } - val down1 = Opt{ (() => up())() } - val down2 = Opt{ Seq(1, 2, 3).map(n => up() * n) } - assert( - down1 == Some("hello1"), - down2 == Some(Seq("hello2", "hello2hello2", "hello2hello2hello2")) - ) - } - 'appliesEvaluatedOncePerLexicalCallsite - { - // If you have multiple Applyable#apply() lexically in the source code of - // your Opt{...} call, each one gets evaluated once, even if the LHS of each - // apply() call is identical. It's up to the downstream zipMap() - // implementation to decide if it wants to dedup them or do other things. - val counter = new Counter() - def up = Opt{ "hello" + counter() } - val down = Opt{ Seq(1, 2, 3).map(n => n + up() + up()) } - assert(down == Some(Seq("1hello1hello2", "2hello1hello2", "3hello1hello2"))) - } - 'appliesEvaluateBeforehand - { - // Every Applyable#apply() within a Opt{...} block evaluates before any - // other logic within that block, even if they would happen first in the - // normal Scala evaluation order - val counter = new Counter() - def up = Opt{ counter() } - val down = Opt{ - val res = counter() - val one = up() - val two = up() - val three = up() - (res, one, two, three) - } - assert(down == Some((4, 1, 2, 3))) - } - } -} diff --git a/main/test/src/mill/define/BasePathTests.scala b/main/test/src/mill/define/BasePathTests.scala deleted file mode 100644 index b8a653c8..00000000 --- a/main/test/src/mill/define/BasePathTests.scala +++ /dev/null @@ -1,73 +0,0 @@ -package mill.define - -import mill.util.{TestGraphs, TestUtil} -import utest._ -import mill.{Module, T} -object BasePathTests extends TestSuite{ - val testGraphs = new TestGraphs - val tests = Tests{ - def check[T <: Module](m: T)(f: T => Module, segments: String*) = { - val remaining = f(m).millSourcePath.relativeTo(m.millSourcePath).segments - assert(remaining == segments) - } - 'singleton - { - check(testGraphs.singleton)(identity) - } - 'backtickIdentifiers - { - check(testGraphs.bactickIdentifiers)( - _.`nested-module`, - "nested-module" - ) - } - 'separateGroups - { - check(TestGraphs.triangleTask)(identity) - } - 'TraitWithModuleObject - { - check(TestGraphs.TraitWithModuleObject)( - _.TraitModule, - "TraitModule" - ) - } - 'nestedModuleNested - { - check(TestGraphs.nestedModule)(_.nested, "nested") - } - 'nestedModuleInstance - { - check(TestGraphs.nestedModule)(_.classInstance, "classInstance") - } - 'singleCross - { - check(TestGraphs.singleCross)(_.cross, "cross") - check(TestGraphs.singleCross)(_.cross("210"), "cross", "210") - check(TestGraphs.singleCross)(_.cross("211"), "cross", "211") - } - 'doubleCross - { - check(TestGraphs.doubleCross)(_.cross, "cross") - check(TestGraphs.doubleCross)(_.cross("210", "jvm"), "cross", "210", "jvm") - check(TestGraphs.doubleCross)(_.cross("212", "js"), "cross", "212", "js") - } - 'nestedCrosses - { - check(TestGraphs.nestedCrosses)(_.cross, "cross") - check(TestGraphs.nestedCrosses)( - _.cross("210").cross2("js"), - "cross", "210", "cross2", "js" - ) - } - 'overriden - { - object overridenBasePath extends TestUtil.BaseModule { - override def millSourcePath = os.pwd / 'overridenBasePathRootValue - object nested extends Module{ - override def millSourcePath = super.millSourcePath / 'overridenBasePathNested - object nested extends Module{ - override def millSourcePath = super.millSourcePath / 'overridenBasePathDoubleNested - } - } - } - assert( - overridenBasePath.millSourcePath == os.pwd / 'overridenBasePathRootValue, - overridenBasePath.nested.millSourcePath == os.pwd / 'overridenBasePathRootValue / 'nested / 'overridenBasePathNested, - overridenBasePath.nested.nested.millSourcePath == os.pwd / 'overridenBasePathRootValue / 'nested / 'overridenBasePathNested / 'nested / 'overridenBasePathDoubleNested - ) - } - - } -} - diff --git a/main/test/src/mill/define/CacherTests.scala b/main/test/src/mill/define/CacherTests.scala deleted file mode 100644 index 59ebf3f6..00000000 --- a/main/test/src/mill/define/CacherTests.scala +++ /dev/null @@ -1,75 +0,0 @@ -package mill.define - -import mill.util.{DummyLogger, TestEvaluator, TestUtil} -import mill.util.Strict.Agg -import mill.T -import mill.api.Result.Success -import utest._ -import utest.framework.TestPath - - -object CacherTests extends TestSuite{ - object Base extends Base - trait Base extends TestUtil.BaseModule{ - def value = T{ 1 } - def result = T{ Success(1) } - } - object Middle extends Middle - trait Middle extends Base{ - override def value = T{ super.value() + 2} - def overriden = T{ super.value()} - } - object Terminal extends Terminal - trait Terminal extends Middle{ - override def value = T{ super.value() + 4} - } - - val tests = Tests{ - def eval[T <: TestUtil.BaseModule, V](mapping: T, v: Task[V]) - (implicit tp: TestPath) = { - val evaluator = new TestEvaluator(mapping) - evaluator(v).right.get._1 - } - def check(x: Any, y: Any) = assert(x == y) - - 'simpleDefIsCached - { - Predef.assert(Base.value eq Base.value) - Predef.assert(eval(Base, Base.value) == 1) - } - - 'resultDefIsCached - { - Predef.assert(Base.result eq Base.result) - Predef.assert(eval(Base, Base.result) == 1) - } - - - 'overridingDefIsAlsoCached - { - Predef.assert(eval(Middle, Middle.value) == 3) - Predef.assert(Middle.value eq Middle.value) - } - - 'overridenDefRemainsAvailable - { - Predef.assert(eval(Middle, Middle.overriden) == 1) - } - - - 'multipleOverridesWork- { - Predef.assert(eval(Terminal, Terminal.value) == 7) - Predef.assert(eval(Terminal, Terminal.overriden) == 1) - } - // Doesn't fail, presumably compileError doesn't go far enough in the - // compilation pipeline to hit the override checks - // - // 'overrideOutsideModuleFails - { - // compileError(""" - // trait Foo{ - // def x = 1 - // } - // object Bar extends Foo{ - // def x = 2 - // } - // """) - // } - } -} - diff --git a/main/test/src/mill/define/DiscoverTests.scala b/main/test/src/mill/define/DiscoverTests.scala deleted file mode 100644 index 248d6afe..00000000 --- a/main/test/src/mill/define/DiscoverTests.scala +++ /dev/null @@ -1,63 +0,0 @@ -package mill.define - -import mill.util.TestGraphs -import utest._ - -object DiscoverTests extends TestSuite{ - val testGraphs = new TestGraphs - val tests = Tests{ - def check[T <: Module](m: T)(targets: (T => Target[_])*) = { - val discovered = m.millInternal.targets - val expected = targets.map(_(m)).toSet - assert(discovered == expected) - } - 'singleton - { - check(testGraphs.singleton)(_.single) - } - 'backtickIdentifiers { - check(testGraphs.bactickIdentifiers)(_.`up-target`, _.`a-down-target`, _.`nested-module`.`nested-target`) - } - 'separateGroups - { - check(TestGraphs.triangleTask)(_.left, _.right) - } - 'TraitWithModuleObject - { - check(TestGraphs.TraitWithModuleObject)(_.TraitModule.testFrameworks) - } - 'nestedModule - { - check(TestGraphs.nestedModule)(_.single, _.nested.single, _.classInstance.single) - } - 'singleCross - { - check(TestGraphs.singleCross)( - _.cross("210").suffix, - _.cross("211").suffix, - _.cross("212").suffix - ) - } - 'doubleCross - { - check(TestGraphs.doubleCross)( - _.cross("210", "jvm").suffix, - _.cross("210", "js").suffix, - _.cross("211", "jvm").suffix, - _.cross("211", "js").suffix, - _.cross("212", "jvm").suffix, - _.cross("212", "js").suffix, - _.cross("212", "native").suffix - ) - } - 'nestedCrosses - { - check(TestGraphs.nestedCrosses)( - _.cross("210").cross2("jvm").suffix, - _.cross("210").cross2("js").suffix, - _.cross("210").cross2("native").suffix, - _.cross("211").cross2("jvm").suffix, - _.cross("211").cross2("js").suffix, - _.cross("211").cross2("native").suffix, - _.cross("212").cross2("jvm").suffix, - _.cross("212").cross2("js").suffix, - _.cross("212").cross2("native").suffix - ) - } - - } -} - diff --git a/main/test/src/mill/define/GraphTests.scala b/main/test/src/mill/define/GraphTests.scala deleted file mode 100644 index 224ce59f..00000000 --- a/main/test/src/mill/define/GraphTests.scala +++ /dev/null @@ -1,211 +0,0 @@ -package mill.define - - -import mill.eval.Evaluator -import mill.util.{TestGraphs, TestUtil} -import utest._ -import mill.util.Strict.Agg -object GraphTests extends TestSuite{ - - val tests = Tests{ - - - val graphs = new TestGraphs() - import graphs._ - import TestGraphs._ - - 'topoSortedTransitiveTargets - { - def check(targets: Agg[Task[_]], expected: Agg[Task[_]]) = { - val result = Graph.topoSorted(Graph.transitiveTargets(targets)).values - TestUtil.checkTopological(result) - assert(result == expected) - } - - 'singleton - check( - targets = Agg(singleton.single), - expected = Agg(singleton.single) - ) - 'backtickIdentifiers - check( - targets = Agg(bactickIdentifiers.`a-down-target`), - expected = Agg(bactickIdentifiers.`up-target`, bactickIdentifiers.`a-down-target`) - ) - 'pair - check( - targets = Agg(pair.down), - expected = Agg(pair.up, pair.down) - ) - 'anonTriple - check( - targets = Agg(anonTriple.down), - expected = Agg(anonTriple.up, anonTriple.down.inputs(0), anonTriple.down) - ) - 'diamond - check( - targets = Agg(diamond.down), - expected = Agg(diamond.up, diamond.left, diamond.right, diamond.down) - ) - 'anonDiamond - check( - targets = Agg(diamond.down), - expected = Agg( - diamond.up, - diamond.down.inputs(0), - diamond.down.inputs(1), - diamond.down - ) - ) - 'defCachedDiamond - check( - targets = Agg(defCachedDiamond.down), - expected = Agg( - defCachedDiamond.up.inputs(0), - defCachedDiamond.up, - defCachedDiamond.down.inputs(0).inputs(0).inputs(0), - defCachedDiamond.down.inputs(0).inputs(0), - defCachedDiamond.down.inputs(0).inputs(1).inputs(0), - defCachedDiamond.down.inputs(0).inputs(1), - defCachedDiamond.down.inputs(0), - defCachedDiamond.down - ) - ) - 'bigSingleTerminal - { - val result = Graph.topoSorted(Graph.transitiveTargets(Agg(bigSingleTerminal.j))).values - TestUtil.checkTopological(result) - assert(result.size == 28) - } - } - - 'groupAroundNamedTargets - { - def check[T, R <: Target[Int]](base: T) - (target: T => R, - important0: Agg[T => Target[_]], - expected: Agg[(R, Int)]) = { - - val topoSorted = Graph.topoSorted(Graph.transitiveTargets(Agg(target(base)))) - - val important = important0.map(_ (base)) - val grouped = Graph.groupAroundImportantTargets(topoSorted) { - case t: Target[_] if important.contains(t) => t - } - val flattened = Agg.from(grouped.values().flatMap(_.items)) - - TestUtil.checkTopological(flattened) - for ((terminal, expectedSize) <- expected) { - val grouping = grouped.lookupKey(terminal) - assert( - grouping.size == expectedSize, - grouping.flatMap(_.asTarget: Option[Target[_]]).filter(important.contains) == Agg(terminal) - ) - } - } - - 'singleton - check(singleton)( - _.single, - Agg(_.single), - Agg(singleton.single -> 1) - ) - 'backtickIdentifiers - check(bactickIdentifiers)( - _.`a-down-target`, - Agg(_.`up-target`, _.`a-down-target`), - Agg( - bactickIdentifiers.`up-target` -> 1, - bactickIdentifiers.`a-down-target` -> 1 - ) - ) - 'pair - check(pair)( - _.down, - Agg(_.up, _.down), - Agg(pair.up -> 1, pair.down -> 1) - ) - 'anonTriple - check(anonTriple)( - _.down, - Agg(_.up, _.down), - Agg(anonTriple.up -> 1, anonTriple.down -> 2) - ) - 'diamond - check(diamond)( - _.down, - Agg(_.up, _.left, _.right, _.down), - Agg( - diamond.up -> 1, - diamond.left -> 1, - diamond.right -> 1, - diamond.down -> 1 - ) - ) - - 'defCachedDiamond - check(defCachedDiamond)( - _.down, - Agg(_.up, _.left, _.right, _.down), - Agg( - defCachedDiamond.up -> 2, - defCachedDiamond.left -> 2, - defCachedDiamond.right -> 2, - defCachedDiamond.down -> 2 - ) - ) - - 'anonDiamond - check(anonDiamond)( - _.down, - Agg(_.down, _.up), - Agg( - anonDiamond.up -> 1, - anonDiamond.down -> 3 - ) - ) - 'bigSingleTerminal - check(bigSingleTerminal)( - _.j, - Agg(_.a, _.b, _.e, _.f, _.i, _.j), - Agg( - bigSingleTerminal.a -> 3, - bigSingleTerminal.b -> 2, - bigSingleTerminal.e -> 9, - bigSingleTerminal.i -> 6, - bigSingleTerminal.f -> 4, - bigSingleTerminal.j -> 4 - ) - ) - } - 'multiTerminalGroupCounts - { - def countGroups(goals: Task[_]*) = { - - val topoSorted = Graph.topoSorted( - Graph.transitiveTargets(Agg.from(goals)) - ) - val grouped = Graph.groupAroundImportantTargets(topoSorted) { - case t: NamedTask[Any] => t - case t if goals.contains(t) => t - } - grouped.keyCount - } - - 'separateGroups - { - import separateGroups._ - val groupCount = countGroups(right, left) - assert(groupCount == 3) - } - - 'triangleTask - { - // Make sure the following graph ends up as a single group, since although - // `right` depends on `left`, both of them depend on the un-cached `task` - // which would force them both to re-compute every time `task` changes - import triangleTask._ - val groupCount = countGroups(right, left) - assert(groupCount == 2) - } - - - 'multiTerminalGroup - { - // Make sure the following graph ends up as two groups - import multiTerminalGroup._ - val groupCount = countGroups(right, left) - assert(groupCount == 2) - } - - - 'multiTerminalBoundary - { - // Make sure the following graph ends up as a three groups: one for - // each cached target, and one for the downstream task we are running - import multiTerminalBoundary._ - val groupCount = countGroups(task2) - assert(groupCount == 3) - } - } - - - } -} diff --git a/main/test/src/mill/define/MacroErrorTests.scala b/main/test/src/mill/define/MacroErrorTests.scala deleted file mode 100644 index c8b140fa..00000000 --- a/main/test/src/mill/define/MacroErrorTests.scala +++ /dev/null @@ -1,145 +0,0 @@ -package mill.define - -import utest._ -import mill.{T, Module} -import mill.util.TestUtil -object MacroErrorTests extends TestSuite{ - - val tests = Tests{ - - 'errors{ - val expectedMsg = - "T{} members must be defs defined in a Cacher class/trait/object body" - - val err = compileError("object Foo extends TestUtil.BaseModule{ val x = T{1} }") - assert(err.msg == expectedMsg) - } - - 'badParameterSets - { - 'command - { - val e = compileError(""" - object foo extends mill.util.TestUtil.BaseModule{ - def w = T.command{1} - } - mill.define.Discover[foo.type] - """) - assert( - e.msg.contains("`T.command` definitions must have 1 parameter list"), - e.pos.contains("def w = ") - ) - } - 'target - { - val e = compileError(""" - object foo extends mill.util.TestUtil.BaseModule{ - def x() = T{1} - } - mill.define.Discover[foo.type] - """) - assert( - e.msg.contains("`T{...}` definitions must have 0 parameter lists"), - e.pos.contains("def x() = ") - ) - } - 'input - { - val e = compileError(""" - object foo extends mill.util.TestUtil.BaseModule{ - def y() = T.input{1} - } - mill.define.Discover[foo.type] - """) - assert( - e.msg.contains("`T.input` definitions must have 0 parameter lists"), - e.pos.contains("def y() = ") - ) - } - 'sources - { - val e = compileError(""" - object foo extends mill.util.TestUtil.BaseModule{ - def z() = T.sources{ammonite.ops.pwd} - } - mill.define.Discover[foo.type] - """) - assert( - e.msg.contains("`T.sources` definitions must have 0 parameter lists"), - e.pos.contains("def z() = ") - ) - } - 'persistent - { - val e = compileError(""" - object foo extends mill.util.TestUtil.BaseModule{ - def a() = T.persistent{1} - } - mill.define.Discover[foo.type] - """) - assert( - e.msg.contains("`T.persistent` definitions must have 0 parameter lists"), - e.pos.contains("def a() = ") - ) - } - } - 'badTmacro - { - // Make sure we can reference values from outside the T{...} block as part - // of our `Target#apply()` calls, but we cannot reference any values that - // come from inside the T{...} block - 'pos - { - val e = compileError(""" - val a = T{ 1 } - val arr = Array(a) - val b = { - val c = 0 - T{ - arr(c)() - } - } - """) - assert(e.msg.contains( - "Modules, Targets and Commands can only be defined within a mill Module") - ) - } - 'neg - { - - val expectedMsg = - "Target#apply() call cannot use `value n` defined within the T{...} block" - val err = compileError("""new Module{ - def a = T{ 1 } - val arr = Array(a) - def b = { - T{ - val n = 0 - arr(n)() - } - } - }""") - assert(err.msg == expectedMsg) - } - 'neg2 - { - - val expectedMsg = - "Target#apply() call cannot use `value x` defined within the T{...} block" - val err = compileError("""new Module{ - def a = T{ 1 } - val arr = Array(a) - def b = { - T{ - arr.map{x => x()} - } - } - }""") - assert(err.msg == expectedMsg) - } - 'neg3{ - val borkedCachedDiamond1 = utest.compileError(""" - object borkedCachedDiamond1 { - def up = T{ TestUtil.test() } - def left = T{ TestUtil.test(up) } - def right = T{ TestUtil.test(up) } - def down = T{ TestUtil.test(left, right) } - } - """) - assert(borkedCachedDiamond1.msg.contains( - "Modules, Targets and Commands can only be defined within a mill Module") - ) - } - } - } -} diff --git a/main/test/src/mill/eval/CrossTests.scala b/main/test/src/mill/eval/CrossTests.scala deleted file mode 100644 index f194924e..00000000 --- a/main/test/src/mill/eval/CrossTests.scala +++ /dev/null @@ -1,56 +0,0 @@ -package mill.eval - - -import mill.define.Discover -import mill.util.TestEvaluator - -import mill.util.TestGraphs.{crossResolved, doubleCross, nestedCrosses, singleCross} -import utest._ -object CrossTests extends TestSuite{ - val tests = Tests{ - 'singleCross - { - val check = new TestEvaluator(singleCross) - - val Right(("210", 1)) = check.apply(singleCross.cross("210").suffix) - val Right(("211", 1)) = check.apply(singleCross.cross("211").suffix) - val Right(("212", 1)) = check.apply(singleCross.cross("212").suffix) - } - - 'crossResolved - { - val check = new TestEvaluator(crossResolved) - - val Right(("2.10", 1)) = check.apply(crossResolved.foo("2.10").suffix) - val Right(("2.11", 1)) = check.apply(crossResolved.foo("2.11").suffix) - val Right(("2.12", 1)) = check.apply(crossResolved.foo("2.12").suffix) - - val Right(("_2.10", 1)) = check.apply(crossResolved.bar("2.10").longSuffix) - val Right(("_2.11", 1)) = check.apply(crossResolved.bar("2.11").longSuffix) - val Right(("_2.12", 1)) = check.apply(crossResolved.bar("2.12").longSuffix) - } - - - 'doubleCross - { - val check = new TestEvaluator(doubleCross) - - val Right(("210_jvm", 1)) = check.apply(doubleCross.cross("210", "jvm").suffix) - val Right(("210_js", 1)) = check.apply(doubleCross.cross("210", "js").suffix) - val Right(("211_jvm", 1)) = check.apply(doubleCross.cross("211", "jvm").suffix) - val Right(("211_js", 1)) = check.apply(doubleCross.cross("211", "js").suffix) - val Right(("212_jvm", 1)) = check.apply(doubleCross.cross("212", "jvm").suffix) - val Right(("212_js", 1)) = check.apply(doubleCross.cross("212", "js").suffix) - val Right(("212_native", 1)) = check.apply(doubleCross.cross("212", "native").suffix) - } - - 'nestedCrosses - { - val check = new TestEvaluator(nestedCrosses) - - val Right(("210_jvm", 1)) = check.apply(nestedCrosses.cross("210").cross2("jvm").suffix) - val Right(("210_js", 1)) = check.apply(nestedCrosses.cross("210").cross2("js").suffix) - val Right(("211_jvm", 1)) = check.apply(nestedCrosses.cross("211").cross2("jvm").suffix) - val Right(("211_js", 1)) = check.apply(nestedCrosses.cross("211").cross2("js").suffix) - val Right(("212_jvm", 1)) = check.apply(nestedCrosses.cross("212").cross2("jvm").suffix) - val Right(("212_js", 1)) = check.apply(nestedCrosses.cross("212").cross2("js").suffix) - val Right(("212_native", 1)) = check.apply(nestedCrosses.cross("212").cross2("native").suffix) - } - } -} diff --git a/main/test/src/mill/eval/EvaluationTests.scala b/main/test/src/mill/eval/EvaluationTests.scala deleted file mode 100644 index 74f9088c..00000000 --- a/main/test/src/mill/eval/EvaluationTests.scala +++ /dev/null @@ -1,354 +0,0 @@ -package mill.eval - - -import mill.util.TestUtil.{Test, test} -import mill.define.{Discover, Graph, Target, Task} -import mill.{Module, T} -import mill.util.{DummyLogger, TestEvaluator, TestGraphs, TestUtil} -import mill.util.Strict.Agg -import utest._ -import utest.framework.TestPath - - - -object EvaluationTests extends TestSuite{ - class Checker[T <: TestUtil.BaseModule](module: T)(implicit tp: TestPath) { - // Make sure data is persisted even if we re-create the evaluator each time - - def evaluator = new TestEvaluator(module).evaluator - - def apply(target: Task[_], expValue: Any, - expEvaled: Agg[Task[_]], - // How many "other" tasks were evaluated other than those listed above. - // Pass in -1 to skip the check entirely - extraEvaled: Int = 0, - // Perform a second evaluation of the same tasks, and make sure the - // outputs are the same but nothing was evaluated. Disable this if you - // are directly evaluating tasks which need to re-evaluate every time - secondRunNoOp: Boolean = true) = { - - val evaled = evaluator.evaluate(Agg(target)) - - val (matchingReturnedEvaled, extra) = - evaled.evaluated.indexed.partition(expEvaled.contains) - - assert( - evaled.values == Seq(expValue), - matchingReturnedEvaled.toSet == expEvaled.toSet, - extraEvaled == -1 || extra.length == extraEvaled - ) - - // Second time the value is already cached, so no evaluation needed - if (secondRunNoOp){ - val evaled2 = evaluator.evaluate(Agg(target)) - val expecteSecondRunEvaluated = Agg() - assert( - evaled2.values == evaled.values, - evaled2.evaluated == expecteSecondRunEvaluated - ) - } - } - } - - - val tests = Tests{ - object graphs extends TestGraphs() - import graphs._ - import TestGraphs._ - 'evaluateSingle - { - - 'singleton - { - import singleton._ - val check = new Checker(singleton) - // First time the target is evaluated - check(single, expValue = 0, expEvaled = Agg(single)) - - single.counter += 1 - // After incrementing the counter, it forces re-evaluation - check(single, expValue = 1, expEvaled = Agg(single)) - } - 'backtickIdentifiers - { - import graphs.bactickIdentifiers._ - val check = new Checker(bactickIdentifiers) - - check(`a-down-target`, expValue = 0, expEvaled = Agg(`up-target`, `a-down-target`)) - - `a-down-target`.counter += 1 - check(`a-down-target`, expValue = 1, expEvaled = Agg(`a-down-target`)) - - `up-target`.counter += 1 - check(`a-down-target`, expValue = 2, expEvaled = Agg(`up-target`, `a-down-target`)) - } - 'pair - { - import pair._ - val check = new Checker(pair) - check(down, expValue = 0, expEvaled = Agg(up, down)) - - down.counter += 1 - check(down, expValue = 1, expEvaled = Agg(down)) - - up.counter += 1 - check(down, expValue = 2, expEvaled = Agg(up, down)) - } - 'anonTriple - { - import anonTriple._ - val check = new Checker(anonTriple) - val middle = down.inputs(0) - check(down, expValue = 0, expEvaled = Agg(up, middle, down)) - - down.counter += 1 - check(down, expValue = 1, expEvaled = Agg(middle, down)) - - up.counter += 1 - check(down, expValue = 2, expEvaled = Agg(up, middle, down)) - - middle.asInstanceOf[TestUtil.Test].counter += 1 - - check(down, expValue = 3, expEvaled = Agg(middle, down)) - } - 'diamond - { - import diamond._ - val check = new Checker(diamond) - check(down, expValue = 0, expEvaled = Agg(up, left, right, down)) - - down.counter += 1 - check(down, expValue = 1, expEvaled = Agg(down)) - - up.counter += 1 - // Increment by 2 because up is referenced twice: once by left once by right - check(down, expValue = 3, expEvaled = Agg(up, left, right, down)) - - left.counter += 1 - check(down, expValue = 4, expEvaled = Agg(left, down)) - - right.counter += 1 - check(down, expValue = 5, expEvaled = Agg(right, down)) - } - 'anonDiamond - { - import anonDiamond._ - val check = new Checker(anonDiamond) - val left = down.inputs(0).asInstanceOf[TestUtil.Test] - val right = down.inputs(1).asInstanceOf[TestUtil.Test] - check(down, expValue = 0, expEvaled = Agg(up, left, right, down)) - - down.counter += 1 - check(down, expValue = 1, expEvaled = Agg(left, right, down)) - - up.counter += 1 - // Increment by 2 because up is referenced twice: once by left once by right - check(down, expValue = 3, expEvaled = Agg(up, left, right, down)) - - left.counter += 1 - check(down, expValue = 4, expEvaled = Agg(left, right, down)) - - right.counter += 1 - check(down, expValue = 5, expEvaled = Agg(left, right, down)) - } - - 'bigSingleTerminal - { - import bigSingleTerminal._ - val check = new Checker(bigSingleTerminal) - - check(j, expValue = 0, expEvaled = Agg(a, b, e, f, i, j), extraEvaled = 22) - - j.counter += 1 - check(j, expValue = 1, expEvaled = Agg(j), extraEvaled = 3) - - i.counter += 1 - // increment value by 2 because `i` is used twice on the way to `j` - check(j, expValue = 3, expEvaled = Agg(j, i), extraEvaled = 8) - - b.counter += 1 - // increment value by 4 because `b` is used four times on the way to `j` - check(j, expValue = 7, expEvaled = Agg(b, e, f, i, j), extraEvaled = 20) - } - } - - 'evaluateMixed - { - 'separateGroups - { - // Make sure that `left` and `right` are able to recompute separately, - // even though one depends on the other - - import separateGroups._ - val checker = new Checker(separateGroups) - val evaled1 = checker.evaluator.evaluate(Agg(right, left)) - val filtered1 = evaled1.evaluated.filter(_.isInstanceOf[Target[_]]) - assert(filtered1 == Agg(change, left, right)) - val evaled2 = checker.evaluator.evaluate(Agg(right, left)) - val filtered2 = evaled2.evaluated.filter(_.isInstanceOf[Target[_]]) - assert(filtered2 == Agg()) - change.counter += 1 - val evaled3 = checker.evaluator.evaluate(Agg(right, left)) - val filtered3 = evaled3.evaluated.filter(_.isInstanceOf[Target[_]]) - assert(filtered3 == Agg(change, right)) - - - } - 'triangleTask - { - - import triangleTask._ - val checker = new Checker(triangleTask) - checker(right, 3, Agg(left, right), extraEvaled = -1) - checker(left, 1, Agg(), extraEvaled = -1) - - } - 'multiTerminalGroup - { - import multiTerminalGroup._ - - val checker = new Checker(multiTerminalGroup) - checker(right, 1, Agg(right), extraEvaled = -1) - checker(left, 1, Agg(left), extraEvaled = -1) - } - - 'multiTerminalBoundary - { - - import multiTerminalBoundary._ - - val checker = new Checker(multiTerminalBoundary) - checker(task2, 4, Agg(right, left), extraEvaled = -1, secondRunNoOp = false) - checker(task2, 4, Agg(), extraEvaled = -1, secondRunNoOp = false) - } - - 'overrideSuperTask - { - // Make sure you can override targets, call their supers, and have the - // overriden target be allocated a spot within the overriden/ folder of - // the main publically-available target - import canOverrideSuper._ - - val checker = new Checker(canOverrideSuper) - checker(foo, Seq("base", "object"), Agg(foo), extraEvaled = -1) - - - val public = ammonite.ops.read(checker.evaluator.outPath / 'foo / "meta.json") - val overriden = ammonite.ops.read( - checker.evaluator.outPath / 'foo / - 'overriden / "mill" / "util" / "TestGraphs" / "BaseModule" / "foo" / "meta.json" - ) - assert( - public.contains("base"), - public.contains("object"), - overriden.contains("base"), - !overriden.contains("object") - ) - } - 'overrideSuperCommand - { - // Make sure you can override commands, call their supers, and have the - // overriden command be allocated a spot within the overriden/ folder of - // the main publically-available command - import canOverrideSuper._ - - val checker = new Checker(canOverrideSuper) - val runCmd = cmd(1) - checker( - runCmd, - Seq("base1", "object1"), - Agg(runCmd), - extraEvaled = -1, - secondRunNoOp = false - ) - - val public = ammonite.ops.read(checker.evaluator.outPath / 'cmd / "meta.json") - val overriden = ammonite.ops.read( - checker.evaluator.outPath / 'cmd / - 'overriden / "mill" / "util" / "TestGraphs" / "BaseModule"/ "cmd" / "meta.json" - ) - assert( - public.contains("base1"), - public.contains("object1"), - overriden.contains("base1"), - !overriden.contains("object1") - ) - } - 'nullTasks - { - import nullTasks._ - val checker = new Checker(nullTasks) - checker(nullTarget1, null, Agg(nullTarget1), extraEvaled = -1) - checker(nullTarget1, null, Agg(), extraEvaled = -1) - checker(nullTarget2, null, Agg(nullTarget2), extraEvaled = -1) - checker(nullTarget2, null, Agg(), extraEvaled = -1) - checker(nullTarget3, null, Agg(nullTarget3), extraEvaled = -1) - checker(nullTarget3, null, Agg(), extraEvaled = -1) - checker(nullTarget4, null, Agg(nullTarget4), extraEvaled = -1) - checker(nullTarget4, null, Agg(), extraEvaled = -1) - - val nc1 = nullCommand1() - val nc2 = nullCommand2() - val nc3 = nullCommand3() - val nc4 = nullCommand4() - - checker(nc1, null, Agg(nc1), extraEvaled = -1, secondRunNoOp = false) - checker(nc1, null, Agg(nc1), extraEvaled = -1, secondRunNoOp = false) - checker(nc2, null, Agg(nc2), extraEvaled = -1, secondRunNoOp = false) - checker(nc2, null, Agg(nc2), extraEvaled = -1, secondRunNoOp = false) - checker(nc3, null, Agg(nc3), extraEvaled = -1, secondRunNoOp = false) - checker(nc3, null, Agg(nc3), extraEvaled = -1, secondRunNoOp = false) - checker(nc4, null, Agg(nc4), extraEvaled = -1, secondRunNoOp = false) - checker(nc4, null, Agg(nc4), extraEvaled = -1, secondRunNoOp = false) - } - - 'tasksAreUncached - { - // Make sure the tasks `left` and `middle` re-compute every time, while - // the target `right` does not - // - // ___ left ___ - // / \ - // up middle -- down - // / - // right - object build extends TestUtil.BaseModule{ - var leftCount = 0 - var rightCount = 0 - var middleCount = 0 - def up = T{ test.anon() } - 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 build._ - - // 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(build) - assert(leftCount == 0, rightCount == 0) - check(down, expValue = 10101, expEvaled = Agg(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 = Agg()) - 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 = Agg(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 = Agg(), extraEvaled = 1, secondRunNoOp = false) - assert(leftCount == 3, middleCount == 2, rightCount == 1) - check(left, expValue = 2, expEvaled = Agg(), extraEvaled = 1, secondRunNoOp = false) - assert(leftCount == 4, middleCount == 2, rightCount == 1) - - check(middle, expValue = 100, expEvaled = Agg(), extraEvaled = 2, secondRunNoOp = false) - assert(leftCount == 4, middleCount == 3, rightCount == 1) - check(middle, expValue = 100, expEvaled = Agg(), extraEvaled = 2, secondRunNoOp = false) - assert(leftCount == 4, middleCount == 4, rightCount == 1) - } - } - } -} diff --git a/main/test/src/mill/eval/FailureTests.scala b/main/test/src/mill/eval/FailureTests.scala deleted file mode 100644 index dcfbcb60..00000000 --- a/main/test/src/mill/eval/FailureTests.scala +++ /dev/null @@ -1,132 +0,0 @@ -package mill.eval -import mill.T -import mill.util.{TestEvaluator, TestUtil} -import mill.api.Result.OuterStack -import utest._ -import utest.framework.TestPath - - -object FailureTests extends TestSuite{ - - val tests = Tests{ - val graphs = new mill.util.TestGraphs() - import graphs._ - - 'evaluateSingle - { - val check = new TestEvaluator(singleton) - check.fail( - target = singleton.single, - expectedFailCount = 0, - expectedRawValues = Seq(Result.Success(0)) - ) - - singleton.single.failure = Some("lols") - - check.fail( - target = singleton.single, - expectedFailCount = 1, - expectedRawValues = Seq(Result.Failure("lols")) - ) - - singleton.single.failure = None - - check.fail( - target = singleton.single, - expectedFailCount = 0, - expectedRawValues = Seq(Result.Success(0)) - ) - - - val ex = new IndexOutOfBoundsException() - singleton.single.exception = Some(ex) - - - check.fail( - target = singleton.single, - expectedFailCount = 1, - expectedRawValues = Seq(Result.Exception(ex, new OuterStack(Nil))) - ) - } - 'evaluatePair - { - val check = new TestEvaluator(pair) - check.fail( - pair.down, - expectedFailCount = 0, - expectedRawValues = Seq(Result.Success(0)) - ) - - pair.up.failure = Some("lols") - - check.fail( - pair.down, - expectedFailCount = 1, - expectedRawValues = Seq(Result.Skipped) - ) - - pair.up.failure = None - - check.fail( - pair.down, - expectedFailCount = 0, - expectedRawValues = Seq(Result.Success(0)) - ) - - pair.up.exception = Some(new IndexOutOfBoundsException()) - - check.fail( - pair.down, - expectedFailCount = 1, - expectedRawValues = Seq(Result.Skipped) - ) - } - 'evaluateBacktickIdentifiers - { - val check = new TestEvaluator(bactickIdentifiers) - import bactickIdentifiers._ - check.fail( - `a-down-target`, - expectedFailCount = 0, - expectedRawValues = Seq(Result.Success(0)) - ) - - `up-target`.failure = Some("lols") - - check.fail( - `a-down-target`, - expectedFailCount = 1, - expectedRawValues = Seq(Result.Skipped) - ) - - `up-target`.failure = None - - check.fail( - `a-down-target`, - expectedFailCount = 0, - expectedRawValues = Seq(Result.Success(0)) - ) - - `up-target`.exception = Some(new IndexOutOfBoundsException()) - - check.fail( - `a-down-target`, - expectedFailCount = 1, - expectedRawValues = Seq(Result.Skipped) - ) - } - 'multipleUsesOfDest - { - object build extends TestUtil.BaseModule { - // Using `T.ctx( ).dest` twice in a single task is ok - def left = T{ + T.ctx().dest.toString.length + T.ctx().dest.toString.length } - - // Using `T.ctx( ).dest` once in two different tasks is not ok - val task = T.task{ T.ctx().dest.toString.length } - def right = T{ task() + left() + T.ctx().dest.toString().length } - } - - val check = new TestEvaluator(build) - val Right(_) = check(build.left) - val Left(Result.Exception(e, _)) = check(build.right) - assert(e.getMessage.contains("`dest` can only be used in one place")) - } - } -} - diff --git a/main/test/src/mill/eval/JavaCompileJarTests.scala b/main/test/src/mill/eval/JavaCompileJarTests.scala deleted file mode 100644 index 426c6ea6..00000000 --- a/main/test/src/mill/eval/JavaCompileJarTests.scala +++ /dev/null @@ -1,164 +0,0 @@ -package mill.eval - -import mill.define.{Discover, Input, Target, Task} -import mill.modules.Jvm -import mill.api.Ctx.Dest -import mill.{Module, T} -import mill.util.{DummyLogger, Loose, TestEvaluator, TestUtil} -import mill.util.Strict.Agg -import utest._ -import mill._ -object JavaCompileJarTests extends TestSuite{ - def compileAll(sources: mill.util.Loose.Agg[PathRef])(implicit ctx: Dest) = { - os.makeDir.all(ctx.dest) - - os.proc("javac", sources.map(_.path.toString()).toSeq, "-d", ctx.dest).call(ctx.dest) - PathRef(ctx.dest) - } - - val tests = Tests{ - 'javac { - val javacSrcPath = os.pwd / 'main / 'test / 'resources / 'examples / 'javac - val javacDestPath = TestUtil.getOutPath() / 'src - - os.makeDir.all(javacDestPath / os.up) - os.copy(javacSrcPath, javacDestPath) - - object Build extends TestUtil.BaseModule{ - def sourceRootPath = javacDestPath / 'src - def resourceRootPath = javacDestPath / 'resources - - // sourceRoot -> allSources -> classFiles - // | - // v - // resourceRoot ----> jar - def sourceRoot = T.sources{ sourceRootPath } - def resourceRoot = T.sources{ resourceRootPath } - def allSources = T{ sourceRoot().flatMap(p => os.walk(p.path)).map(PathRef(_)) } - def classFiles = T{ compileAll(allSources()) } - def jar = T{ Jvm.createJar(Loose.Agg(classFiles().path) ++ resourceRoot().map(_.path)) } - // Test createJar() with optional file filter. - def filterJar(fileFilter: (os.Path, os.RelPath) => Boolean) = T{ Jvm.createJar(Loose.Agg(classFiles().path) ++ resourceRoot().map(_.path), None, fileFilter) } - - def run(mainClsName: String) = T.command{ - os.proc('java, "-Duser.language=en", "-cp", classFiles().path, mainClsName).call() - } - } - - import Build._ - - var evaluator = new TestEvaluator(Build) - def eval[T](t: Task[T]) = { - evaluator.apply(t) - } - def check(targets: Agg[Task[_]], expected: Agg[Task[_]]) = { - evaluator.check(targets, expected) - } - - def append(path: os.Path, txt: String) = ammonite.ops.write.append(path, txt) - - - check( - targets = Agg(jar), - expected = Agg(allSources, classFiles, jar) - ) - - // Re-running with no changes results in nothing being evaluated - check(targets = Agg(jar), expected = Agg()) - // Appending an empty string gets ignored due to file-content hashing - append(sourceRootPath / "Foo.java", "") - check(targets = Agg(jar), expected = Agg()) - - // Appending whitespace forces a recompile, but the classfilesend up - // exactly the same so no re-jarring. - append(sourceRootPath / "Foo.java", " ") - // Note that `sourceRoot` and `resourceRoot` never turn up in the `expected` - // list, because they are `Source`s not `Target`s - check(targets = Agg(jar), expected = Agg(/*sourceRoot, */allSources, classFiles)) - - // Appending a new class changes the classfiles, which forces us to - // re-create the final jar - append(sourceRootPath / "Foo.java", "\nclass FooTwo{}") - check(targets = Agg(jar), expected = Agg(allSources, classFiles, jar)) - - // Tweaking the resources forces rebuild of the final jar, without - // recompiling classfiles - append(resourceRootPath / "hello.txt", " ") - check(targets = Agg(jar), expected = Agg(jar)) - - // You can swap evaluators halfway without any ill effects - evaluator = new TestEvaluator(Build) - - // Asking for an intermediate target forces things to be build up to that - // target only; these are re-used for any downstream targets requested - append(sourceRootPath / "Bar.java", "\nclass BarTwo{}") - append(resourceRootPath / "hello.txt", " ") - check(targets = Agg(classFiles), expected = Agg(allSources, classFiles)) - check(targets = Agg(jar), expected = Agg(jar)) - check(targets = Agg(allSources), expected = Agg()) - - append(sourceRootPath / "Bar.java", "\nclass BarThree{}") - append(resourceRootPath / "hello.txt", " ") - check(targets = Agg(resourceRoot), expected = Agg()) - check(targets = Agg(allSources), expected = Agg(allSources)) - check(targets = Agg(jar), expected = Agg(classFiles, jar)) - - val jarContents = os.proc('jar, "-tf", evaluator.outPath/'jar/'dest/"out.jar").call(evaluator.outPath).out.string - val expectedJarContents = - """META-INF/MANIFEST.MF - |test/Bar.class - |test/BarThree.class - |test/BarTwo.class - |test/Foo.class - |test/FooTwo.class - |hello.txt - |""".stripMargin - assert(jarContents.linesIterator.toSeq == expectedJarContents.linesIterator.toSeq) - - // Create the Jar again, but this time, filter out the Foo files. - def noFoos(s: String) = !s.contains("Foo") - val filterFunc = (p: os.Path, r: os.RelPath) => noFoos(r.last) - eval(filterJar(filterFunc)) - val filteredJarContents = os.proc('jar, "-tf", evaluator.outPath/'filterJar/'dest/"out.jar").call(evaluator.outPath).out.string - assert(filteredJarContents.linesIterator.toSeq == expectedJarContents.linesIterator.filter(noFoos(_)).toSeq) - - val executed = os.proc('java, "-cp", evaluator.outPath/'jar/'dest/"out.jar", "test.Foo").call(evaluator.outPath).out.string - assert(executed == (31337 + 271828) + System.lineSeparator) - - for(i <- 0 until 3){ - // Build.run is not cached, so every time we eval it it has to - // re-evaluate - val Right((runOutput, evalCount)) = eval(Build.run("test.Foo")) - assert( - runOutput.out.string == (31337 + 271828) + System.lineSeparator, - evalCount == 1 - ) - } - - val Left(Result.Exception(ex, _)) = eval(Build.run("test.BarFour")) - - assert(ex.getMessage.contains("Could not find or load main class")) - - append( - sourceRootPath / "Bar.java", - """ - class BarFour{ - public static void main(String[] args){ - System.out.println("New Cls!"); - } - } - """ - ) - val Right((runOutput2, evalCount2)) = eval(Build.run("test.BarFour")) - assert( - runOutput2.out.string == "New Cls!" + System.lineSeparator, - evalCount2 == 3 - ) - val Right((runOutput3, evalCount3)) = eval(Build.run("test.BarFour")) - assert( - runOutput3.out.string == "New Cls!" + System.lineSeparator, - evalCount3 == 1 - ) - } - } -} diff --git a/main/test/src/mill/eval/ModuleTests.scala b/main/test/src/mill/eval/ModuleTests.scala deleted file mode 100644 index f28fc9b6..00000000 --- a/main/test/src/mill/eval/ModuleTests.scala +++ /dev/null @@ -1,45 +0,0 @@ -package mill.eval - - -import mill.util.{TestEvaluator, TestUtil} -import mill.T -import mill.define.Discover - -import utest._ - -object ModuleTests extends TestSuite{ - object ExternalModule extends mill.define.ExternalModule { - def x = T{13} - object inner extends mill.Module{ - def y = T{17} - } - lazy val millDiscover = Discover[this.type] - } - object Build extends TestUtil.BaseModule{ - def z = T{ ExternalModule.x() + ExternalModule.inner.y() } - } - val tests = Tests { - os.remove.all(TestEvaluator.externalOutPath) - 'externalModuleTargetsAreNamespacedByModulePackagePath - { - val check = new TestEvaluator(Build) - val zresult = check.apply(Build.z) - assert( - zresult == Right((30, 1)), - os.read(check.evaluator.outPath / 'z / "meta.json").contains("30"), - os.read(TestEvaluator.externalOutPath / 'mill / 'eval / 'ModuleTests / 'ExternalModule / 'x / "meta.json").contains("13"), - os.read(TestEvaluator.externalOutPath / 'mill / 'eval / 'ModuleTests / 'ExternalModule / 'inner / 'y / "meta.json").contains("17") - ) - } - 'externalModuleMustBeGlobalStatic - { - - - object Build extends mill.define.ExternalModule { - - def z = T{ ExternalModule.x() + ExternalModule.inner.y() } - lazy val millDiscover = Discover[this.type] - } - - intercept[java.lang.AssertionError]{ Build } - } - } -} diff --git a/main/test/src/mill/eval/TarjanTests.scala b/main/test/src/mill/eval/TarjanTests.scala deleted file mode 100644 index 2f9d0a4d..00000000 --- a/main/test/src/mill/eval/TarjanTests.scala +++ /dev/null @@ -1,91 +0,0 @@ -package mill.eval - -import utest._ - -object TarjanTests extends TestSuite{ - def check(input: Seq[Seq[Int]], expected: Seq[Seq[Int]]) = { - val result = Tarjans(input).map(_.sorted) - val sortedExpected = expected.map(_.sorted) - assert(result == sortedExpected) - } - val tests = Tests{ - // - 'empty - check(Seq(), Seq()) - - // (0) - 'singleton - check(Seq(Seq()), Seq(Seq(0))) - - - // (0)-. - // ^._/ - 'selfCycle - check(Seq(Seq(0)), Seq(Seq(0))) - - // (0) <-> (1) - 'simpleCycle- check(Seq(Seq(1), Seq(0)), Seq(Seq(1, 0))) - - // (0) (1) (2) - 'multipleSingletons - check( - Seq(Seq(), Seq(), Seq()), - Seq(Seq(0), Seq(1), Seq(2)) - ) - - // (0) -> (1) -> (2) - 'straightLineNoCycles- check( - Seq(Seq(1), Seq(2), Seq()), - Seq(Seq(2), Seq(1), Seq(0)) - ) - - // (0) <- (1) <- (2) - 'straightLineNoCyclesReversed- check( - Seq(Seq(), Seq(0), Seq(1)), - Seq(Seq(0), Seq(1), Seq(2)) - ) - - // (0) <-> (1) (2) -> (3) -> (4) - // ^.____________/ - 'independentSimpleCycles - check( - Seq(Seq(1), Seq(0), Seq(3), Seq(4), Seq(2)), - Seq(Seq(1, 0), Seq(4, 3, 2)) - ) - - // ___________________ - // v \ - // (0) <-> (1) (2) -> (3) -> (4) - // ^.____________/ - 'independentLinkedCycles - check( - Seq(Seq(1), Seq(0), Seq(3), Seq(4), Seq(2, 1)), - Seq(Seq(1, 0), Seq(4, 3, 2)) - ) - // _____________ - // / v - // (0) <-> (1) (2) -> (3) -> (4) - // ^.____________/ - 'independentLinkedCycles2 - check( - Seq(Seq(1, 2), Seq(0), Seq(3), Seq(4), Seq(2)), - Seq(Seq(4, 3, 2), Seq(1, 0)) - ) - - // _____________ - // / v - // (0) <-> (1) (2) -> (3) -> (4) - // ^. ^.____________/ - // \________________/ - 'combinedCycles - check( - Seq(Seq(1, 2), Seq(0), Seq(3), Seq(4), Seq(2, 1)), - Seq(Seq(4, 3, 2, 1, 0)) - ) - // - // (0) <-> (1) <- (2) <- (3) <-> (4) <- (5) - // ^.____________/ / / - // / / - // (6) <- (7) <-/ (8) <-' - // / / - // v / - // (9) <--------' - 'combinedCycles - check( - Seq(Seq(1), Seq(0), Seq(0, 1), Seq(2, 4, 7, 9), Seq(3), Seq(4, 8), Seq(9), Seq(6), Seq(), Seq()), - Seq(Seq(0, 1), Seq(2), Seq(9), Seq(6), Seq(7), Seq(3, 4), Seq(8), Seq(5)) - ) - - } -} \ No newline at end of file diff --git a/main/test/src/mill/eval/TaskTests.scala b/main/test/src/mill/eval/TaskTests.scala deleted file mode 100644 index 0bfd8efc..00000000 --- a/main/test/src/mill/eval/TaskTests.scala +++ /dev/null @@ -1,95 +0,0 @@ -package mill.eval - -import utest._ - -import mill.T - -import mill.util.TestEvaluator -object TaskTests extends TestSuite{ - val tests = Tests{ - object build extends mill.util.TestUtil.BaseModule{ - var count = 0 - // Explicitly instantiate `Function1` objects to make sure we get - // different instances each time - def staticWorker = T.worker{ - new Function1[Int, Int] { - def apply(v1: Int) = v1 + 1 - } - } - def noisyWorker = T.worker{ - new Function1[Int, Int] { - def apply(v1: Int) = input() + 1 - } - } - def input = T.input{ - count += 1 - count - } - def task = T.task{ - count += 1 - count - } - def taskInput = T{ input() } - def taskNoInput = T{ task() } - - def persistent = T.persistent{ - input() // force re-computation - os.makeDir.all(T.ctx().dest) - os.write.append(T.ctx().dest/'count, "hello\n") - os.read.lines(T.ctx().dest/'count).length - } - def nonPersistent = T{ - input() // force re-computation - os.makeDir.all(T.ctx().dest) - os.write.append(T.ctx().dest/'count, "hello\n") - os.read.lines(T.ctx().dest/'count).length - } - - def staticWorkerDownstream = T{ - staticWorker().apply(1) - } - def noisyWorkerDownstream = T{ - noisyWorker().apply(1) - } - } - - 'inputs - { - // Inputs always re-evaluate, including forcing downstream cached Targets - // to re-evaluate, but normal Tasks behind a Target run once then are cached - val check = new TestEvaluator(build) - - val Right((1, 1)) = check.apply(build.taskInput) - val Right((2, 1)) = check.apply(build.taskInput) - val Right((3, 1)) = check.apply(build.taskInput) - - val Right((4, 1)) = check.apply(build.taskNoInput) - val Right((4, 0)) = check.apply(build.taskNoInput) - val Right((4, 0)) = check.apply(build.taskNoInput) - } - - 'persistent - { - // Persistent tasks keep the working dir around between runs - val check = new TestEvaluator(build) - val Right((1, 1)) = check.apply(build.persistent) - val Right((2, 1)) = check.apply(build.persistent) - val Right((3, 1)) = check.apply(build.persistent) - - val Right((1, 1)) = check.apply(build.nonPersistent) - val Right((1, 1)) = check.apply(build.nonPersistent) - val Right((1, 1)) = check.apply(build.nonPersistent) - } - - 'worker - { - // Persistent task - def check = new TestEvaluator(build) - - val Right((2, 1)) = check.apply(build.noisyWorkerDownstream) - val Right((3, 1)) = check.apply(build.noisyWorkerDownstream) - val Right((4, 1)) = check.apply(build.noisyWorkerDownstream) - - val Right((2, 1)) = check.apply(build.staticWorkerDownstream) - val Right((2, 0)) = check.apply(build.staticWorkerDownstream) - val Right((2, 0)) = check.apply(build.staticWorkerDownstream) - } - } -} diff --git a/main/test/src/mill/main/ClientServerTests.scala b/main/test/src/mill/main/ClientServerTests.scala deleted file mode 100644 index 05238a5f..00000000 --- a/main/test/src/mill/main/ClientServerTests.scala +++ /dev/null @@ -1,214 +0,0 @@ -package mill.main -import java.io._ - -import mill.main.client.{Util, Locks} - -import scala.collection.JavaConverters._ -import utest._ -class EchoServer extends MillServerMain[Int]{ - def main0(args: Array[String], - stateCache: Option[Int], - mainInteractive: Boolean, - stdin: InputStream, - stdout: PrintStream, - stderr: PrintStream, - env: Map[String, String], - setIdle: Boolean => Unit) = { - - val reader = new BufferedReader(new InputStreamReader(stdin)) - val str = reader.readLine() - if (args.nonEmpty){ - stdout.println(str + args(0)) - } - env.toSeq.sortBy(_._1).foreach{ - case (key, value) => stdout.println(s"$key=$value") - } - stdout.flush() - if (args.nonEmpty){ - stderr.println(str.toUpperCase + args(0)) - } - stderr.flush() - (true, None) - } -} - -object ClientServerTests extends TestSuite{ - def initStreams() = { - val in = new ByteArrayInputStream("hello\n".getBytes()) - val out = new ByteArrayOutputStream() - val err = new ByteArrayOutputStream() - (in, out, err) - } - def init() = { - val tmpDir = java.nio.file.Files.createTempDirectory("") - val locks = Locks.memory() - - (tmpDir, locks) - } - - def spawnEchoServer(tmpDir : java.nio.file.Path, locks: Locks): Unit = { - new Thread(() => new Server( - tmpDir.toString, - new EchoServer(), - () => (), - 1000, - locks - ).run()).start() - } - - def runClientAux(tmpDir : java.nio.file.Path, locks: Locks) - (env : Map[String, String], args: Array[String]) = { - val (in, out, err) = initStreams() - Server.lockBlock(locks.clientLock){ - mill.main.client.MillClientMain.run( - tmpDir.toString, - () => spawnEchoServer(tmpDir, locks), - locks, - in, - out, - err, - args, - env.asJava - ) - Thread.sleep(100) - (new String(out.toByteArray), new String(err.toByteArray)) - } - } - - def tests = Tests{ - 'hello - { - if (!Util.isWindows){ - val (tmpDir, locks) = init() - def runClient(s: String) = runClientAux(tmpDir, locks)(Map.empty, Array(s)) - - // Make sure the simple "have the client start a server and - // exchange one message" workflow works from end to end. - - assert( - locks.clientLock.probe(), - locks.serverLock.probe(), - locks.processLock.probe() - ) - - val (out1, err1) = runClient("world") - - assert( - out1 == "helloworld\n", - err1 == "HELLOworld\n" - ) - - // Give a bit of time for the server to release the lock and - // re-acquire it to signal to the client that it's done - Thread.sleep(100) - - assert( - locks.clientLock.probe(), - !locks.serverLock.probe(), - !locks.processLock.probe() - ) - - // A seecond client in sequence connect to the same server - val (out2, err2) = runClient(" WORLD") - - assert( - out2 == "hello WORLD\n", - err2 == "HELLO WORLD\n" - ) - - // Make sure the server times out of not used for a while - Thread.sleep(2000) - assert( - locks.clientLock.probe(), - locks.serverLock.probe(), - locks.processLock.probe() - ) - - // Have a third client spawn/connect-to a new server at the same path - val (out3, err3) = runClient(" World") - assert( - out3 == "hello World\n", - err3 == "HELLO World\n" - ) - } - - 'envVars - { - if (!Util.isWindows){ - val (tmpDir, locks) = init() - - def runClient(env : Map[String, String]) = runClientAux(tmpDir, locks)(env, Array()) - - // Make sure the simple "have the client start a server and - // exchange one message" workflow works from end to end. - - assert( - locks.clientLock.probe(), - locks.serverLock.probe(), - locks.processLock.probe() - ) - - def longString(s : String) = Array.fill(1000)(s).mkString - val b1000 = longString("b") - val c1000 = longString("c") - val a1000 = longString("a") - - val env = Map( - "a" -> a1000, - "b" -> b1000, - "c" -> c1000 - ) - - - val (out1, err1) = runClient(env) - val expected = s"a=$a1000\nb=$b1000\nc=$c1000\n" - - assert( - out1 == expected, - err1 == "" - ) - - // Give a bit of time for the server to release the lock and - // re-acquire it to signal to the client that it's done - Thread.sleep(100) - - assert( - locks.clientLock.probe(), - !locks.serverLock.probe(), - !locks.processLock.probe() - ) - - val path = List( - "/Users/foo/Library/Haskell/bin", - "/usr/local/git/bin", - "/sw/bin/", - "/usr/local/bin", - "/usr/local/", - "/usr/local/sbin", - "/usr/local/mysql/bin", - "/usr/local/bin", - "/usr/bin", - "/bin", - "/usr/sbin", - "/sbin", - "/opt/X11/bin", - "/usr/local/MacGPG2/bin", - "/Library/TeX/texbin", - "/usr/local/bin/", - "/Users/foo/bin", - "/Users/foo/go/bin", - "~/.bloop" - ) - - val pathEnvVar = path.mkString(":") - val (out2, err2) = runClient(Map("PATH" -> pathEnvVar)) - - val expected2 = s"PATH=$pathEnvVar\n" - - assert( - out2 == expected2, - err2 == "" - ) - } - } - } - } -} diff --git a/main/test/src/mill/main/ForeignBuildsTest.scala b/main/test/src/mill/main/ForeignBuildsTest.scala deleted file mode 100644 index cfc8d00c..00000000 --- a/main/test/src/mill/main/ForeignBuildsTest.scala +++ /dev/null @@ -1,30 +0,0 @@ -package mill.main - -import mill.util.ScriptTestSuite -import utest._ - -object ForeignBuildsTest extends ScriptTestSuite(fork = false) { - def workspaceSlug = "foreign-builds" - def scriptSourcePath = - os.pwd / 'main / 'test / 'resources / 'examples / 'foreign - override def buildPath = os.rel / 'project / "build.sc" - - val tests = Tests { - initWorkspace() - 'test - { - // See https://github.com/lihaoyi/mill/issues/302 - if (!ammonite.util.Util.java9OrAbove) { - assert( - eval("checkProjectPaths"), - eval("checkInnerPaths"), - eval("checkOuterPaths"), - eval("checkOuterInnerPaths"), - eval("checkProjectDests"), - eval("checkInnerDests"), - eval("checkOuterDests"), - eval("checkOuterInnerDests") - ) - } - } - } -} diff --git a/main/test/src/mill/main/ForeignConflictTest.scala b/main/test/src/mill/main/ForeignConflictTest.scala deleted file mode 100644 index a4352bb6..00000000 --- a/main/test/src/mill/main/ForeignConflictTest.scala +++ /dev/null @@ -1,25 +0,0 @@ -package mill.main - - -import mill.util.ScriptTestSuite -import utest._ - -object ForeignConflictTest extends ScriptTestSuite(fork = false) { - def workspaceSlug = "foreign-conflict" - def scriptSourcePath = - os.pwd / 'main / 'test / 'resources / 'examples / 'foreign - override def buildPath = os.rel / 'conflict / "build.sc" - - val tests = Tests { - initWorkspace() - 'test - { - // see https://github.com/lihaoyi/mill/issues/302 - if (!ammonite.util.Util.java9OrAbove) { - assert( - eval("checkPaths"), - eval("checkDests") - ) - } - } - } -} diff --git a/main/test/src/mill/main/JavaCompileJarTests.scala b/main/test/src/mill/main/JavaCompileJarTests.scala deleted file mode 100644 index 37c64b05..00000000 --- a/main/test/src/mill/main/JavaCompileJarTests.scala +++ /dev/null @@ -1,67 +0,0 @@ -package mill.main - -import mill.util.ScriptTestSuite -import utest._ - -object JavaCompileJarTests extends ScriptTestSuite(fork = false) { - def workspaceSlug = "java-compile-jar" - def scriptSourcePath = os.pwd / 'main / 'test / 'resources / 'examples / 'javac - val tests = Tests{ - initWorkspace() - 'test - { - if (!ammonite.util.Util.java9OrAbove) { - // Basic target evaluation works - assert(eval("classFiles")) - assert(eval("jar")) - - val classFiles1 = meta("classFiles") - val jar1 = meta("jar") - - assert(eval("classFiles")) - assert(eval("jar")) - - // Repeated evaluation has the same results - val classFiles2 = meta("classFiles") - val jar2 = meta("jar") - - assert( - jar1 == jar2, - classFiles1 == classFiles2 - ) - - // If we update resources, classFiles are unchanged but jar changes - for(scalaFile <- os.walk(workspacePath).filter(_.ext == "txt")){ - os.write.append(scalaFile, "\n") - } - - assert(eval("classFiles")) - assert(eval("jar")) - - val classFiles3 = meta("classFiles") - val jar3 = meta("jar") - - assert( - jar2 != jar3, - classFiles2 == classFiles3 - ) - - // We can intentionally break the code, have the targets break, then - // fix the code and have them recover. - for(scalaFile <- os.walk(workspacePath).filter(_.ext == "java")){ - os.write.append(scalaFile, "\n}") - } - - assert(!eval("classFiles")) - assert(!eval("jar")) - - for(scalaFile <- os.walk(workspacePath).filter(_.ext == "java")){ - os.write.over(scalaFile, os.read(scalaFile).dropRight(2)) - } - - assert(eval("classFiles")) - assert(eval("jar")) - } - } - } -} - diff --git a/main/test/src/mill/main/MainTests.scala b/main/test/src/mill/main/MainTests.scala deleted file mode 100644 index e836099c..00000000 --- a/main/test/src/mill/main/MainTests.scala +++ /dev/null @@ -1,272 +0,0 @@ -package mill.main - -import mill.define.{Discover, Segment, Task} -import mill.util.TestGraphs._ - -import utest._ -object MainTests extends TestSuite{ - - def check[T <: mill.define.BaseModule](module: T)( - selectorString: String, - expected0: Either[String, Seq[T => Task[_]]])= { - - val expected = expected0.map(_.map(_(module))) - val resolved = for{ - selectors <- mill.util.ParseArgs(Seq(selectorString), multiSelect = false).map(_._1.head) - val crossSelectors = selectors._2.value.map{case Segment.Cross(x) => x.toList.map(_.toString) case _ => Nil} - task <- mill.main.ResolveTasks.resolve( - selectors._2.value.toList, module, module.millDiscover, Nil, crossSelectors.toList, Nil - ) - } yield task - assert(resolved == expected) - } - val tests = Tests{ - val graphs = new mill.util.TestGraphs() - import graphs._ - 'single - { - val check = MainTests.check(singleton) _ - 'pos - check("single", Right(Seq(_.single))) - 'neg1 - check("sngle", Left("Cannot resolve sngle. Did you mean single?")) - 'neg2 - check("snigle", Left("Cannot resolve snigle. Did you mean single?")) - 'neg3 - check("nsiigle", Left("Cannot resolve nsiigle. Did you mean single?")) - 'neg4 - check("ansiigle", Left("Cannot resolve ansiigle. Try `mill resolve _` to see what's available.")) - 'neg5 - check("doesntExist", Left("Cannot resolve doesntExist. Try `mill resolve _` to see what's available.")) - 'neg6 - check("single.doesntExist", Left("Task single is not a module and has no children.")) - 'neg7 - check("", Left("Selector cannot be empty")) - } - 'backtickIdentifiers - { - val check = MainTests.check(bactickIdentifiers) _ - 'pos1 - check("up-target", Right(Seq(_.`up-target`))) - 'pos2 - check("a-down-target", Right(Seq(_.`a-down-target`))) - 'neg1 - check("uptarget", Left("Cannot resolve uptarget. Did you mean up-target?")) - 'neg2 - check("upt-arget", Left("Cannot resolve upt-arget. Did you mean up-target?")) - 'neg3 - check("up-target.doesntExist", Left("Task up-target is not a module and has no children.")) - 'neg4 - check("", Left("Selector cannot be empty")) - 'neg5 - check("invisible&", Left("Cannot resolve invisible. Try `mill resolve _` to see what's available.")) - 'nested - { - 'pos - check("nested-module.nested-target", Right(Seq(_.`nested-module`.`nested-target`))) - 'neg - check("nested-module.doesntExist", Left("Cannot resolve nested-module.doesntExist. Try `mill resolve nested-module._` to see what's available.")) - } - } - 'nested - { - val check = MainTests.check(nestedModule) _ - 'pos1 - check("single", Right(Seq(_.single))) - 'pos2 - check("nested.single", Right(Seq(_.nested.single))) - 'pos3 - check("classInstance.single", Right(Seq(_.classInstance.single))) - 'neg1 - check( - "doesntExist", - Left("Cannot resolve doesntExist. Try `mill resolve _` to see what's available.") - ) - 'neg2 - check( - "single.doesntExist", - Left("Task single is not a module and has no children.") - ) - 'neg3 - check( - "nested.doesntExist", - Left("Cannot resolve nested.doesntExist. Try `mill resolve nested._` to see what's available.") - ) - 'neg3 - check( - "nested.singel", - Left("Cannot resolve nested.singel. Did you mean nested.single?") - ) - 'neg4 - check( - "classInstance.doesntExist", - Left("Cannot resolve classInstance.doesntExist. Try `mill resolve classInstance._` to see what's available.") - ) - 'wildcard - check( - "_.single", - Right(Seq( - _.classInstance.single, - _.nested.single - )) - ) - 'wildcardNeg - check( - "_._.single", - Left("Cannot resolve _._.single. Try `mill resolve _` to see what's available") - ) - 'wildcardNeg2 - check( - "_._.__", - Left("Cannot resolve _._.__. Try `mill resolve _` to see what's available") - ) - 'wildcardNeg3 - check( - "nested._.foobar", - Left("Cannot resolve nested._.foobar. Try `mill resolve nested._` to see what's available") - ) - 'wildcard2 - check( - "__.single", - Right(Seq( - _.single, - _.classInstance.single, - _.nested.single - )) - ) - - 'wildcard3 - check( - "_.__.single", - Right(Seq( - _.classInstance.single, - _.nested.single - )) - ) - - } - 'cross - { - 'single - { - val check = MainTests.check(singleCross) _ - 'pos1 - check("cross[210].suffix", Right(Seq(_.cross("210").suffix))) - 'pos2 - check("cross[211].suffix", Right(Seq(_.cross("211").suffix))) - 'neg1 - check( - "cross[210].doesntExist", - Left("Cannot resolve cross[210].doesntExist. Try `mill resolve cross[210]._` to see what's available.") - ) - 'neg2 - check( - "cross[doesntExist].doesntExist", - Left("Cannot resolve cross[doesntExist]. Try `mill resolve cross[__]` to see what's available.") - ) - 'neg3 - check( - "cross[221].doesntExist", - Left("Cannot resolve cross[221]. Did you mean cross[211]?") - ) - 'neg4 - check( - "cross[doesntExist].suffix", - Left("Cannot resolve cross[doesntExist]. Try `mill resolve cross[__]` to see what's available.") - ) - 'wildcard - check( - "cross[_].suffix", - Right(Seq( - _.cross("210").suffix, - _.cross("211").suffix, - _.cross("212").suffix - )) - ) - 'wildcard2 - check( - "cross[__].suffix", - Right(Seq( - _.cross("210").suffix, - _.cross("211").suffix, - _.cross("212").suffix - )) - ) - } - 'double - { - val check = MainTests.check(doubleCross) _ - 'pos1 - check( - "cross[210,jvm].suffix", - Right(Seq(_.cross("210", "jvm").suffix)) - ) - 'pos2 - check( - "cross[211,jvm].suffix", - Right(Seq(_.cross("211", "jvm").suffix)) - ) - 'wildcard - { - 'labelNeg - check( - "_.suffix", - Left("Cannot resolve _.suffix. Try `mill resolve _._` to see what's available.") - ) - 'labelPos - check( - "__.suffix", - Right(Seq( - _.cross("210", "jvm").suffix, - _.cross("210", "js").suffix, - - _.cross("211", "jvm").suffix, - _.cross("211", "js").suffix, - - _.cross("212", "jvm").suffix, - _.cross("212", "js").suffix, - _.cross("212", "native").suffix - )) - ) - 'first - check( - "cross[_,jvm].suffix", - Right(Seq( - _.cross("210", "jvm").suffix, - _.cross("211", "jvm").suffix, - _.cross("212", "jvm").suffix - )) - ) - 'second - check( - "cross[210,_].suffix", - Right(Seq( - _.cross("210", "jvm").suffix, - _.cross("210", "js").suffix - )) - ) - 'both - check( - "cross[_,_].suffix", - Right(Seq( - _.cross("210", "jvm").suffix, - _.cross("210", "js").suffix, - - _.cross("211", "jvm").suffix, - _.cross("211", "js").suffix, - - _.cross("212", "jvm").suffix, - _.cross("212", "js").suffix, - _.cross("212", "native").suffix - )) - ) - 'both2 - check( - "cross[__].suffix", - Right(Seq( - _.cross("210", "jvm").suffix, - _.cross("210", "js").suffix, - - _.cross("211", "jvm").suffix, - _.cross("211", "js").suffix, - - _.cross("212", "jvm").suffix, - _.cross("212", "js").suffix, - _.cross("212", "native").suffix - )) - ) - } - } - 'nested - { - val check = MainTests.check(nestedCrosses) _ - 'pos1 - check( - "cross[210].cross2[js].suffix", - Right(Seq(_.cross("210").cross2("js").suffix)) - ) - 'pos2 - check( - "cross[211].cross2[jvm].suffix", - Right(Seq(_.cross("211").cross2("jvm").suffix)) - ) - 'wildcard - { - 'first - check( - "cross[_].cross2[jvm].suffix", - Right(Seq( - _.cross("210").cross2("jvm").suffix, - _.cross("211").cross2("jvm").suffix, - _.cross("212").cross2("jvm").suffix - )) - ) - 'second - check( - "cross[210].cross2[_].suffix", - Right(Seq( - _.cross("210").cross2("jvm").suffix, - _.cross("210").cross2("js").suffix, - _.cross("210").cross2("native").suffix - )) - ) - 'both - check( - "cross[_].cross2[_].suffix", - Right(Seq( - _.cross("210").cross2("jvm").suffix, - _.cross("210").cross2("js").suffix, - _.cross("210").cross2("native").suffix, - - _.cross("211").cross2("jvm").suffix, - _.cross("211").cross2("js").suffix, - _.cross("211").cross2("native").suffix, - - _.cross("212").cross2("jvm").suffix, - _.cross("212").cross2("js").suffix, - _.cross("212").cross2("native").suffix - )) - ) - } - } - } - } -} diff --git a/main/test/src/mill/util/ParseArgsTest.scala b/main/test/src/mill/util/ParseArgsTest.scala deleted file mode 100644 index e31baf4f..00000000 --- a/main/test/src/mill/util/ParseArgsTest.scala +++ /dev/null @@ -1,254 +0,0 @@ -package mill.util - -import mill.define.{Segment, Segments} -import mill.define.Segment.{Cross, Label} -import utest._ - -object ParseArgsTest extends TestSuite { - - val tests = Tests { - 'extractSelsAndArgs - { - def check(input: Seq[String], - expectedSelectors: Seq[String], - expectedArgs: Seq[String], - multiSelect: Boolean) = { - val (selectors, args) = ParseArgs.extractSelsAndArgs(input, multiSelect) - - assert( - selectors == expectedSelectors, - args == expectedArgs - ) - } - - 'empty - check(input = Seq.empty, - expectedSelectors = Seq.empty, - expectedArgs = Seq.empty, - multiSelect = false) - 'singleSelector - check( - input = Seq("core.compile"), - expectedSelectors = Seq("core.compile"), - expectedArgs = Seq.empty, - multiSelect = false - ) - 'singleSelectorWithArgs - check( - input = Seq("application.run", "hello", "world"), - expectedSelectors = Seq("application.run"), - expectedArgs = Seq("hello", "world"), - multiSelect = false - ) - 'singleSelectorWithAllInArgs - check( - input = Seq("application.run", "hello", "world", "--all"), - expectedSelectors = Seq("application.run"), - expectedArgs = Seq("hello", "world", "--all"), - multiSelect = false - ) - 'multiSelectors - check( - input = Seq("core.jar", "core.docJar", "core.sourcesJar"), - expectedSelectors = Seq("core.jar", "core.docJar", "core.sourcesJar"), - expectedArgs = Seq.empty, - multiSelect = true - ) - 'multiSelectorsSeq - check( - input = Seq("core.jar", "core.docJar", "core.sourcesJar"), - expectedSelectors = Seq("core.jar", "core.docJar", "core.sourcesJar"), - expectedArgs = Seq.empty, - multiSelect = true - ) - 'multiSelectorsWithArgs - check( - input = Seq("core.compile", - "application.runMain", - "--", - "Main", - "hello", - "world"), - expectedSelectors = Seq("core.compile", "application.runMain"), - expectedArgs = Seq("Main", "hello", "world"), - multiSelect = true - ) - 'multiSelectorsWithArgsWithAllInArgs - check( - input = Seq("core.compile", - "application.runMain", - "--", - "Main", - "--all", - "world"), - expectedSelectors = Seq("core.compile", "application.runMain"), - expectedArgs = Seq("Main", "--all", "world"), - multiSelect = true - ) - } - 'expandBraces - { - def check(input: String, expectedExpansion: List[String]) = { - val Right(expanded) = ParseArgs.expandBraces(input) - - assert(expanded == expectedExpansion) - } - - 'expandLeft - check( - "{application,core}.compile", - List("application.compile", "core.compile") - ) - 'expandRight - check( - "application.{jar,docJar,sourcesJar}", - List("application.jar", "application.docJar", "application.sourcesJar") - ) - 'expandBoth - check( - "{core,application}.{jar,docJar}", - List( - "core.jar", - "core.docJar", - "application.jar", - "application.docJar" - ) - ) - 'expandNested - { - check("{hello,world.{cow,moo}}", - List("hello", "world.cow", "world.moo")) - check("{a,b{c,d}}", List("a", "bc", "bd")) - check("{a,b,{c,d}}", List("a", "b", "c", "d")) - check("{a,b{c,d{e,f}}}", List("a", "bc", "bde", "bdf")) - check("{a{b,c},d}", List("ab", "ac", "d")) - check("{a,{b,c}d}", List("a", "bd", "cd")) - check("{a{b,c},d{e,f}}", List("ab", "ac", "de", "df")) - check("{a,b{c,d},e{f,g}}", List("a", "bc", "bd", "ef", "eg")) - } - 'expandMixed - check( - "{a,b}.{c}.{}.e", - List("a.{c}.{}.e", "b.{c}.{}.e") - ) - 'malformed - { - val malformed = Seq("core.{compile", "core.{compile,test]") - - malformed.foreach { m => - val Left(error) = ParseArgs.expandBraces(m) - assert(error.contains("Parsing exception")) - } - } - 'dontExpand - { - check("core.compile", List("core.compile")) - check("{}.compile", List("{}.compile")) - check("{core}.compile", List("{core}.compile")) - } - 'keepUnknownSymbols - { - check("{a,b}.e<>", List("a.e<>", "b.e<>")) - check("a[99]&&", List("a[99]&&")) - check( - "{a,b}.<%%>.{c,d}", - List("a.<%%>.c", "a.<%%>.d", "b.<%%>.c", "b.<%%>.d") - ) - } - } - - 'apply - { - def check(input: Seq[String], - expectedSelectors: List[(Option[List[Segment]], List[Segment])], - expectedArgs: Seq[String], - multiSelect: Boolean) = { - val Right((selectors0, args)) = ParseArgs(input, multiSelect) - - val selectors = selectors0.map{ - case (Some(v1), v2) => (Some(v1.value), v2.value) - case (None, v2) => (None, v2.value) - } - assert( - selectors == expectedSelectors, - args == expectedArgs - ) - } - - 'rejectEmpty { - assert(ParseArgs(Seq.empty, multiSelect = false) == Left("Selector cannot be empty")) - } - 'singleSelector - check( - input = Seq("core.compile"), - expectedSelectors = List( - None -> List(Label("core"), Label("compile")) - ), - expectedArgs = Seq.empty, - multiSelect = false - ) - 'externalSelector - check( - input = Seq("foo.bar/core.compile"), - expectedSelectors = List( - Some(List(Label("foo"), Label("bar"))) -> List(Label("core"), Label("compile")) - ), - expectedArgs = Seq.empty, - multiSelect = false - ) - 'singleSelectorWithArgs - check( - input = Seq("application.run", "hello", "world"), - expectedSelectors = List( - None -> List(Label("application"), Label("run")) - ), - expectedArgs = Seq("hello", "world"), - multiSelect = false - ) - 'singleSelectorWithCross - check( - input = Seq("bridges[2.12.4,jvm].compile"), - expectedSelectors = List( - None -> List(Label("bridges"), Cross(Seq("2.12.4", "jvm")), Label("compile")) - ), - expectedArgs = Seq.empty, - multiSelect = false - ) - 'multiSelectorsBraceExpansion - check( - input = Seq("{core,application}.compile"), - expectedSelectors = List( - None -> List(Label("core"), Label("compile")), - None -> List(Label("application"), Label("compile")) - ), - expectedArgs = Seq.empty, - multiSelect = true - ) - 'multiSelectorsBraceExpansionWithArgs - check( - input = Seq("{core,application}.run", "--", "hello", "world"), - expectedSelectors = List( - None -> List(Label("core"), Label("run")), - None -> List(Label("application"), Label("run")) - ), - expectedArgs = Seq("hello", "world"), - multiSelect = true - ) - 'multiSelectorsBraceExpansionWithCross - check( - input = Seq("bridges[2.12.4,jvm].{test,jar}"), - expectedSelectors = List( - None -> List(Label("bridges"), Cross(Seq("2.12.4", "jvm")), Label("test")), - None -> List(Label("bridges"), Cross(Seq("2.12.4", "jvm")), Label("jar")) - ), - expectedArgs = Seq.empty, - multiSelect = true - ) - 'multiSelectorsBraceExpansionInsideCross - check( - input = Seq("bridges[{2.11.11,2.11.8,2.13.0-M3}].jar"), - expectedSelectors = List( - None -> List(Label("bridges"), Cross(Seq("2.11.11")), Label("jar")), - None -> List(Label("bridges"), Cross(Seq("2.11.8")), Label("jar")), - None -> List(Label("bridges"), Cross(Seq("2.13.0-M3")), Label("jar")) - ), - expectedArgs = Seq.empty, - multiSelect = true - ) - 'multiSelectorsBraceExpansionWithoutAll - { - val res = ParseArgs(Seq("{core,application}.compile"), multiSelect = false) - val expected = Right( - List( - None -> Segments(Label("core"), Label("compile")), - None -> Segments(Label("application"), Label("compile")) - ), - Nil - ) - assert(res == expected) - } - 'multiSelectorsWithoutAllAsSingle - check( - // this is how it works when we pass multiple tasks without --all flag - input = Seq("core.compile", "application.compile"), - expectedSelectors = List( - None -> List(Label("core"), Label("compile")) - ), - expectedArgs = Seq("application.compile"), - multiSelect = false - ) - } - } - -} diff --git a/main/test/src/mill/util/ScriptTestSuite.scala b/main/test/src/mill/util/ScriptTestSuite.scala deleted file mode 100644 index f448aaaf..00000000 --- a/main/test/src/mill/util/ScriptTestSuite.scala +++ /dev/null @@ -1,53 +0,0 @@ -package mill.util - -import java.io.{ByteArrayInputStream, ByteArrayOutputStream, PrintStream} - -import utest._ - -abstract class ScriptTestSuite(fork: Boolean) extends TestSuite{ - def workspaceSlug: String - def scriptSourcePath: os.Path - def buildPath: os.RelPath = "build.sc" - - val workspacePath = os.pwd / 'target / 'workspace / workspaceSlug - val wd = workspacePath / buildPath / os.up - val stdOutErr = System.out // new PrintStream(new ByteArrayOutputStream()) - val stdIn = new ByteArrayInputStream(Array()) - val disableTicker = false - val debugLog = false - lazy val runner = new mill.main.MainRunner( - ammonite.main.Cli.Config(wd = wd), disableTicker, - stdOutErr, stdOutErr, stdIn, None, Map.empty, - b => (), debugLog - ) - def eval(s: String*) = { - if (!fork) runner.runScript(workspacePath / buildPath , s.toList) - else{ - try { - os.proc(os.home / "mill-release", "-i", s).call( - wd, - stdin = os.Inherit, - stdout = os.Inherit, - stderr = os.Inherit, - ) - true - }catch{case e: Throwable => false} - } - } - def meta(s: String) = { - val (List(selector), args) = ParseArgs.apply(Seq(s), multiSelect = false).right.get - - os.read(wd / "out" / selector._2.value.flatMap(_.pathSegments) / "meta.json") - } - - - def initWorkspace() = { - os.remove.all(workspacePath) - os.makeDir.all(workspacePath / os.up) - // The unzipped git repo snapshots we get from github come with a - // wrapper-folder inside the zip file, so copy the wrapper folder to the - // destination instead of the folder containing the wrapper. - - os.copy(scriptSourcePath, workspacePath) - } -} diff --git a/main/test/src/mill/util/TestEvaluator.scala b/main/test/src/mill/util/TestEvaluator.scala deleted file mode 100644 index 9a235679..00000000 --- a/main/test/src/mill/util/TestEvaluator.scala +++ /dev/null @@ -1,80 +0,0 @@ -package mill.util - -import mill.define.{Input, Target, Task} -import mill.api.Result.OuterStack -import mill.eval.{Evaluator, Result} -import mill.util.Strict.Agg -import utest.assert -import utest.framework.TestPath - -import language.experimental.macros -object TestEvaluator{ - val externalOutPath = os.pwd / 'target / 'external - - - def static(module: TestUtil.BaseModule)(implicit fullName: sourcecode.FullName) = { - new TestEvaluator(module)(fullName, TestPath(Nil)) - } -} - -class TestEvaluator(module: TestUtil.BaseModule) - (implicit fullName: sourcecode.FullName, - tp: TestPath){ - val outPath = TestUtil.getOutPath() - -// val logger = DummyLogger - val logger = new PrintLogger( - colored = true, disableTicker=false, - ammonite.util.Colors.Default, System.out, System.out, System.err, System.in, debugEnabled = false - ) - val evaluator = new Evaluator(Ctx.defaultHome, outPath, TestEvaluator.externalOutPath, module, logger) - - def apply[T](t: Task[T]): Either[Result.Failing[T], (T, Int)] = { - val evaluated = evaluator.evaluate(Agg(t)) - - if (evaluated.failing.keyCount == 0) { - Right( - Tuple2( - evaluated.rawValues.head.asInstanceOf[Result.Success[T]].value, - evaluated.evaluated.collect { - case t: Target[_] - if module.millInternal.targets.contains(t) - && !t.isInstanceOf[Input[_]] - && !t.ctx.external => t - case t: mill.define.Command[_] => t - }.size - )) - } else { - Left( - evaluated.failing.lookupKey(evaluated.failing.keys().next).items.next() - .asInstanceOf[Result.Failing[T]] - ) - } - } - - def fail(target: Target[_], expectedFailCount: Int, expectedRawValues: Seq[Result[_]]) = { - - val res = evaluator.evaluate(Agg(target)) - - val cleaned = res.rawValues.map{ - case Result.Exception(ex, _) => Result.Exception(ex, new OuterStack(Nil)) - case x => x - } - - assert( - cleaned == expectedRawValues, - res.failing.keyCount == expectedFailCount - ) - - } - - def check(targets: Agg[Task[_]], expected: Agg[Task[_]]) = { - val evaluated = evaluator.evaluate(targets) - .evaluated - .flatMap(_.asTarget) - .filter(module.millInternal.targets.contains) - .filter(!_.isInstanceOf[Input[_]]) - assert(evaluated == expected) - } - -} diff --git a/main/test/src/mill/util/TestGraphs.scala b/main/test/src/mill/util/TestGraphs.scala deleted file mode 100644 index d3b35ddc..00000000 --- a/main/test/src/mill/util/TestGraphs.scala +++ /dev/null @@ -1,271 +0,0 @@ -package mill.util -import TestUtil.test -import mill.define.{Cross, Discover} -import mill.{Module, T} - -/** - * Example dependency graphs for us to use in our test suite. - * - * The graphs using `test()` live in the `class` and need to be instantiated - * every time you use them, because they are mutable (you can poke at the - * `test`'s `counter`/`failure`/`exception` fields to test various graph - * evaluation scenarios. - * - * The immutable graphs, used for testing discovery & target resolution, - * live in the companion object. - */ -class TestGraphs(){ - // single - object singleton extends TestUtil.BaseModule { - val single = test() - } - - object bactickIdentifiers extends TestUtil.BaseModule { - val `up-target` = test() - val `a-down-target` = test(`up-target`) - val `invisible&` = test() - object `nested-module` extends TestUtil.BaseModule { - val `nested-target` = test() - } - } - - // up---down - object pair extends TestUtil.BaseModule{ - val up = test() - val down = test(up) - } - - // up---o---down - object anonTriple extends TestUtil.BaseModule { - val up = test() - val down = test(test.anon(up)) - } - - // left - // / \ - // up down - // \ / - // right - object diamond extends TestUtil.BaseModule { - val up = test() - val left = test(up) - val right = test(up) - val down = test(left, right) - } - - // o - // / \ - // up down - // \ / - // o - object anonDiamond extends TestUtil.BaseModule { - val up = test() - val down = test(test.anon(up), test.anon(up)) - } - - object defCachedDiamond extends TestUtil.BaseModule { - def up = T{ test() } - def left = T{ test(up) } - def right = T{ test(up) } - def down = T{ test(left, right) } - } - - - object borkedCachedDiamond2 extends TestUtil.BaseModule { - def up = test() - def left = test(up) - def right = test(up) - def down = test(left, right) - } - - object borkedCachedDiamond3 extends TestUtil.BaseModule { - def up = test() - def left = test(up) - def right = test(up) - def down = test(left, right) - } - - // o g-----o - // \ \ \ - // o o h-----I---o - // \ / \ / \ / \ \ - // A---c--o E o-o \ \ - // / \ / \ / \ o---J - // o d o--o o / / - // \ / \ / / - // o o---F---o - // / / - // o--B o - object bigSingleTerminal extends TestUtil.BaseModule { - val a = test(test.anon(), test.anon()) - val b = test(test.anon()) - val e = { - val c = test.anon(a) - val d = test.anon(a) - test( - test.anon(test.anon(), test.anon(c)), - test.anon(test.anon(c, test.anon(d, b))) - ) - } - val f = test(test.anon(test.anon(), test.anon(e))) - - val i = { - val g = test.anon() - val h = test.anon(g, e) - test(test.anon(g), test.anon(test.anon(h))) - } - val j = test(test.anon(i), test.anon(i, f), test.anon(f)) - } - // _ left _ - // / \ - // task1 -------- right - // _/ - // change - task2 - object separateGroups extends TestUtil.BaseModule { - val task1 = T.task{ 1 } - def left = T{ task1() } - val change = test() - val task2 = T.task{ change() } - def right = T{ task1() + task2() + left() + 1 } - - } -} - - -object TestGraphs{ - // _ left _ - // / \ - // task -------- right - object triangleTask extends TestUtil.BaseModule { - val task = T.task{ 1 } - def left = T{ task() } - def right = T{ task() + left() + 1 } - } - - - // _ left - // / - // task -------- right - object multiTerminalGroup extends TestUtil.BaseModule { - val task = T.task{ 1 } - def left = T{ task() } - def right = T{ task() } - } - - // _ left _____________ - // / \ \ - // task1 -------- right ----- task2 - object multiTerminalBoundary extends TestUtil.BaseModule { - val task1 = T.task{ 1 } - def left = T{ task1() } - def right = T{ task1() + left() + 1 } - val task2 = T.task{ left() + right() } - } - - - trait CanNest extends Module{ - def single = T{ 1 } - def invisible: Any = T{ 2 } - def invisible2: mill.define.Task[Int] = T{ 3 } - def invisible3: mill.define.Task[_] = T{ 4 } - } - object nestedModule extends TestUtil.BaseModule { - def single = T{ 5 } - def invisible: Any = T{ 6 } - object nested extends Module{ - def single = T{ 7 } - def invisible: Any = T{ 8 } - - } - object classInstance extends CanNest - - } - - trait BaseModule extends Module { - def foo = T{ Seq("base") } - def cmd(i: Int) = T.command{ Seq("base" + i) } - } - - object canOverrideSuper extends TestUtil.BaseModule with BaseModule { - override def foo = T{ super.foo() ++ Seq("object") } - override def cmd(i: Int) = T.command{ super.cmd(i)() ++ Seq("object" + i) } - override lazy val millDiscover: Discover[this.type] = Discover[this.type] - } - - trait TraitWithModule extends Module{ outer => - object TraitModule extends Module{ - def testFrameworks = T{ Seq("mill.UTestFramework") } - def test() = T.command{ ()/*donothing*/ } - } - } - - - // Make sure nested objects inherited from traits work - object TraitWithModuleObject extends TestUtil.BaseModule with TraitWithModule{ - override lazy val millDiscover: Discover[this.type] = Discover[this.type] - } - - object nullTasks extends TestUtil.BaseModule{ - val nullString: String = null - def nullTask1 = T.task{ nullString } - def nullTask2 = T.task{ nullTask1() } - - def nullTarget1 = T{ nullString } - def nullTarget2 = T{ nullTarget1() } - def nullTarget3 = T{ nullTask1() } - def nullTarget4 = T{ nullTask2() } - - def nullCommand1() = T.command{ nullString } - def nullCommand2() = T.command{ nullTarget1() } - def nullCommand3() = T.command{ nullTask1() } - def nullCommand4() = T.command{ nullTask2() } - - override lazy val millDiscover: Discover[this.type] = Discover[this.type] - } - - object singleCross extends TestUtil.BaseModule { - object cross extends mill.Cross[Cross]("210", "211", "212") - class Cross(scalaVersion: String) extends Module{ - def suffix = T{ scalaVersion } - } - } - object crossResolved extends TestUtil.BaseModule { - trait MyModule extends Module{ - def crossVersion: String - implicit object resolver extends mill.define.Cross.Resolver[MyModule]{ - def resolve[V <: MyModule](c: Cross[V]): V = c.itemMap(List(crossVersion)) - } - } - - object foo extends mill.Cross[FooModule]("2.10", "2.11", "2.12") - class FooModule(val crossVersion: String) extends MyModule{ - def suffix = T{ crossVersion } - } - - object bar extends mill.Cross[BarModule]("2.10", "2.11", "2.12") - class BarModule(val crossVersion: String) extends MyModule{ - def longSuffix = T{ "_" + foo().suffix() } - } - } - object doubleCross extends TestUtil.BaseModule { - val crossMatrix = for{ - scalaVersion <- Seq("210", "211", "212") - platform <- Seq("jvm", "js", "native") - if !(platform == "native" && scalaVersion != "212") - } yield (scalaVersion, platform) - object cross extends mill.Cross[Cross](crossMatrix:_*) - class Cross(scalaVersion: String, platform: String) extends Module{ - def suffix = T{ scalaVersion + "_" + platform } - } - } - - object nestedCrosses extends TestUtil.BaseModule { - object cross extends mill.Cross[Cross]("210", "211", "212") - class Cross(scalaVersion: String) extends mill.Module{ - object cross2 extends mill.Cross[Cross]("jvm", "js", "native") - class Cross(platform: String) extends mill.Module{ - def suffix = T{ scalaVersion + "_" + platform } - } - } - } -} diff --git a/main/test/src/mill/util/TestUtil.scala b/main/test/src/mill/util/TestUtil.scala deleted file mode 100644 index baab2992..00000000 --- a/main/test/src/mill/util/TestUtil.scala +++ /dev/null @@ -1,86 +0,0 @@ -package mill.util - -import mill.util.Router.Overrides -import mill.define._ -import mill.api.Result -import mill.api.Result.OuterStack -import utest.assert -import mill.util.Strict.Agg -import utest.framework.TestPath - -import scala.collection.mutable - -object TestUtil { - def getOutPath()(implicit fullName: sourcecode.FullName, - tp: TestPath) = { - os.pwd / 'target / 'workspace / (fullName.value.split('.') ++ tp.value) - } - def getOutPathStatic()(implicit fullName: sourcecode.FullName) = { - os.pwd / 'target / 'workspace / fullName.value.split('.') - } - - def getSrcPathStatic()(implicit fullName: sourcecode.FullName) = { - os.pwd / 'target / 'worksources / fullName.value.split('.') - } - def getSrcPathBase() = { - os.pwd / 'target / 'worksources - } - - class BaseModule(implicit millModuleEnclosing0: sourcecode.Enclosing, - millModuleLine0: sourcecode.Line, - millName0: sourcecode.Name, - overrides: Overrides) - extends mill.define.BaseModule(getSrcPathBase() / millModuleEnclosing0.value.split("\\.| |#"))( - implicitly, implicitly, implicitly, implicitly, implicitly){ - lazy val millDiscover: Discover[this.type] = Discover[this.type] - } - - object test{ - - def anon(inputs: Task[Int]*) = new Test(inputs) - def apply(inputs: Task[Int]*) - (implicit ctx: mill.define.Ctx)= { - new TestTarget(inputs, pure = inputs.nonEmpty) - } - } - - class Test(val inputs: Seq[Task[Int]]) extends Task[Int]{ - var counter = 0 - var failure = Option.empty[String] - var exception = Option.empty[Throwable] - override def evaluate(args: Ctx) = { - failure.map(Result.Failure(_)) orElse - exception.map(Result.Exception(_, new OuterStack(Nil))) getOrElse - Result.Success(counter + args.args.map(_.asInstanceOf[Int]).sum) - } - override def sideHash = counter + failure.hashCode() + exception.hashCode() - } - /** - * A dummy target that takes any number of inputs, and whose output can be - * controlled externally, so you can construct arbitrary dataflow graphs and - * test how changes propagate. - */ - class TestTarget(inputs: Seq[Task[Int]], - val pure: Boolean) - (implicit ctx0: mill.define.Ctx) - extends Test(inputs) with Target[Int]{ - val ctx = ctx0.copy(segments = ctx0.segments ++ Seq(ctx0.segment)) - val readWrite = upickle.default.readwriter[Int] - - - } - def checkTopological(targets: Agg[Task[_]]) = { - val seen = mutable.Set.empty[Task[_]] - for(t <- targets.indexed.reverseIterator){ - seen.add(t) - for(upstream <- t.inputs){ - assert(!seen(upstream)) - } - } - } - def disableInJava9OrAbove(f: => Any): Unit = { - if (!ammonite.util.Util.java9OrAbove) { - f - } - } -} diff --git a/main/test/src/util/ParseArgsTest.scala b/main/test/src/util/ParseArgsTest.scala new file mode 100644 index 00000000..e31baf4f --- /dev/null +++ b/main/test/src/util/ParseArgsTest.scala @@ -0,0 +1,254 @@ +package mill.util + +import mill.define.{Segment, Segments} +import mill.define.Segment.{Cross, Label} +import utest._ + +object ParseArgsTest extends TestSuite { + + val tests = Tests { + 'extractSelsAndArgs - { + def check(input: Seq[String], + expectedSelectors: Seq[String], + expectedArgs: Seq[String], + multiSelect: Boolean) = { + val (selectors, args) = ParseArgs.extractSelsAndArgs(input, multiSelect) + + assert( + selectors == expectedSelectors, + args == expectedArgs + ) + } + + 'empty - check(input = Seq.empty, + expectedSelectors = Seq.empty, + expectedArgs = Seq.empty, + multiSelect = false) + 'singleSelector - check( + input = Seq("core.compile"), + expectedSelectors = Seq("core.compile"), + expectedArgs = Seq.empty, + multiSelect = false + ) + 'singleSelectorWithArgs - check( + input = Seq("application.run", "hello", "world"), + expectedSelectors = Seq("application.run"), + expectedArgs = Seq("hello", "world"), + multiSelect = false + ) + 'singleSelectorWithAllInArgs - check( + input = Seq("application.run", "hello", "world", "--all"), + expectedSelectors = Seq("application.run"), + expectedArgs = Seq("hello", "world", "--all"), + multiSelect = false + ) + 'multiSelectors - check( + input = Seq("core.jar", "core.docJar", "core.sourcesJar"), + expectedSelectors = Seq("core.jar", "core.docJar", "core.sourcesJar"), + expectedArgs = Seq.empty, + multiSelect = true + ) + 'multiSelectorsSeq - check( + input = Seq("core.jar", "core.docJar", "core.sourcesJar"), + expectedSelectors = Seq("core.jar", "core.docJar", "core.sourcesJar"), + expectedArgs = Seq.empty, + multiSelect = true + ) + 'multiSelectorsWithArgs - check( + input = Seq("core.compile", + "application.runMain", + "--", + "Main", + "hello", + "world"), + expectedSelectors = Seq("core.compile", "application.runMain"), + expectedArgs = Seq("Main", "hello", "world"), + multiSelect = true + ) + 'multiSelectorsWithArgsWithAllInArgs - check( + input = Seq("core.compile", + "application.runMain", + "--", + "Main", + "--all", + "world"), + expectedSelectors = Seq("core.compile", "application.runMain"), + expectedArgs = Seq("Main", "--all", "world"), + multiSelect = true + ) + } + 'expandBraces - { + def check(input: String, expectedExpansion: List[String]) = { + val Right(expanded) = ParseArgs.expandBraces(input) + + assert(expanded == expectedExpansion) + } + + 'expandLeft - check( + "{application,core}.compile", + List("application.compile", "core.compile") + ) + 'expandRight - check( + "application.{jar,docJar,sourcesJar}", + List("application.jar", "application.docJar", "application.sourcesJar") + ) + 'expandBoth - check( + "{core,application}.{jar,docJar}", + List( + "core.jar", + "core.docJar", + "application.jar", + "application.docJar" + ) + ) + 'expandNested - { + check("{hello,world.{cow,moo}}", + List("hello", "world.cow", "world.moo")) + check("{a,b{c,d}}", List("a", "bc", "bd")) + check("{a,b,{c,d}}", List("a", "b", "c", "d")) + check("{a,b{c,d{e,f}}}", List("a", "bc", "bde", "bdf")) + check("{a{b,c},d}", List("ab", "ac", "d")) + check("{a,{b,c}d}", List("a", "bd", "cd")) + check("{a{b,c},d{e,f}}", List("ab", "ac", "de", "df")) + check("{a,b{c,d},e{f,g}}", List("a", "bc", "bd", "ef", "eg")) + } + 'expandMixed - check( + "{a,b}.{c}.{}.e", + List("a.{c}.{}.e", "b.{c}.{}.e") + ) + 'malformed - { + val malformed = Seq("core.{compile", "core.{compile,test]") + + malformed.foreach { m => + val Left(error) = ParseArgs.expandBraces(m) + assert(error.contains("Parsing exception")) + } + } + 'dontExpand - { + check("core.compile", List("core.compile")) + check("{}.compile", List("{}.compile")) + check("{core}.compile", List("{core}.compile")) + } + 'keepUnknownSymbols - { + check("{a,b}.e<>", List("a.e<>", "b.e<>")) + check("a[99]&&", List("a[99]&&")) + check( + "{a,b}.<%%>.{c,d}", + List("a.<%%>.c", "a.<%%>.d", "b.<%%>.c", "b.<%%>.d") + ) + } + } + + 'apply - { + def check(input: Seq[String], + expectedSelectors: List[(Option[List[Segment]], List[Segment])], + expectedArgs: Seq[String], + multiSelect: Boolean) = { + val Right((selectors0, args)) = ParseArgs(input, multiSelect) + + val selectors = selectors0.map{ + case (Some(v1), v2) => (Some(v1.value), v2.value) + case (None, v2) => (None, v2.value) + } + assert( + selectors == expectedSelectors, + args == expectedArgs + ) + } + + 'rejectEmpty { + assert(ParseArgs(Seq.empty, multiSelect = false) == Left("Selector cannot be empty")) + } + 'singleSelector - check( + input = Seq("core.compile"), + expectedSelectors = List( + None -> List(Label("core"), Label("compile")) + ), + expectedArgs = Seq.empty, + multiSelect = false + ) + 'externalSelector - check( + input = Seq("foo.bar/core.compile"), + expectedSelectors = List( + Some(List(Label("foo"), Label("bar"))) -> List(Label("core"), Label("compile")) + ), + expectedArgs = Seq.empty, + multiSelect = false + ) + 'singleSelectorWithArgs - check( + input = Seq("application.run", "hello", "world"), + expectedSelectors = List( + None -> List(Label("application"), Label("run")) + ), + expectedArgs = Seq("hello", "world"), + multiSelect = false + ) + 'singleSelectorWithCross - check( + input = Seq("bridges[2.12.4,jvm].compile"), + expectedSelectors = List( + None -> List(Label("bridges"), Cross(Seq("2.12.4", "jvm")), Label("compile")) + ), + expectedArgs = Seq.empty, + multiSelect = false + ) + 'multiSelectorsBraceExpansion - check( + input = Seq("{core,application}.compile"), + expectedSelectors = List( + None -> List(Label("core"), Label("compile")), + None -> List(Label("application"), Label("compile")) + ), + expectedArgs = Seq.empty, + multiSelect = true + ) + 'multiSelectorsBraceExpansionWithArgs - check( + input = Seq("{core,application}.run", "--", "hello", "world"), + expectedSelectors = List( + None -> List(Label("core"), Label("run")), + None -> List(Label("application"), Label("run")) + ), + expectedArgs = Seq("hello", "world"), + multiSelect = true + ) + 'multiSelectorsBraceExpansionWithCross - check( + input = Seq("bridges[2.12.4,jvm].{test,jar}"), + expectedSelectors = List( + None -> List(Label("bridges"), Cross(Seq("2.12.4", "jvm")), Label("test")), + None -> List(Label("bridges"), Cross(Seq("2.12.4", "jvm")), Label("jar")) + ), + expectedArgs = Seq.empty, + multiSelect = true + ) + 'multiSelectorsBraceExpansionInsideCross - check( + input = Seq("bridges[{2.11.11,2.11.8,2.13.0-M3}].jar"), + expectedSelectors = List( + None -> List(Label("bridges"), Cross(Seq("2.11.11")), Label("jar")), + None -> List(Label("bridges"), Cross(Seq("2.11.8")), Label("jar")), + None -> List(Label("bridges"), Cross(Seq("2.13.0-M3")), Label("jar")) + ), + expectedArgs = Seq.empty, + multiSelect = true + ) + 'multiSelectorsBraceExpansionWithoutAll - { + val res = ParseArgs(Seq("{core,application}.compile"), multiSelect = false) + val expected = Right( + List( + None -> Segments(Label("core"), Label("compile")), + None -> Segments(Label("application"), Label("compile")) + ), + Nil + ) + assert(res == expected) + } + 'multiSelectorsWithoutAllAsSingle - check( + // this is how it works when we pass multiple tasks without --all flag + input = Seq("core.compile", "application.compile"), + expectedSelectors = List( + None -> List(Label("core"), Label("compile")) + ), + expectedArgs = Seq("application.compile"), + multiSelect = false + ) + } + } + +} diff --git a/main/test/src/util/ScriptTestSuite.scala b/main/test/src/util/ScriptTestSuite.scala new file mode 100644 index 00000000..f448aaaf --- /dev/null +++ b/main/test/src/util/ScriptTestSuite.scala @@ -0,0 +1,53 @@ +package mill.util + +import java.io.{ByteArrayInputStream, ByteArrayOutputStream, PrintStream} + +import utest._ + +abstract class ScriptTestSuite(fork: Boolean) extends TestSuite{ + def workspaceSlug: String + def scriptSourcePath: os.Path + def buildPath: os.RelPath = "build.sc" + + val workspacePath = os.pwd / 'target / 'workspace / workspaceSlug + val wd = workspacePath / buildPath / os.up + val stdOutErr = System.out // new PrintStream(new ByteArrayOutputStream()) + val stdIn = new ByteArrayInputStream(Array()) + val disableTicker = false + val debugLog = false + lazy val runner = new mill.main.MainRunner( + ammonite.main.Cli.Config(wd = wd), disableTicker, + stdOutErr, stdOutErr, stdIn, None, Map.empty, + b => (), debugLog + ) + def eval(s: String*) = { + if (!fork) runner.runScript(workspacePath / buildPath , s.toList) + else{ + try { + os.proc(os.home / "mill-release", "-i", s).call( + wd, + stdin = os.Inherit, + stdout = os.Inherit, + stderr = os.Inherit, + ) + true + }catch{case e: Throwable => false} + } + } + def meta(s: String) = { + val (List(selector), args) = ParseArgs.apply(Seq(s), multiSelect = false).right.get + + os.read(wd / "out" / selector._2.value.flatMap(_.pathSegments) / "meta.json") + } + + + def initWorkspace() = { + os.remove.all(workspacePath) + os.makeDir.all(workspacePath / os.up) + // The unzipped git repo snapshots we get from github come with a + // wrapper-folder inside the zip file, so copy the wrapper folder to the + // destination instead of the folder containing the wrapper. + + os.copy(scriptSourcePath, workspacePath) + } +} diff --git a/main/test/src/util/TestEvaluator.scala b/main/test/src/util/TestEvaluator.scala new file mode 100644 index 00000000..9a235679 --- /dev/null +++ b/main/test/src/util/TestEvaluator.scala @@ -0,0 +1,80 @@ +package mill.util + +import mill.define.{Input, Target, Task} +import mill.api.Result.OuterStack +import mill.eval.{Evaluator, Result} +import mill.util.Strict.Agg +import utest.assert +import utest.framework.TestPath + +import language.experimental.macros +object TestEvaluator{ + val externalOutPath = os.pwd / 'target / 'external + + + def static(module: TestUtil.BaseModule)(implicit fullName: sourcecode.FullName) = { + new TestEvaluator(module)(fullName, TestPath(Nil)) + } +} + +class TestEvaluator(module: TestUtil.BaseModule) + (implicit fullName: sourcecode.FullName, + tp: TestPath){ + val outPath = TestUtil.getOutPath() + +// val logger = DummyLogger + val logger = new PrintLogger( + colored = true, disableTicker=false, + ammonite.util.Colors.Default, System.out, System.out, System.err, System.in, debugEnabled = false + ) + val evaluator = new Evaluator(Ctx.defaultHome, outPath, TestEvaluator.externalOutPath, module, logger) + + def apply[T](t: Task[T]): Either[Result.Failing[T], (T, Int)] = { + val evaluated = evaluator.evaluate(Agg(t)) + + if (evaluated.failing.keyCount == 0) { + Right( + Tuple2( + evaluated.rawValues.head.asInstanceOf[Result.Success[T]].value, + evaluated.evaluated.collect { + case t: Target[_] + if module.millInternal.targets.contains(t) + && !t.isInstanceOf[Input[_]] + && !t.ctx.external => t + case t: mill.define.Command[_] => t + }.size + )) + } else { + Left( + evaluated.failing.lookupKey(evaluated.failing.keys().next).items.next() + .asInstanceOf[Result.Failing[T]] + ) + } + } + + def fail(target: Target[_], expectedFailCount: Int, expectedRawValues: Seq[Result[_]]) = { + + val res = evaluator.evaluate(Agg(target)) + + val cleaned = res.rawValues.map{ + case Result.Exception(ex, _) => Result.Exception(ex, new OuterStack(Nil)) + case x => x + } + + assert( + cleaned == expectedRawValues, + res.failing.keyCount == expectedFailCount + ) + + } + + def check(targets: Agg[Task[_]], expected: Agg[Task[_]]) = { + val evaluated = evaluator.evaluate(targets) + .evaluated + .flatMap(_.asTarget) + .filter(module.millInternal.targets.contains) + .filter(!_.isInstanceOf[Input[_]]) + assert(evaluated == expected) + } + +} diff --git a/main/test/src/util/TestGraphs.scala b/main/test/src/util/TestGraphs.scala new file mode 100644 index 00000000..d3b35ddc --- /dev/null +++ b/main/test/src/util/TestGraphs.scala @@ -0,0 +1,271 @@ +package mill.util +import TestUtil.test +import mill.define.{Cross, Discover} +import mill.{Module, T} + +/** + * Example dependency graphs for us to use in our test suite. + * + * The graphs using `test()` live in the `class` and need to be instantiated + * every time you use them, because they are mutable (you can poke at the + * `test`'s `counter`/`failure`/`exception` fields to test various graph + * evaluation scenarios. + * + * The immutable graphs, used for testing discovery & target resolution, + * live in the companion object. + */ +class TestGraphs(){ + // single + object singleton extends TestUtil.BaseModule { + val single = test() + } + + object bactickIdentifiers extends TestUtil.BaseModule { + val `up-target` = test() + val `a-down-target` = test(`up-target`) + val `invisible&` = test() + object `nested-module` extends TestUtil.BaseModule { + val `nested-target` = test() + } + } + + // up---down + object pair extends TestUtil.BaseModule{ + val up = test() + val down = test(up) + } + + // up---o---down + object anonTriple extends TestUtil.BaseModule { + val up = test() + val down = test(test.anon(up)) + } + + // left + // / \ + // up down + // \ / + // right + object diamond extends TestUtil.BaseModule { + val up = test() + val left = test(up) + val right = test(up) + val down = test(left, right) + } + + // o + // / \ + // up down + // \ / + // o + object anonDiamond extends TestUtil.BaseModule { + val up = test() + val down = test(test.anon(up), test.anon(up)) + } + + object defCachedDiamond extends TestUtil.BaseModule { + def up = T{ test() } + def left = T{ test(up) } + def right = T{ test(up) } + def down = T{ test(left, right) } + } + + + object borkedCachedDiamond2 extends TestUtil.BaseModule { + def up = test() + def left = test(up) + def right = test(up) + def down = test(left, right) + } + + object borkedCachedDiamond3 extends TestUtil.BaseModule { + def up = test() + def left = test(up) + def right = test(up) + def down = test(left, right) + } + + // o g-----o + // \ \ \ + // o o h-----I---o + // \ / \ / \ / \ \ + // A---c--o E o-o \ \ + // / \ / \ / \ o---J + // o d o--o o / / + // \ / \ / / + // o o---F---o + // / / + // o--B o + object bigSingleTerminal extends TestUtil.BaseModule { + val a = test(test.anon(), test.anon()) + val b = test(test.anon()) + val e = { + val c = test.anon(a) + val d = test.anon(a) + test( + test.anon(test.anon(), test.anon(c)), + test.anon(test.anon(c, test.anon(d, b))) + ) + } + val f = test(test.anon(test.anon(), test.anon(e))) + + val i = { + val g = test.anon() + val h = test.anon(g, e) + test(test.anon(g), test.anon(test.anon(h))) + } + val j = test(test.anon(i), test.anon(i, f), test.anon(f)) + } + // _ left _ + // / \ + // task1 -------- right + // _/ + // change - task2 + object separateGroups extends TestUtil.BaseModule { + val task1 = T.task{ 1 } + def left = T{ task1() } + val change = test() + val task2 = T.task{ change() } + def right = T{ task1() + task2() + left() + 1 } + + } +} + + +object TestGraphs{ + // _ left _ + // / \ + // task -------- right + object triangleTask extends TestUtil.BaseModule { + val task = T.task{ 1 } + def left = T{ task() } + def right = T{ task() + left() + 1 } + } + + + // _ left + // / + // task -------- right + object multiTerminalGroup extends TestUtil.BaseModule { + val task = T.task{ 1 } + def left = T{ task() } + def right = T{ task() } + } + + // _ left _____________ + // / \ \ + // task1 -------- right ----- task2 + object multiTerminalBoundary extends TestUtil.BaseModule { + val task1 = T.task{ 1 } + def left = T{ task1() } + def right = T{ task1() + left() + 1 } + val task2 = T.task{ left() + right() } + } + + + trait CanNest extends Module{ + def single = T{ 1 } + def invisible: Any = T{ 2 } + def invisible2: mill.define.Task[Int] = T{ 3 } + def invisible3: mill.define.Task[_] = T{ 4 } + } + object nestedModule extends TestUtil.BaseModule { + def single = T{ 5 } + def invisible: Any = T{ 6 } + object nested extends Module{ + def single = T{ 7 } + def invisible: Any = T{ 8 } + + } + object classInstance extends CanNest + + } + + trait BaseModule extends Module { + def foo = T{ Seq("base") } + def cmd(i: Int) = T.command{ Seq("base" + i) } + } + + object canOverrideSuper extends TestUtil.BaseModule with BaseModule { + override def foo = T{ super.foo() ++ Seq("object") } + override def cmd(i: Int) = T.command{ super.cmd(i)() ++ Seq("object" + i) } + override lazy val millDiscover: Discover[this.type] = Discover[this.type] + } + + trait TraitWithModule extends Module{ outer => + object TraitModule extends Module{ + def testFrameworks = T{ Seq("mill.UTestFramework") } + def test() = T.command{ ()/*donothing*/ } + } + } + + + // Make sure nested objects inherited from traits work + object TraitWithModuleObject extends TestUtil.BaseModule with TraitWithModule{ + override lazy val millDiscover: Discover[this.type] = Discover[this.type] + } + + object nullTasks extends TestUtil.BaseModule{ + val nullString: String = null + def nullTask1 = T.task{ nullString } + def nullTask2 = T.task{ nullTask1() } + + def nullTarget1 = T{ nullString } + def nullTarget2 = T{ nullTarget1() } + def nullTarget3 = T{ nullTask1() } + def nullTarget4 = T{ nullTask2() } + + def nullCommand1() = T.command{ nullString } + def nullCommand2() = T.command{ nullTarget1() } + def nullCommand3() = T.command{ nullTask1() } + def nullCommand4() = T.command{ nullTask2() } + + override lazy val millDiscover: Discover[this.type] = Discover[this.type] + } + + object singleCross extends TestUtil.BaseModule { + object cross extends mill.Cross[Cross]("210", "211", "212") + class Cross(scalaVersion: String) extends Module{ + def suffix = T{ scalaVersion } + } + } + object crossResolved extends TestUtil.BaseModule { + trait MyModule extends Module{ + def crossVersion: String + implicit object resolver extends mill.define.Cross.Resolver[MyModule]{ + def resolve[V <: MyModule](c: Cross[V]): V = c.itemMap(List(crossVersion)) + } + } + + object foo extends mill.Cross[FooModule]("2.10", "2.11", "2.12") + class FooModule(val crossVersion: String) extends MyModule{ + def suffix = T{ crossVersion } + } + + object bar extends mill.Cross[BarModule]("2.10", "2.11", "2.12") + class BarModule(val crossVersion: String) extends MyModule{ + def longSuffix = T{ "_" + foo().suffix() } + } + } + object doubleCross extends TestUtil.BaseModule { + val crossMatrix = for{ + scalaVersion <- Seq("210", "211", "212") + platform <- Seq("jvm", "js", "native") + if !(platform == "native" && scalaVersion != "212") + } yield (scalaVersion, platform) + object cross extends mill.Cross[Cross](crossMatrix:_*) + class Cross(scalaVersion: String, platform: String) extends Module{ + def suffix = T{ scalaVersion + "_" + platform } + } + } + + object nestedCrosses extends TestUtil.BaseModule { + object cross extends mill.Cross[Cross]("210", "211", "212") + class Cross(scalaVersion: String) extends mill.Module{ + object cross2 extends mill.Cross[Cross]("jvm", "js", "native") + class Cross(platform: String) extends mill.Module{ + def suffix = T{ scalaVersion + "_" + platform } + } + } + } +} diff --git a/main/test/src/util/TestUtil.scala b/main/test/src/util/TestUtil.scala new file mode 100644 index 00000000..baab2992 --- /dev/null +++ b/main/test/src/util/TestUtil.scala @@ -0,0 +1,86 @@ +package mill.util + +import mill.util.Router.Overrides +import mill.define._ +import mill.api.Result +import mill.api.Result.OuterStack +import utest.assert +import mill.util.Strict.Agg +import utest.framework.TestPath + +import scala.collection.mutable + +object TestUtil { + def getOutPath()(implicit fullName: sourcecode.FullName, + tp: TestPath) = { + os.pwd / 'target / 'workspace / (fullName.value.split('.') ++ tp.value) + } + def getOutPathStatic()(implicit fullName: sourcecode.FullName) = { + os.pwd / 'target / 'workspace / fullName.value.split('.') + } + + def getSrcPathStatic()(implicit fullName: sourcecode.FullName) = { + os.pwd / 'target / 'worksources / fullName.value.split('.') + } + def getSrcPathBase() = { + os.pwd / 'target / 'worksources + } + + class BaseModule(implicit millModuleEnclosing0: sourcecode.Enclosing, + millModuleLine0: sourcecode.Line, + millName0: sourcecode.Name, + overrides: Overrides) + extends mill.define.BaseModule(getSrcPathBase() / millModuleEnclosing0.value.split("\\.| |#"))( + implicitly, implicitly, implicitly, implicitly, implicitly){ + lazy val millDiscover: Discover[this.type] = Discover[this.type] + } + + object test{ + + def anon(inputs: Task[Int]*) = new Test(inputs) + def apply(inputs: Task[Int]*) + (implicit ctx: mill.define.Ctx)= { + new TestTarget(inputs, pure = inputs.nonEmpty) + } + } + + class Test(val inputs: Seq[Task[Int]]) extends Task[Int]{ + var counter = 0 + var failure = Option.empty[String] + var exception = Option.empty[Throwable] + override def evaluate(args: Ctx) = { + failure.map(Result.Failure(_)) orElse + exception.map(Result.Exception(_, new OuterStack(Nil))) getOrElse + Result.Success(counter + args.args.map(_.asInstanceOf[Int]).sum) + } + override def sideHash = counter + failure.hashCode() + exception.hashCode() + } + /** + * A dummy target that takes any number of inputs, and whose output can be + * controlled externally, so you can construct arbitrary dataflow graphs and + * test how changes propagate. + */ + class TestTarget(inputs: Seq[Task[Int]], + val pure: Boolean) + (implicit ctx0: mill.define.Ctx) + extends Test(inputs) with Target[Int]{ + val ctx = ctx0.copy(segments = ctx0.segments ++ Seq(ctx0.segment)) + val readWrite = upickle.default.readwriter[Int] + + + } + def checkTopological(targets: Agg[Task[_]]) = { + val seen = mutable.Set.empty[Task[_]] + for(t <- targets.indexed.reverseIterator){ + seen.add(t) + for(upstream <- t.inputs){ + assert(!seen(upstream)) + } + } + } + def disableInJava9OrAbove(f: => Any): Unit = { + if (!ammonite.util.Util.java9OrAbove) { + f + } + } +} -- cgit v1.2.3