mirror of https://github.com/sbt/sbt.git
Print: use roots when rendering reverse graph (#808)
* Print: use roots when rendering reverse graph * Print: Refactored to be able to return Tree data structure * SBT: Refactored code and added coursierWhatDependsOn Task (?) * SBT: Added tests to the new coursierWhatDependsOn task * Tasks: Another try to fix the build * Tree: Restored Apply[A] for binary compatibility * Print.scala: Fix the build * sbt-coursier: Refined what depends on check to check correctness * PrintTests: Added test for reverseTree * CR Fixes... 1. changed gotVersion to reconciledVersion 2. Moved AppliedTree to tests project 3. Changed message (wanted version, got version) to X -> Y
This commit is contained in:
parent
15c9be7950
commit
db2a871391
|
|
@ -4,6 +4,35 @@ import coursier.core.{ Attributes, Dependency, Module, Orders, Project, Resoluti
|
|||
|
||||
object Print {
|
||||
|
||||
object Colors {
|
||||
private val `with`: Colors = Colors(Console.RED, Console.YELLOW, Console.RESET)
|
||||
private val `without`: Colors = Colors("", "", "")
|
||||
|
||||
def get(colors: Boolean): Colors = if (colors) `with` else `without`
|
||||
}
|
||||
|
||||
case class Colors private(red: String, yellow: String, reset: String)
|
||||
|
||||
trait Renderable {
|
||||
def repr(colors: Colors): String
|
||||
}
|
||||
|
||||
trait Elem extends Renderable {
|
||||
def dep: Dependency
|
||||
def excluded: Boolean
|
||||
def reconciledVersion: String
|
||||
def children: Seq[Elem]
|
||||
}
|
||||
|
||||
trait Parent extends Renderable {
|
||||
def module: Module
|
||||
def version: String
|
||||
def dependsOn: Module
|
||||
def wantVersion: String
|
||||
def reconciledVersion: String
|
||||
def excluding: Boolean
|
||||
}
|
||||
|
||||
def dependency(dep: Dependency): String =
|
||||
dependency(dep, printExclusions = false)
|
||||
|
||||
|
|
@ -80,23 +109,27 @@ object Print {
|
|||
reverse: Boolean,
|
||||
colors: Boolean
|
||||
): String = {
|
||||
val colorsCase = Colors.get(colors)
|
||||
|
||||
val (red, yellow, reset) =
|
||||
if (colors)
|
||||
(Console.RED, Console.YELLOW, Console.RESET)
|
||||
else
|
||||
("", "", "")
|
||||
if (reverse) {
|
||||
reverseTree(resolution.dependencies.toSeq, resolution, printExclusions).render(_.repr(colorsCase))
|
||||
} else {
|
||||
normalTree(roots, resolution, printExclusions).render(_.repr(colorsCase))
|
||||
}
|
||||
|
||||
final case class Elem(dep: Dependency, excluded: Boolean) {
|
||||
}
|
||||
|
||||
lazy val reconciledVersion = resolution.reconciledVersions
|
||||
private def getElemFactory(resolution: Resolution, withExclusions: Boolean): Dependency => Elem = {
|
||||
final case class ElemImpl(dep: Dependency, excluded: Boolean) extends Elem {
|
||||
|
||||
val reconciledVersion: String = resolution.reconciledVersions
|
||||
.getOrElse(dep.module, dep.version)
|
||||
|
||||
lazy val repr =
|
||||
def repr(colors: Colors): String =
|
||||
if (excluded)
|
||||
resolution.reconciledVersions.get(dep.module) match {
|
||||
case None =>
|
||||
s"$yellow(excluded)$reset ${dep.module}:${dep.version}"
|
||||
s"${colors.yellow}(excluded)${colors.reset} ${dep.module}:${dep.version}"
|
||||
case Some(version) =>
|
||||
val versionMsg =
|
||||
if (version == dep.version)
|
||||
|
|
@ -104,8 +137,8 @@ object Print {
|
|||
else
|
||||
s"version $version"
|
||||
|
||||
s"${dep.module}:${dep.version} " +
|
||||
s"$red(excluded, $versionMsg present anyway)$reset"
|
||||
s"${dep.module}:${dep.version} " +
|
||||
s"${colors.red}(excluded, $versionMsg present anyway)${colors.reset}"
|
||||
}
|
||||
else {
|
||||
val versionStr =
|
||||
|
|
@ -114,16 +147,16 @@ object Print {
|
|||
else {
|
||||
val assumeCompatibleVersions = compatibleVersions(dep.version, reconciledVersion)
|
||||
|
||||
(if (assumeCompatibleVersions) yellow else red) +
|
||||
(if (assumeCompatibleVersions) colors.yellow else colors.red) +
|
||||
s"${dep.version} -> $reconciledVersion" +
|
||||
(if (assumeCompatibleVersions || colors) "" else " (possible incompatibility)") +
|
||||
reset
|
||||
(if (assumeCompatibleVersions) "" else " (possible incompatibility)") +
|
||||
colors.reset
|
||||
}
|
||||
|
||||
s"${dep.module}:$versionStr"
|
||||
}
|
||||
|
||||
lazy val children: Seq[Elem] =
|
||||
val children: Seq[Elem] =
|
||||
if (excluded)
|
||||
Nil
|
||||
else {
|
||||
|
|
@ -146,81 +179,85 @@ object Print {
|
|||
}
|
||||
.map(_.moduleVersion)
|
||||
.filterNot(dependencies.map(_.moduleVersion).toSet).map {
|
||||
case (mod, ver) =>
|
||||
Elem(
|
||||
Dependency(mod, ver, "", Set.empty, Attributes("", ""), false, false),
|
||||
excluded = true
|
||||
)
|
||||
}
|
||||
case (mod, ver) =>
|
||||
ElemImpl(
|
||||
Dependency(mod, ver, "", Set.empty, Attributes("", ""), false, false),
|
||||
excluded = true
|
||||
)
|
||||
}
|
||||
|
||||
dependencies.map(Elem(_, excluded = false)) ++
|
||||
(if (printExclusions) excluded else Nil)
|
||||
dependencies.map(ElemImpl(_, excluded = false)) ++
|
||||
(if (withExclusions) excluded else Nil)
|
||||
}
|
||||
}
|
||||
|
||||
if (reverse) {
|
||||
a => ElemImpl(a, excluded = false)
|
||||
}
|
||||
|
||||
final case class Parent(
|
||||
module: Module,
|
||||
version: String,
|
||||
dependsOn: Module,
|
||||
wantVersion: String,
|
||||
gotVersion: String,
|
||||
excluding: Boolean
|
||||
) {
|
||||
lazy val repr: String =
|
||||
if (excluding)
|
||||
s"$yellow(excluded by)$reset $module:$version"
|
||||
else if (wantVersion == gotVersion)
|
||||
s"$module:$version"
|
||||
else {
|
||||
val assumeCompatibleVersions = compatibleVersions(wantVersion, gotVersion)
|
||||
def normalTree(roots: Seq[Dependency], resolution: Resolution, withExclusions: Boolean): Tree[Elem] = {
|
||||
val elemFactory = getElemFactory(resolution, withExclusions)
|
||||
Tree[Elem](roots.toVector.map(elemFactory), (elem: Elem) => elem.children)
|
||||
}
|
||||
|
||||
s"$module:$version " +
|
||||
(if (assumeCompatibleVersions) yellow else red) +
|
||||
s"(wants $dependsOn:$wantVersion, got $gotVersion)" +
|
||||
reset
|
||||
}
|
||||
}
|
||||
def reverseTree(roots: Seq[Dependency], resolution: Resolution, withExclusions: Boolean): Tree[Parent] = {
|
||||
val elemFactory = getElemFactory(resolution, withExclusions)
|
||||
|
||||
val parents: Map[Module, Seq[Parent]] = {
|
||||
val links = for {
|
||||
dep <- resolution.dependencies.toVector
|
||||
elem <- Elem(dep, excluded = false).children
|
||||
final case class ParentImpl(
|
||||
module: Module,
|
||||
version: String,
|
||||
dependsOn: Module,
|
||||
wantVersion: String,
|
||||
reconciledVersion: String,
|
||||
excluding: Boolean
|
||||
) extends Parent {
|
||||
def repr(colors: Colors): String =
|
||||
if (excluding)
|
||||
s"${colors.yellow}(excluded by)${colors.reset} $module:$version"
|
||||
else if (wantVersion == reconciledVersion)
|
||||
s"$module:$version"
|
||||
else {
|
||||
val assumeCompatibleVersions = compatibleVersions(wantVersion, reconciledVersion)
|
||||
|
||||
s"$module:$version " +
|
||||
(if (assumeCompatibleVersions) colors.yellow else colors.red) +
|
||||
s"$dependsOn:$wantVersion -> $reconciledVersion" +
|
||||
colors.reset
|
||||
}
|
||||
yield elem.dep.module -> Parent(
|
||||
dep.module,
|
||||
dep.version,
|
||||
elem.dep.module,
|
||||
elem.dep.version,
|
||||
elem.reconciledVersion,
|
||||
elem.excluded
|
||||
)
|
||||
}
|
||||
|
||||
links
|
||||
.groupBy(_._1)
|
||||
.mapValues(_.map(_._2).distinct.sortBy(par => (par.module.organization, par.module.name)))
|
||||
.iterator
|
||||
.toMap
|
||||
val parents: Map[Module, Seq[Parent]] = {
|
||||
val links = for {
|
||||
dep <- resolution.dependencies.toVector
|
||||
elem <- elemFactory(dep).children
|
||||
}
|
||||
yield elem.dep.module -> ParentImpl(
|
||||
dep.module,
|
||||
dep.version,
|
||||
elem.dep.module,
|
||||
elem.dep.version,
|
||||
elem.reconciledVersion,
|
||||
elem.excluded
|
||||
)
|
||||
|
||||
def children(par: Parent) =
|
||||
if (par.excluding)
|
||||
Nil
|
||||
else
|
||||
parents.getOrElse(par.module, Nil)
|
||||
links
|
||||
.groupBy(_._1)
|
||||
.mapValues(_.map(_._2).distinct.sortBy(par => (par.module.organization, par.module.name)))
|
||||
.iterator
|
||||
.toMap
|
||||
}
|
||||
|
||||
Tree(
|
||||
resolution
|
||||
.dependencies
|
||||
.toVector
|
||||
.sortBy(dep => (dep.module.organization, dep.module.name, dep.version))
|
||||
.map(dep =>
|
||||
Parent(dep.module, dep.version, dep.module, dep.version, dep.version, excluding = false)
|
||||
)
|
||||
)(children, _.repr)
|
||||
} else
|
||||
Tree(roots.toVector.map(Elem(_, excluded = false)))(_.children, _.repr)
|
||||
def children(par: Parent) =
|
||||
if (par.excluding)
|
||||
Nil
|
||||
else
|
||||
parents.getOrElse(par.module, Nil)
|
||||
|
||||
Tree[Parent](roots
|
||||
.toVector
|
||||
.sortBy(dep => (dep.module.organization, dep.module.name, dep.version))
|
||||
.map(dep => {
|
||||
ParentImpl(dep.module, dep.version, dep.module, dep.version, dep.version, excluding = false)
|
||||
}), (par: Parent) => children(par))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,15 @@ import scala.collection.mutable.ArrayBuffer
|
|||
|
||||
object Tree {
|
||||
|
||||
def apply[T](roots: IndexedSeq[T])(children: T => Seq[T], show: T => String): String = {
|
||||
def apply[A](roots: IndexedSeq[A])(children: A => Seq[A], show: A => String): String = {
|
||||
Tree(roots, children).render(show)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case class Tree[A](roots: IndexedSeq[A], children: A => Seq[A]) {
|
||||
|
||||
def render(show: A => String): String = {
|
||||
|
||||
/**
|
||||
* Recursively go down the resolution for the elems to construct the tree for print out.
|
||||
|
|
@ -14,8 +22,8 @@ object Tree {
|
|||
* @param prefix prefix for the print out
|
||||
* @param acc accumulation method on a string
|
||||
*/
|
||||
def recursivePrint(elems: Seq[T], ancestors: Set[T], prefix: String, acc: String => Unit): Unit = {
|
||||
val unseenElems: Seq[T] = elems.filterNot(ancestors.contains)
|
||||
def recursivePrint(elems: Seq[A], ancestors: Set[A], prefix: String, acc: String => Unit): Unit = {
|
||||
val unseenElems: Seq[A] = elems.filterNot(ancestors.contains)
|
||||
val unseenElemsLen = unseenElems.length
|
||||
for ((elem, idx) <- unseenElems.iterator.zipWithIndex) {
|
||||
val isLast = idx == unseenElemsLen - 1
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ object CoursierPlugin extends AutoPlugin {
|
|||
|
||||
val coursierDependencyTree = Keys.coursierDependencyTree
|
||||
val coursierDependencyInverseTree = Keys.coursierDependencyInverseTree
|
||||
val coursierWhatDependsOn = Keys.coursierWhatDependsOn
|
||||
|
||||
val coursierArtifacts = Keys.coursierArtifacts
|
||||
val coursierSignedArtifacts = Keys.coursierSignedArtifacts
|
||||
|
|
@ -65,7 +66,12 @@ object CoursierPlugin extends AutoPlugin {
|
|||
).value,
|
||||
coursierDependencyInverseTree := Tasks.coursierDependencyTreeTask(
|
||||
inverse = true
|
||||
).value
|
||||
).value,
|
||||
coursierWhatDependsOn := Def.inputTaskDyn {
|
||||
import sbt.complete.DefaultParsers._
|
||||
val input = token(SpaceClass ~ NotQuoted, "<arg>").parsed._2
|
||||
Tasks.coursierWhatDependsOnTask(input)
|
||||
}.evaluated
|
||||
)
|
||||
|
||||
def makeIvyXmlBefore[T](
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import java.net.URL
|
|||
|
||||
import coursier.core.Publication
|
||||
import sbt.librarymanagement.GetClassifiersModule
|
||||
import sbt.{Resolver, SettingKey, TaskKey}
|
||||
import sbt.{InputKey, Resolver, SettingKey, TaskKey}
|
||||
|
||||
import scala.concurrent.duration.Duration
|
||||
|
||||
|
|
@ -62,6 +62,10 @@ object Keys {
|
|||
"Prints dependencies and transitive dependencies as an inverted tree (dependees as children)"
|
||||
)
|
||||
|
||||
val coursierWhatDependsOn = InputKey[String](
|
||||
"coursier-what-depends-on",
|
||||
"Prints dependencies and transitive dependencies as an inverted tree for a specific module (dependees as children)"
|
||||
)
|
||||
val coursierArtifacts = TaskKey[Map[Artifact, Either[FileError, File]]]("coursier-artifacts")
|
||||
val coursierSignedArtifacts = TaskKey[Map[Artifact, Either[FileError, File]]]("coursier-signed-artifacts")
|
||||
val coursierClassifiersArtifacts = TaskKey[Map[Artifact, Either[FileError, File]]]("coursier-classifiers-artifacts")
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ import coursier.interop.scalaz._
|
|||
import coursier.ivy.{IvyRepository, PropertiesPattern}
|
||||
import coursier.Keys._
|
||||
import coursier.Structure._
|
||||
import coursier.util.Print
|
||||
import coursier.util.Print.Colors
|
||||
import coursier.util.{Parse, Print}
|
||||
import sbt.librarymanagement._
|
||||
import sbt.{Classpaths, Def, Resolver, UpdateReport}
|
||||
import sbt.Keys._
|
||||
|
|
@ -1349,13 +1350,12 @@ object Tasks {
|
|||
}
|
||||
}
|
||||
|
||||
def coursierDependencyTreeTask(
|
||||
inverse: Boolean,
|
||||
case class ResolutionResult(configs: Set[String], resolution: Resolution, dependencies: Seq[Dependency])
|
||||
|
||||
private def coursierResolutionTask(
|
||||
sbtClassifiers: Boolean = false,
|
||||
ignoreArtifactErrors: Boolean = false
|
||||
) = Def.taskDyn {
|
||||
|
||||
val projectName = thisProjectRef.value.project
|
||||
): Def.Initialize[sbt.Task[Seq[ResolutionResult]]] = Def.taskDyn {
|
||||
|
||||
val currentProjectTask =
|
||||
if (sbtClassifiers)
|
||||
|
|
@ -1393,9 +1393,9 @@ object Tasks {
|
|||
val resolutions = resolutionsTask.value
|
||||
|
||||
for {
|
||||
(subGraphConfigs, res) <- resolutions
|
||||
(subGraphConfigs, res) <- resolutions.toSeq
|
||||
if subGraphConfigs.exists(includedConfigs)
|
||||
} {
|
||||
} yield {
|
||||
|
||||
val dependencies0 = currentProject.dependencies.collect {
|
||||
case (cfg, dep) if includedConfigs(cfg) && subGraphConfigs(cfg) => dep
|
||||
|
|
@ -1405,20 +1405,60 @@ object Tasks {
|
|||
|
||||
val subRes = res.subset(dependencies0.toSet)
|
||||
|
||||
// use sbt logging?
|
||||
println(
|
||||
s"$projectName (configurations ${subGraphConfigs.toVector.sorted.mkString(", ")})" + "\n" +
|
||||
Print.dependencyTree(
|
||||
dependencies0,
|
||||
subRes,
|
||||
printExclusions = true,
|
||||
inverse,
|
||||
colors = !sys.props.get("sbt.log.noformat").toSeq.contains("true")
|
||||
)
|
||||
)
|
||||
ResolutionResult(subGraphConfigs, subRes, dependencies0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def coursierDependencyTreeTask(
|
||||
inverse: Boolean,
|
||||
sbtClassifiers: Boolean = false,
|
||||
ignoreArtifactErrors: Boolean = false
|
||||
) = Def.task {
|
||||
val projectName = thisProjectRef.value.project
|
||||
|
||||
val resolutions = coursierResolutionTask(sbtClassifiers, ignoreArtifactErrors).value
|
||||
for (ResolutionResult(subGraphConfigs, resolution, dependencies) <- resolutions) {
|
||||
// use sbt logging?
|
||||
println(
|
||||
s"$projectName (configurations ${subGraphConfigs.toVector.sorted.mkString(", ")})" + "\n" +
|
||||
Print.dependencyTree(
|
||||
dependencies,
|
||||
resolution,
|
||||
printExclusions = true,
|
||||
inverse,
|
||||
colors = !sys.props.get("sbt.log.noformat").toSeq.contains("true")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def coursierWhatDependsOnTask(
|
||||
moduleName: String,
|
||||
sbtClassifiers: Boolean = false,
|
||||
ignoreArtifactErrors: Boolean = false
|
||||
) = Def.task {
|
||||
val module = Parse.module(moduleName, scalaVersion.value)
|
||||
.right
|
||||
.getOrElse(throw new RuntimeException(s"Could not parse module `$moduleName`"))
|
||||
|
||||
val projectName = thisProjectRef.value.project
|
||||
|
||||
val resolutions = coursierResolutionTask(sbtClassifiers, ignoreArtifactErrors).value
|
||||
val result = new mutable.StringBuilder()
|
||||
for (ResolutionResult(subGraphConfigs, resolution, _) <- resolutions) {
|
||||
val roots: Seq[Dependency] = resolution.transitiveDependencies.filter(f => f.module == module)
|
||||
val strToPrint = s"$projectName (configurations ${subGraphConfigs.toVector.sorted.mkString(", ")})" + "\n" +
|
||||
Print.reverseTree(roots, resolution, withExclusions = true)
|
||||
.render(_.repr(Colors.get(!sys.props.get("sbt.log.noformat").toSeq.contains("true"))));
|
||||
println(strToPrint)
|
||||
result.append(strToPrint)
|
||||
result.append("\n")
|
||||
}
|
||||
|
||||
result.toString
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
scalaVersion := "2.11.8"
|
||||
|
||||
libraryDependencies += "org.apache.zookeeper" % "zookeeper" % "3.5.0-alpha"
|
||||
|
||||
lazy val whatDependsOnCheck = TaskKey[Unit]("whatDependsOnCheck")
|
||||
|
||||
import CoursierPlugin.autoImport._
|
||||
|
||||
whatDependsOnCheck := {
|
||||
val result = (coursierWhatDependsOn in Compile).toTask(" log4j:log4j").value
|
||||
val file = new File("whatDependsOnResult.log")
|
||||
assert(IO.read(file).toString == result)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
val pluginVersion = sys.props.getOrElse(
|
||||
"plugin.version",
|
||||
throw new RuntimeException(
|
||||
"""|The system property 'plugin.version' is not defined.
|
||||
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin
|
||||
)
|
||||
)
|
||||
|
||||
addSbtPlugin("io.get-coursier" % "sbt-coursier" % pluginVersion)
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
import org.apache.zookeeper.ZooKeeper
|
||||
|
||||
object Main extends App {
|
||||
Files.write(new File("output").toPath, classOf[ZooKeeper].getSimpleName.getBytes("UTF-8"))
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
> coursierDependencyTree
|
||||
> whatDependsOnCheck
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
dependency-graph (configurations compile, compile-internal, optional, provided, runtime, runtime-internal, test, test-internal)
|
||||
├─ log4j:log4j:1.2.16
|
||||
│ ├─ org.apache.zookeeper:zookeeper:3.5.0-alpha [33mlog4j:log4j:1.2.16 -> 1.2.17[0m
|
||||
│ └─ org.slf4j:slf4j-log4j12:1.7.5
|
||||
│ └─ org.apache.zookeeper:zookeeper:3.5.0-alpha
|
||||
└─ log4j:log4j:1.2.17
|
||||
├─ org.apache.zookeeper:zookeeper:3.5.0-alpha [33mlog4j:log4j:1.2.16 -> 1.2.17[0m
|
||||
└─ org.slf4j:slf4j-log4j12:1.7.5
|
||||
└─ org.apache.zookeeper:zookeeper:3.5.0-alpha
|
||||
|
|
@ -1,11 +1,25 @@
|
|||
package coursier.util
|
||||
|
||||
import coursier.core.Attributes
|
||||
import coursier.test.CentralTests
|
||||
import coursier.{Dependency, Module}
|
||||
import utest._
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
object PrintTests extends TestSuite {
|
||||
|
||||
object AppliedTree {
|
||||
def apply[A](tree: Tree[A]): Seq[AppliedTree[A]] = {
|
||||
tree.roots.map(root => {
|
||||
AppliedTree[A](root, apply(Tree(tree.children(root).toIndexedSeq, tree.children)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
case class AppliedTree[A](root: A, children: Seq[AppliedTree[A]])
|
||||
|
||||
|
||||
val tests = Tests {
|
||||
'ignoreAttributes - {
|
||||
val dep = Dependency(
|
||||
|
|
@ -23,6 +37,30 @@ object PrintTests extends TestSuite {
|
|||
|
||||
assert(res == expectedRes)
|
||||
}
|
||||
|
||||
'reverseTree - {
|
||||
val junit = Module("junit", "junit")
|
||||
val junitVersion = "4.10"
|
||||
|
||||
CentralTests.resolve(Set(Dependency(junit, junitVersion))).map(result => {
|
||||
val hamcrest = Module("org.hamcrest", "hamcrest-core")
|
||||
val hamcrestVersion = "1.1"
|
||||
val reverseTree = Print.reverseTree(Seq(Dependency(hamcrest, hamcrestVersion)),
|
||||
result, withExclusions = true)
|
||||
|
||||
val applied = AppliedTree.apply(reverseTree)
|
||||
assert(applied.length == 1)
|
||||
|
||||
val expectedHead = applied.head
|
||||
assert(expectedHead.root.module == hamcrest)
|
||||
assert(expectedHead.root.version == hamcrestVersion)
|
||||
assert(expectedHead.children.length == 1)
|
||||
|
||||
val expectedChild = expectedHead.children.head
|
||||
assert(expectedChild.root.module == junit)
|
||||
assert(expectedChild.root.version == junitVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue