diff options
Diffstat (limited to 'main/test/src/util')
-rw-r--r-- | main/test/src/util/ParseArgsTest.scala | 254 | ||||
-rw-r--r-- | main/test/src/util/ScriptTestSuite.scala | 53 | ||||
-rw-r--r-- | main/test/src/util/TestEvaluator.scala | 80 | ||||
-rw-r--r-- | main/test/src/util/TestGraphs.scala | 271 | ||||
-rw-r--r-- | main/test/src/util/TestUtil.scala | 86 |
5 files changed, 744 insertions, 0 deletions
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 + } + } +} |