Added conflict report and unit tests

This commit is contained in:
Eugene Yokota 2014-03-22 02:47:11 -04:00
parent f43daecee3
commit 5b1c33dd6e
2 changed files with 124 additions and 4 deletions

View File

@ -64,6 +64,8 @@ abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions
val label: String = getClass.getName.stripSuffix("$")
override def toString: String = label
/** The [[Configuration]]s to add to each project that activates this AutoPlugin.*/
def projectConfigurations: Seq[Configuration] = Nil
@ -169,8 +171,7 @@ object Plugins extends PluginsFunctions
val forbidden: Set[AutoPlugin] = (selectedPlugins flatMap { Plugins.asExclusions }).toSet
val c = selectedPlugins.toSet & forbidden
if (!c.isEmpty) {
val listString = (c map {_.label}).mkString(", ")
throw AutoPluginException(s"Contradiction in selected plugins. These plguins were both included and excluded: ${listString}")
exlusionConflictError(requestedPlugins, selectedPlugins, c.toSeq sortBy {_.label})
}
val retval = topologicalSort(selectedPlugins, log)
log.debug(s" :: sorted deduced result: ${retval.toString}")
@ -193,7 +194,7 @@ object Plugins extends PluginsFunctions
doSort(roots, nonRoots, ns.size * ns.size + 1)
}
private[sbt] def translateMessage(e: LogicException) = e match {
case ic: InitialContradictions => s"Contradiction in selected plugins. These plguins were both included and excluded: ${literalsString(ic.literals.toSeq)}"
case ic: InitialContradictions => s"Contradiction in selected plugins. These plugins were both included and excluded: ${literalsString(ic.literals.toSeq)}"
case io: InitialOverlap => s"Cannot directly enable plugins. Plugins are enabled when their required plugins are satisifed. The directly selected plugins were: ${literalsString(io.literals.toSeq)}"
case cn: CyclicNegation => s"Cycles in plugin requirements cannot involve excludes. The problematic cycle is: ${literalsString(cn.cycle)}"
}
@ -208,6 +209,29 @@ object Plugins extends PluginsFunctions
val message = s"Plugin$ns provided by multiple AutoPlugins:$nl${dupStrings.mkString(nl)}"
throw AutoPluginException(message)
}
private[this] def exlusionConflictError(requested: Plugins, selected: Seq[AutoPlugin], conflicting: Seq[AutoPlugin]) {
def listConflicts(ns: Seq[AutoPlugin]) = (ns map { c =>
val reasons = (if (flatten(requested) contains c) List("requested")
else Nil) ++
(if (c.requires != empty && c.trigger == allRequirements) List(s"enabled by ${c.requires.toString}")
else Nil) ++
{
val reqs = selected filter { x => asRequirements(x) contains c }
if (!reqs.isEmpty) List(s"""required by ${reqs.mkString(", ")}""")
else Nil
} ++
{
val exs = selected filter { x => asExclusions(x) contains c }
if (!exs.isEmpty) List(s"""excluded by ${exs.mkString(", ")}""")
else Nil
}
s""" - conflict: ${c.label} is ${reasons.mkString("; ")}"""
}).mkString("\n")
throw AutoPluginException(s"""Contradiction in enabled plugins:
- requested: ${requested.toString}
- enabled: ${selected.mkString(", ")}
${listConflicts(conflicting)}""")
}
private[sbt] final object Empty extends Plugins {
def &&(o: Basic): Plugins = o
@ -225,7 +249,7 @@ object Plugins extends PluginsFunctions
}
private[sbt] final case class And(plugins: List[Basic]) extends Plugins {
def &&(o: Basic): Plugins = And(o :: plugins)
override def toString = plugins.mkString(", ")
override def toString = plugins.mkString(" && ")
}
private[sbt] def and(a: Plugins, b: Plugins) = b match {
case Empty => a

View File

@ -0,0 +1,96 @@
package sbt
import java.io.File
import org.specs2._
import mutable.Specification
object PluginsTest extends Specification
{
import AI._
"Auto plugin" should {
"enable plugins with trigger=allRequirements AND requirements met" in {
deducePlugin(A && B, log) must contain(Q)
}
"enable transive plugins with trigger=allRequirements AND requirements met" in {
deducePlugin(A && B, log) must contain(R)
}
"order enable plugins after required plugins" in {
val ns = deducePlugin(A && B, log)
( (ns indexOf Q) must beGreaterThan(ns indexOf A) ) and
( (ns indexOf Q) must beGreaterThan(ns indexOf B) ) and
( (ns indexOf R) must beGreaterThan(ns indexOf A) ) and
( (ns indexOf R) must beGreaterThan(ns indexOf B) ) and
( (ns indexOf R) must beGreaterThan(ns indexOf Q) )
}
"not enable plugins with trigger=allRequirements but conflicting requirements" in {
deducePlugin(A && B, log) must not contain(S)
}
"enable plugins that are required by the requested plugins" in {
val ns = deducePlugin(Q, log)
(ns must contain(A)) and
(ns must contain(B))
}
"throw an AutoPluginException on conflicting requirements" in {
deducePlugin(S, log) must throwAn[AutoPluginException](message = """Contradiction in enabled plugins:
- requested: sbt.AI\$S
- enabled: sbt.AI\$S, sbt.AI\$Q, sbt.AI\$R, sbt.AI\$B, sbt.AI\$A
- conflict: sbt.AI\$R is enabled by sbt.AI\$Q; excluded by sbt.AI\$S""")
}
"generates a detailed report on conflicting requirements" in {
deducePlugin(T && U, log) must throwAn[AutoPluginException](message = """Contradiction in enabled plugins:
- requested: sbt.AI\$T && sbt.AI\$U
- enabled: sbt.AI\$U, sbt.AI\$T, sbt.AI\$A, sbt.AI\$Q, sbt.AI\$R, sbt.AI\$B
- conflict: sbt.AI\$Q is enabled by sbt.AI\$A && sbt.AI\$B; required by sbt.AI\$T, sbt.AI\$R; excluded by sbt.AI\$U
- conflict: sbt.AI\$R is enabled by sbt.AI\$Q; excluded by sbt.AI\$T""")
}
}
}
object AI
{
lazy val allPlugins: List[AutoPlugin] = List(A, B, Q, R, S, T, U)
lazy val deducePlugin = Plugins.deducer(allPlugins)
lazy val log = Logger.Null
trait EmptyAutoPlugin extends AutoPlugin {
def requires = empty
def trigger = noTrigger
}
object A extends EmptyAutoPlugin
object B extends EmptyAutoPlugin
object Q extends AutoPlugin
{
def requires: Plugins = A && B
def trigger = allRequirements
}
object R extends AutoPlugin
{
def requires = Q
def trigger = allRequirements
}
object S extends AutoPlugin
{
def requires = Q && !R
def trigger = allRequirements
}
// This is an opt-in plugin with a requirement
// Unless explicitly loaded by the build user, this will not be activated.
object T extends AutoPlugin
{
def requires = Q && !R
def trigger = noTrigger
}
// This is an opt-in plugin with a requirement
// Unless explicitly loaded by the build user, this will not be activated.
object U extends AutoPlugin
{
def requires = A && !Q
def trigger = noTrigger
}
}