From 60d7b68ed6c1d0a86d05668f040f73ee619ddde8 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sat, 25 Nov 2017 09:25:28 -0800 Subject: Unit tests for `Main.resolve` --- core/src/main/scala/mill/Main.scala | 82 +++++++++++++++------------ core/src/test/scala/mill/main/MainTests.scala | 49 ++++++++++++++++ 2 files changed, 95 insertions(+), 36 deletions(-) create mode 100644 core/src/test/scala/mill/main/MainTests.scala (limited to 'core/src') diff --git a/core/src/main/scala/mill/Main.scala b/core/src/main/scala/mill/Main.scala index a8c51a5b..a3cde4eb 100644 --- a/core/src/main/scala/mill/Main.scala +++ b/core/src/main/scala/mill/Main.scala @@ -24,66 +24,76 @@ object Main { query.parse(input) } - def parseArgs(args: Seq[String]): Either[String, List[scala.util.Either[String,Seq[String]]]] = { - import fastparse.all.Parsed - - val Seq(selectorString, rest @_*) = args + def renderSelector(selector: List[Either[String, Seq[String]]]) = { + val Left(head) :: rest = selector + head + rest.map{case Left(s) => "." + s case Right(vs) => "[" + vs.mkString(",") + "]"}.mkString + } - parseSelector(selectorString) match { + def parseArgs(selectorString: String): Either[String, List[scala.util.Either[String,Seq[String]]]] = { + import fastparse.all.Parsed + if (selectorString.isEmpty) Left("Selector cannot be empty") + else parseSelector(selectorString) match { case f: Parsed.Failure => Left(s"Parsing exception ${f.msg}") case Parsed.Success(selector, _) => Right(selector) } } - def resolve[T, V](selector: List[Either[String, Seq[String]]], - hierarchy: Mirror[T, V])(implicit + def resolve[T, V](remainingSelector: List[Either[String, Seq[String]]], + hierarchy: Mirror[T, V], obj: T, rest: Seq[String], - crossSelectors: List[List[String]]): Either[String, Task[Any]] = { + remainingCrossSelectors: List[List[String]], + revSelectorsSoFar: List[Either[String, Seq[String]]]): Either[String, Task[Any]] = { - selector match{ - case Right(_) :: Nil => Left("No target or command selected") + remainingSelector match{ + case Right(_) :: Nil => Left("Selector cannot start with a [cross] segment") case Left(last) :: Nil => def target: Option[Task[Any]] = - hierarchy.targets.find(_.label == last) - .map{x => x.run(hierarchy.node(obj, crossSelectors))} + hierarchy.targets + .find(_.label == last) + .map{x => x.run(hierarchy.node(obj, remainingCrossSelectors))} def targetModule: Seq[Task[Any]] = for{ (label, child) <- hierarchy.children if label == last - node <- child.node(obj, crossSelectors) match{ + node <- child.node(obj, remainingCrossSelectors) match{ case x: TaskModule => Some(x) case _ => None } - } yield node.self - - def command: Either[String, Task[Any]] = - hierarchy.commands.find(_.name == last).fold[Either[String, Task[Any]]]( - Left(s"Command not found $last") - ){ x => - Option(hierarchy.node(obj, crossSelectors)).fold[Either[String, Task[Any]]]( - Left(s"Instance not found for calling $last") - ){ inst => - (x.invoke(inst, ammonite.main.Scripts.groupArgs(rest.toList)) match { - case Router.Result.Success(v) => Right(v) - case _ => Left(s"Method not found $last") - }) - } + } yield node.self() + + def command = + for(x <- hierarchy.commands.find(_.name == last)) + yield x.invoke( + hierarchy.node(obj, remainingCrossSelectors), + ammonite.main.Scripts.groupArgs(rest.toList) + ) match { + case Router.Result.Success(v) => Right(v) + case _ => Left(s"Command failed $last") } - target.map(Right(_)) orElse targetModule.headOption.map(Right(_)) getOrElse command + command orElse target.map(Right(_)) orElse targetModule.headOption.map(Right(_)) match{ + case None => Left("Cannot resolve task " + renderSelector((Left(last) :: revSelectorsSoFar).reverse)) + case Some(either) => either + } + + case head :: tail => + val newRevSelectorsSoFar = head :: revSelectorsSoFar head match{ case Left(singleLabel) => hierarchy.children.collectFirst{ - case (label, child) if label == singleLabel => - resolve(tail, child) - }.getOrElse( Left(s"Single label not found $singleLabel") ) + case (label, child) if label == singleLabel => child + } match{ + case Some(child) => resolve(tail, child, obj, rest, remainingCrossSelectors, newRevSelectorsSoFar) + case None => Left("Cannot resolve module " + renderSelector(newRevSelectorsSoFar)) + } + case Right(cross) => - resolve(tail, hierarchy.crossChildren.get._2) + resolve(tail, hierarchy.crossChildren.get._2, obj, rest, remainingCrossSelectors, newRevSelectorsSoFar) } - case Nil => Left("Nothing to run") + case Nil => Left("Selector cannot be empty") } } @@ -134,14 +144,14 @@ object Main { val log = new Logger(coloredOutput) - val Seq(_, rest @_*) = args + val Seq(selectorString, rest @_*) = args val res = for { - sel <- parseArgs(args) + sel <- parseArgs(selectorString) disc <- discoverMirror(obj) val crossSelectors = sel.collect{case Right(x) => x.toList} - target <- resolve(sel, disc.mirror)(obj, rest, crossSelectors) + target <- resolve(sel, disc.mirror, obj, rest, crossSelectors, Nil) val mapping = Discovered.mapping(obj)(disc) val workspacePath = pwd / 'out val evaluator = new Evaluator(workspacePath, mapping, log.info) diff --git a/core/src/test/scala/mill/main/MainTests.scala b/core/src/test/scala/mill/main/MainTests.scala new file mode 100644 index 00000000..de7cfd7d --- /dev/null +++ b/core/src/test/scala/mill/main/MainTests.scala @@ -0,0 +1,49 @@ +package mill.main + +import mill.Module +import mill.define.Task +import mill.discover.Discovered +import mill.util.TestUtil.test +import utest._ + +object MainTests extends TestSuite{ + def check[T: Discovered](obj: T, + selectorString: String, + expected: Either[String, Task[_]]) = { + val resolved = for{ + args <- mill.Main.parseArgs(selectorString) + task <- mill.Main.resolve(args, implicitly[Discovered[T]].mirror, obj, Nil, Nil, Nil) + } yield task + assert(resolved == expected) + } + val tests = Tests{ + val graphs = new mill.util.TestGraphs() + import graphs._ + 'single - { + 'pos - check(singleton, "single", Right(singleton.single)) + 'neg1 - check(singleton, "doesntExist", Left("Cannot resolve task doesntExist")) + 'neg2 - check(singleton, "single.doesntExist", Left("Cannot resolve module single")) + 'neg3 - check(singleton, "", Left("Selector cannot be empty")) + } + 'nested - { + class CanNest extends Module{ + val single = test() + } + object outer { + val single = test() + object nested extends Module{ + val single = test() + } + val classInstance = new CanNest + + } + 'pos1 - check(outer, "single", Right(outer.single)) + 'pos2 - check(outer, "nested.single", Right(outer.nested.single)) + 'pos3 - check(outer, "classInstance.single", Right(outer.classInstance.single)) + 'neg1 - check(outer, "doesntExist", Left("Cannot resolve task doesntExist")) + 'neg2 - check(outer, "single.doesntExist", Left("Cannot resolve module single")) + 'neg3 - check(outer, "nested.doesntExist", Left("Cannot resolve task nested.doesntExist")) + 'neg4 - check(outer, "classInstance.doesntExist", Left("Cannot resolve task classInstance.doesntExist")) + } + } +} -- cgit v1.2.3