mirror of https://github.com/sbt/sbt.git
Merge pull request #5137 from eed3si9n/wip/hedgehog
Use Scala Hedgehog in ParseKey test
This commit is contained in:
commit
0bdde4aee8
|
|
@ -67,7 +67,9 @@ def commonSettings: Seq[Setting[_]] = Def.settings(
|
|||
resolvers += Resolver.typesafeIvyRepo("releases").withName("typesafe-sbt-build-ivy-releases"),
|
||||
resolvers += Resolver.sonatypeRepo("snapshots"),
|
||||
resolvers += "bintray-sbt-maven-releases" at "https://dl.bintray.com/sbt/maven-releases/",
|
||||
resolvers += Resolver.url("bintray-scala-hedgehog", url("https://dl.bintray.com/hedgehogqa/scala-hedgehog"))(Resolver.ivyStylePatterns),
|
||||
addCompilerPlugin("org.spire-math" % "kind-projector" % "0.9.4" cross CrossVersion.binary),
|
||||
testFrameworks += TestFramework("hedgehog.sbt.Framework"),
|
||||
concurrentRestrictions in Global += Util.testExclusiveRestriction,
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"),
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "2"),
|
||||
|
|
|
|||
|
|
@ -7,125 +7,141 @@
|
|||
|
||||
package sbt
|
||||
|
||||
import Project._
|
||||
import sbt.internal.util.Types.idFun
|
||||
import sbt.internal.TestBuild._
|
||||
import sbt.librarymanagement.Configuration
|
||||
import org.scalacheck._
|
||||
import Prop._
|
||||
import Gen._
|
||||
import hedgehog._
|
||||
import hedgehog.Result.{ all, assert, failure, success }
|
||||
import hedgehog.runner._
|
||||
|
||||
object Delegates extends Properties("delegates") {
|
||||
property("generate non-empty configs") = forAll { (c: Vector[Configuration]) =>
|
||||
c.nonEmpty
|
||||
}
|
||||
property("generate non-empty tasks") = forAll { (t: Vector[Taskk]) =>
|
||||
t.nonEmpty
|
||||
}
|
||||
object Delegates extends Properties {
|
||||
|
||||
property("no duplicate scopes") = forAll { (keys: TestKeys) =>
|
||||
allDelegates(keys) { (_, ds) =>
|
||||
ds.distinct.size == ds.size
|
||||
}
|
||||
}
|
||||
property("delegates non-empty") = forAll { (keys: TestKeys) =>
|
||||
allDelegates(keys) { (_, ds) =>
|
||||
ds.nonEmpty
|
||||
}
|
||||
}
|
||||
|
||||
property("An initially Zero axis is Zero in all delegates") = allAxes(alwaysZero)
|
||||
|
||||
property("Projects precede builds precede Zero") = forAll { (keys: TestKeys) =>
|
||||
allDelegates(keys) { (scope, ds) =>
|
||||
val projectAxes = ds.map(_.project)
|
||||
val nonProject = projectAxes.dropWhile {
|
||||
case Select(_: ProjectRef) => true; case _ => false
|
||||
}
|
||||
val global = nonProject.dropWhile { case Select(_: BuildRef) => true; case _ => false }
|
||||
global forall { _ == Zero }
|
||||
}
|
||||
}
|
||||
|
||||
property("Initial scope present with all combinations of Global axes") = allAxes(
|
||||
(s, ds, _) => globalCombinations(s, ds)
|
||||
)
|
||||
|
||||
property("initial scope first") = forAll { (keys: TestKeys) =>
|
||||
allDelegates(keys) { (scope, ds) =>
|
||||
ds.head == scope
|
||||
}
|
||||
}
|
||||
|
||||
property("global scope last") = forAll { (keys: TestKeys) =>
|
||||
allDelegates(keys) { (_, ds) =>
|
||||
ds.last == Scope.GlobalScope
|
||||
}
|
||||
}
|
||||
|
||||
property("Project axis delegates to BuildRef then Zero") = forAll { (keys: TestKeys) =>
|
||||
allDelegates(keys) { (key, ds) =>
|
||||
key.project match {
|
||||
case Zero => true // filtering out of testing
|
||||
case Select(rr: ResolvedReference) =>
|
||||
rr match {
|
||||
case BuildRef(_) => ds.indexOf(key) < ds.indexOf(key.copy(project = Zero))
|
||||
case ProjectRef(uri, _) =>
|
||||
val buildScoped = key.copy(project = Select(BuildRef(uri)))
|
||||
val idxKey = ds.indexOf(key)
|
||||
val idxB = ds.indexOf(buildScoped)
|
||||
val z = key.copy(project = Zero)
|
||||
val idxZ = ds.indexOf(z)
|
||||
if (z == Scope.GlobalScope) true
|
||||
else
|
||||
(s"idxKey = $idxKey; idxB = $idxB; idxZ = $idxZ") |: (idxKey < idxB) && (idxB < idxZ)
|
||||
override def tests: List[Test] =
|
||||
List(
|
||||
property("generate non-empty configs", cGen.forAll.map { c =>
|
||||
assert(c.nonEmpty)
|
||||
}),
|
||||
property("generate non-empty tasks", tGen.forAll.map { t =>
|
||||
assert(t.nonEmpty)
|
||||
}),
|
||||
property("no duplicate scopes", keysGen.forAll.map { keys =>
|
||||
allDelegates(keys) { (_, ds) =>
|
||||
ds.distinct.size ==== ds.size
|
||||
}
|
||||
}),
|
||||
property("delegates non-empty", keysGen.forAll.map { keys =>
|
||||
allDelegates(keys) { (_, ds) =>
|
||||
assert(ds.nonEmpty)
|
||||
}
|
||||
}),
|
||||
property("An initially Zero axis is Zero in all delegates", allAxes(alwaysZero)),
|
||||
property(
|
||||
"Projects precede builds precede Zero",
|
||||
keysGen.forAll.map { keys =>
|
||||
allDelegates(keys) { (scope, ds) =>
|
||||
val projectAxes = ds.map(_.project)
|
||||
val nonProject = projectAxes.dropWhile {
|
||||
case Select(_: ProjectRef) => true; case _ => false
|
||||
}
|
||||
val global = nonProject.dropWhile { case Select(_: BuildRef) => true; case _ => false }
|
||||
all(global.map { _ ==== Zero }.toList)
|
||||
}
|
||||
case Select(_) | This =>
|
||||
throw new AssertionError(s"Scope's reference should be resolved, but was ${key.project}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property("Config axis delegates to parent configuration") = forAll { (keys: TestKeys) =>
|
||||
allDelegates(keys) { (key, ds) =>
|
||||
key.config match {
|
||||
case Zero => true
|
||||
case Select(config) if key.project.isSelect =>
|
||||
val p = key.project.toOption.get
|
||||
val r = keys.env.resolve(p)
|
||||
keys.env.inheritConfig(r, config).headOption.fold(Prop(true)) { parent =>
|
||||
val idxKey = ds.indexOf(key)
|
||||
val a = key.copy(config = Select(parent))
|
||||
val idxA = ds.indexOf(a)
|
||||
(s"idxKey = $idxKey; a = $a; idxA = $idxA") |: idxKey < idxA
|
||||
}
|
||||
),
|
||||
property(
|
||||
"Initial scope present with all combinations of Global axes",
|
||||
allAxes(
|
||||
(s, ds, _) => globalCombinations(s, ds)
|
||||
)
|
||||
),
|
||||
property("initial scope first", keysGen.forAll.map { keys =>
|
||||
allDelegates(keys) { (scope, ds) =>
|
||||
ds.head ==== scope
|
||||
}
|
||||
}),
|
||||
property("global scope last", keysGen.forAll.map { keys =>
|
||||
allDelegates(keys) { (_, ds) =>
|
||||
ds.last ==== Scope.GlobalScope
|
||||
}
|
||||
}),
|
||||
property(
|
||||
"Project axis delegates to BuildRef then Zero",
|
||||
keysGen.forAll.map { keys =>
|
||||
allDelegates(keys) {
|
||||
(key, ds) =>
|
||||
key.project match {
|
||||
case Zero => success // filtering out of testing
|
||||
case Select(rr: ResolvedReference) =>
|
||||
rr match {
|
||||
case BuildRef(_) =>
|
||||
assert(ds.indexOf(key) < ds.indexOf(key.copy(project = Zero)))
|
||||
case ProjectRef(uri, _) =>
|
||||
val buildScoped = key.copy(project = Select(BuildRef(uri)))
|
||||
val idxKey = ds.indexOf(key)
|
||||
val idxB = ds.indexOf(buildScoped)
|
||||
val z = key.copy(project = Zero)
|
||||
val idxZ = ds.indexOf(z)
|
||||
(z ==== Scope.GlobalScope)
|
||||
.or(
|
||||
assert((idxKey < idxB) && (idxB < idxZ))
|
||||
.log(s"idxKey = $idxKey; idxB = $idxB; idxZ = $idxZ")
|
||||
)
|
||||
}
|
||||
case Select(_) | This =>
|
||||
failure.log(s"Scope's reference should be resolved, but was ${key.project}")
|
||||
}
|
||||
}
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
property(
|
||||
"Config axis delegates to parent configuration",
|
||||
keysGen.forAll.map { keys =>
|
||||
allDelegates(keys) {
|
||||
(key, ds) =>
|
||||
key.config match {
|
||||
case Zero => success
|
||||
case Select(config) =>
|
||||
key.project match {
|
||||
case Select(p @ ProjectRef(_, _)) =>
|
||||
val r = keys.env.resolve(p)
|
||||
keys.env.inheritConfig(r, config).headOption.fold(success) { parent =>
|
||||
val idxKey = ds.indexOf(key)
|
||||
val a = key.copy(config = Select(parent))
|
||||
val idxA = ds.indexOf(a)
|
||||
assert(idxKey < idxA)
|
||||
.log(s"idxKey = $idxKey; a = $a; idxA = $idxA")
|
||||
}
|
||||
case _ => success
|
||||
}
|
||||
case _ => success
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def allAxes(f: (Scope, Seq[Scope], Scope => ScopeAxis[_]) => Prop): Prop = forAll {
|
||||
(keys: TestKeys) =>
|
||||
def allAxes(f: (Scope, Seq[Scope], Scope => ScopeAxis[_]) => hedgehog.Result): Property =
|
||||
keysGen.forAll.map { keys =>
|
||||
allDelegates(keys) { (s, ds) =>
|
||||
all(f(s, ds, _.project), f(s, ds, _.config), f(s, ds, _.task), f(s, ds, _.extra))
|
||||
all(List(f(s, ds, _.project), f(s, ds, _.config), f(s, ds, _.task), f(s, ds, _.extra)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def allDelegates(keys: TestKeys)(f: (Scope, Seq[Scope]) => Prop): Prop =
|
||||
all(keys.scopes map { scope =>
|
||||
def allDelegates(keys: TestKeys)(f: (Scope, Seq[Scope]) => hedgehog.Result): hedgehog.Result =
|
||||
all(keys.scopes.map { scope =>
|
||||
val delegates = keys.env.delegates(scope)
|
||||
("Scope: " + Scope.display(scope, "_")) |:
|
||||
("Delegates:\n\t" + delegates.map(scope => Scope.display(scope, "_")).mkString("\n\t")) |:
|
||||
f(scope, delegates)
|
||||
}: _*)
|
||||
f(scope, delegates)
|
||||
.log("Scope: " + Scope.display(scope, "_"))
|
||||
.log("Delegates:\n\t" + delegates.map(scope => Scope.display(scope, "_")).mkString("\n\t"))
|
||||
}.toList)
|
||||
|
||||
def alwaysZero(s: Scope, ds: Seq[Scope], axis: Scope => ScopeAxis[_]): Prop =
|
||||
(axis(s) != Zero) ||
|
||||
all(ds map { d =>
|
||||
(axis(d) == Zero): Prop
|
||||
}: _*)
|
||||
def alwaysZero(s: Scope, ds: Seq[Scope], axis: Scope => ScopeAxis[_]): hedgehog.Result =
|
||||
assert(axis(s) != Zero).or(
|
||||
all(ds.map { d =>
|
||||
axis(d) ==== Zero
|
||||
}.toList)
|
||||
)
|
||||
|
||||
def globalCombinations(s: Scope, ds: Seq[Scope]): Prop = {
|
||||
def globalCombinations(s: Scope, ds: Seq[Scope]): hedgehog.Result = {
|
||||
val mods = List[Scope => Scope](
|
||||
_.copy(project = Zero),
|
||||
_.copy(config = Zero),
|
||||
|
|
@ -143,6 +159,6 @@ object Delegates extends Properties("delegates") {
|
|||
loop(s, s :: acc, xs)
|
||||
}
|
||||
}
|
||||
all(loop(s, Nil, modAndIdent).map(ds contains _: Prop): _*)
|
||||
all(loop(s, Nil, modAndIdent).map(x => assert(ds contains x)).toList)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,26 +7,47 @@
|
|||
|
||||
package sbt
|
||||
|
||||
import java.net.URI
|
||||
|
||||
import org.scalacheck.Arbitrary.{ arbBool, arbitrary }
|
||||
import org.scalacheck.Gen._
|
||||
import org.scalacheck.Prop._
|
||||
import org.scalacheck._
|
||||
import sbt.Def.{ ScopedKey, displayFull, displayMasked }
|
||||
import sbt.internal.TestBuild._
|
||||
import sbt.internal.util.AttributeKey
|
||||
import sbt.internal.util.complete.{ DefaultParsers, Parser }
|
||||
import sbt.internal.util.complete.Parser
|
||||
import sbt.internal.{ Resolve, TestBuild }
|
||||
import sbt.librarymanagement.Configuration
|
||||
import hedgehog._
|
||||
import hedgehog.core.{ ShrinkLimit, SuccessCount }
|
||||
import hedgehog.runner._
|
||||
|
||||
/**
|
||||
* Tests that the scoped key parser in Act can correctly parse a ScopedKey converted by Def.show*Key.
|
||||
* This includes properly resolving omitted components.
|
||||
*/
|
||||
object ParseKey extends Properties("Key parser test") {
|
||||
propertyWithSeed("An explicitly specified axis is always parsed to that explicit value", None) =
|
||||
forAll(roundtrip(_))
|
||||
object ParseKey extends Properties {
|
||||
val exampleCount = 1000
|
||||
|
||||
override def tests: List[Test] = List(
|
||||
propertyN(
|
||||
"An explicitly specified axis is always parsed to that explicit value",
|
||||
arbStructureKeyMask.forAll.map(roundtrip),
|
||||
5000
|
||||
),
|
||||
propertyN(
|
||||
"An unspecified project axis resolves to the current project",
|
||||
arbStructureKeyMask.forAll.map(noProject),
|
||||
5000
|
||||
),
|
||||
propertyN(
|
||||
"An unspecified task axis resolves to Zero",
|
||||
arbStructureKeyMask.forAll.map(noTask),
|
||||
exampleCount
|
||||
),
|
||||
propertyN(
|
||||
"An unspecified configuration axis resolves to the first configuration directly defining the key or else Zero",
|
||||
arbStructureKeyMask.forAll.map(noConfig),
|
||||
exampleCount
|
||||
)
|
||||
)
|
||||
|
||||
def propertyN(name: String, result: => Property, n: Int): Test =
|
||||
Test(name, result)
|
||||
.config(_.copy(testLimit = SuccessCount(n), shrinkLimit = ShrinkLimit(n * 10)))
|
||||
|
||||
def roundtrip(skm: StructureKeyMask) = {
|
||||
import skm.{ structure, key }
|
||||
|
|
@ -37,70 +58,59 @@ object ParseKey extends Properties("Key parser test") {
|
|||
// so we mitigate this by explicitly displaying the configuration axis set to Zero
|
||||
val hasZeroConfig = key.scope.config == Zero
|
||||
|
||||
val showZeroConfig = hasZeroConfig || hasAmbiguousLowercaseAxes(key)
|
||||
val showZeroConfig = hasZeroConfig || hasAmbiguousLowercaseAxes(key, structure)
|
||||
val mask = if (showZeroConfig) skm.mask.copy(project = true) else skm.mask
|
||||
|
||||
val expected = resolve(structure, key, mask)
|
||||
parseCheck(structure, key, mask, showZeroConfig)(
|
||||
sk =>
|
||||
Project.equal(sk, expected, mask)
|
||||
:| s"$sk.key == $expected.key: ${sk.key == expected.key}"
|
||||
:| s"${sk.scope} == ${expected.scope}: ${Scope.equal(sk.scope, expected.scope, mask)}"
|
||||
) :| s"Expected: ${displayFull(expected)}"
|
||||
hedgehog.Result
|
||||
.assert(Project.equal(sk, expected, mask))
|
||||
.log(s"$sk.key == $expected.key: ${sk.key == expected.key}")
|
||||
.log(s"${sk.scope} == ${expected.scope}: ${Scope.equal(sk.scope, expected.scope, mask)}")
|
||||
).log(s"Expected: ${displayFull(expected)}")
|
||||
}
|
||||
|
||||
propertyWithSeed("An unspecified project axis resolves to the current project", None) = forAll(
|
||||
noProject(_)
|
||||
)
|
||||
|
||||
def noProject(skm: StructureKeyMask) = {
|
||||
import skm.{ structure, key }
|
||||
val mask = skm.mask.copy(project = false)
|
||||
// skip when config axis is set to Zero
|
||||
val hasZeroConfig = key.scope.config == Zero
|
||||
val showZeroConfig = hasAmbiguousLowercaseAxes(key)
|
||||
val hasZeroConfig = key.scope.config ==== Zero
|
||||
val showZeroConfig = hasAmbiguousLowercaseAxes(key, structure)
|
||||
parseCheck(structure, key, mask, showZeroConfig)(
|
||||
sk =>
|
||||
(hasZeroConfig || sk.scope.project == Select(structure.current))
|
||||
:| s"Current: ${structure.current}"
|
||||
(hasZeroConfig or sk.scope.project ==== Select(structure.current))
|
||||
.log(s"parsed subproject: ${sk.scope.project}")
|
||||
.log(s"current subproject: ${structure.current}")
|
||||
)
|
||||
}
|
||||
|
||||
propertyWithSeed("An unspecified task axis resolves to Zero", None) = forAll(noTask(_))
|
||||
|
||||
def noTask(skm: StructureKeyMask) = {
|
||||
import skm.{ structure, key }
|
||||
val mask = skm.mask.copy(task = false)
|
||||
parseCheck(structure, key, mask)(_.scope.task == Zero)
|
||||
parseCheck(structure, key, mask)(_.scope.task ==== Zero)
|
||||
}
|
||||
|
||||
propertyWithSeed(
|
||||
"An unspecified configuration axis resolves to the first configuration directly defining the key or else Zero",
|
||||
None
|
||||
) = forAll(noConfig(_))
|
||||
|
||||
def noConfig(skm: StructureKeyMask) = {
|
||||
import skm.{ structure, key }
|
||||
val mask = ScopeMask(config = false)
|
||||
val resolvedConfig = Resolve.resolveConfig(structure.extra, key.key, mask)(key.scope).config
|
||||
val showZeroConfig = hasAmbiguousLowercaseAxes(key)
|
||||
val showZeroConfig = hasAmbiguousLowercaseAxes(key, structure)
|
||||
parseCheck(structure, key, mask, showZeroConfig)(
|
||||
sk => (sk.scope.config == resolvedConfig) || (sk.scope == Scope.GlobalScope)
|
||||
) :| s"Expected configuration: ${resolvedConfig map (_.name)}"
|
||||
sk => (sk.scope.config ==== resolvedConfig) or (sk.scope ==== Scope.GlobalScope)
|
||||
).log(s"Expected configuration: ${resolvedConfig map (_.name)}")
|
||||
}
|
||||
|
||||
implicit val arbStructure: Arbitrary[Structure] = Arbitrary {
|
||||
val arbStructure: Gen[Structure] =
|
||||
for {
|
||||
env <- mkEnv
|
||||
loadFactor <- choose(0.0, 1.0)
|
||||
loadFactor <- Gen.double(Range.linearFrac(0.0, 1.0))
|
||||
scopes <- pickN(loadFactor, env.allFullScopes)
|
||||
current <- oneOf(env.allProjects.unzip._1)
|
||||
structure <- {
|
||||
val settings = structureSettings(scopes, env)
|
||||
TestBuild.structure(env, settings, current)
|
||||
}
|
||||
} yield structure
|
||||
}
|
||||
} yield {
|
||||
val settings = structureSettings(scopes, env)
|
||||
TestBuild.structure(env, settings, current)
|
||||
}
|
||||
|
||||
def structureSettings(scopes: Seq[Scope], env: Env): Seq[Def.Setting[String]] = {
|
||||
for {
|
||||
|
|
@ -111,18 +121,18 @@ object ParseKey extends Properties("Key parser test") {
|
|||
|
||||
final case class StructureKeyMask(structure: Structure, key: ScopedKey[_], mask: ScopeMask)
|
||||
|
||||
implicit val arbStructureKeyMask: Arbitrary[StructureKeyMask] = Arbitrary {
|
||||
for {
|
||||
val arbStructureKeyMask: Gen[StructureKeyMask] =
|
||||
(for {
|
||||
structure <- arbStructure
|
||||
// NOTE: Generating this after the structure improves shrinking
|
||||
mask <- maskGen
|
||||
structure <- arbitrary[Structure]
|
||||
key <- for {
|
||||
scope <- TestBuild.scope(structure.env)
|
||||
key <- oneOf(structure.allAttributeKeys.toSeq)
|
||||
} yield ScopedKey(scope, key)
|
||||
skm = StructureKeyMask(structure, key, mask)
|
||||
if configExistsInIndex(skm)
|
||||
} yield skm
|
||||
}
|
||||
} yield skm)
|
||||
.filter(configExistsInIndex)
|
||||
|
||||
private def configExistsInIndex(skm: StructureKeyMask): Boolean = {
|
||||
import skm._
|
||||
|
|
@ -153,18 +163,19 @@ object ParseKey extends Properties("Key parser test") {
|
|||
key: ScopedKey[_],
|
||||
mask: ScopeMask,
|
||||
showZeroConfig: Boolean = false,
|
||||
)(f: ScopedKey[_] => Prop): Prop = {
|
||||
)(f: ScopedKey[_] => hedgehog.Result): hedgehog.Result = {
|
||||
val s = displayMasked(key, mask, showZeroConfig)
|
||||
val parser = makeParser(structure)
|
||||
val parsed = Parser.result(parser, s).left.map(_().toString)
|
||||
(
|
||||
parsed.fold(_ => falsified, f)
|
||||
:| s"Key: ${Scope.displayPedantic(key.scope, key.key.label)}"
|
||||
:| s"Mask: $mask"
|
||||
:| s"Key string: '$s'"
|
||||
:| s"Parsed: ${parsed.right.map(displayFull)}"
|
||||
:| s"Structure: $structure"
|
||||
)
|
||||
parsed
|
||||
.fold(_ => hedgehog.Result.failure, f)
|
||||
.log(s"Key: ${Scope.displayPedantic(key.scope, key.key.label)}")
|
||||
.log(s"Mask: $mask")
|
||||
.log(s"Key string: '$s'")
|
||||
.log(s"Parsed: ${parsed.right.map(displayFull)}")
|
||||
.log(s"Structure: $structure")
|
||||
)
|
||||
}
|
||||
|
||||
// pickN is a function that randomly picks load % items from the "from" sequence.
|
||||
|
|
@ -172,207 +183,16 @@ object ParseKey extends Properties("Key parser test") {
|
|||
def pickN[T](load: Double, from: Seq[T]): Gen[Seq[T]] =
|
||||
pick((load * from.size).toInt max 1, from)
|
||||
|
||||
implicit val shrinkStructureKeyMask: Shrink[StructureKeyMask] = Shrink { skm =>
|
||||
Shrink
|
||||
.shrink(skm.structure)
|
||||
.map(s => skm.copy(structure = s))
|
||||
.flatMap(fixKey)
|
||||
}
|
||||
|
||||
def fixKey(skm: StructureKeyMask): Stream[StructureKeyMask] = {
|
||||
for {
|
||||
scope <- fixScope(skm)
|
||||
attributeKey <- fixAttributeKey(skm)
|
||||
} yield skm.copy(key = ScopedKey(scope, attributeKey))
|
||||
}
|
||||
|
||||
def fixScope(skm: StructureKeyMask): Stream[Scope] = {
|
||||
def validScope(scope: Scope) = scope match {
|
||||
case Scope(Select(BuildRef(build)), _, _, _) if !validBuild(build) => false
|
||||
case Scope(Select(ProjectRef(build, project)), _, _, _) if !validProject(build, project) =>
|
||||
false
|
||||
case Scope(Select(ProjectRef(build, project)), Select(ConfigKey(config)), _, _)
|
||||
if !validConfig(build, project, config) =>
|
||||
false
|
||||
case Scope(_, Select(ConfigKey(config)), _, _) if !configExists(config) =>
|
||||
false
|
||||
case Scope(_, _, Select(task), _) => validTask(task)
|
||||
case _ => true
|
||||
}
|
||||
def validBuild(build: URI) = skm.structure.env.buildMap.contains(build)
|
||||
def validProject(build: URI, project: String) = {
|
||||
skm.structure.env.buildMap
|
||||
.get(build)
|
||||
.exists(_.projectMap.contains(project))
|
||||
}
|
||||
def validConfig(build: URI, project: String, config: String) = {
|
||||
skm.structure.env.buildMap
|
||||
.get(build)
|
||||
.toSeq
|
||||
.flatMap(_.projectMap.get(project))
|
||||
.flatMap(_.configurations.map(_.name))
|
||||
.contains(config)
|
||||
}
|
||||
def configExists(config: String) = {
|
||||
val configs = for {
|
||||
build <- skm.structure.env.builds
|
||||
project <- build.projects
|
||||
config <- project.configurations
|
||||
} yield config.name
|
||||
configs.contains(config)
|
||||
}
|
||||
def validTask(task: AttributeKey[_]) = skm.structure.env.taskMap.contains(task)
|
||||
if (validScope(skm.key.scope)) {
|
||||
Stream(skm.key.scope)
|
||||
} else {
|
||||
// We could return all scopes here but we want to explore the other paths first since there
|
||||
// is a greater chance of a successful shrink. If necessary these could be appended to the end.
|
||||
Stream.empty
|
||||
}
|
||||
}
|
||||
|
||||
def fixAttributeKey(skm: StructureKeyMask): Stream[AttributeKey[_]] = {
|
||||
if (skm.structure.allAttributeKeys.contains(skm.key.key)) {
|
||||
Stream(skm.key.key)
|
||||
} else {
|
||||
// Likewise here, we should try other paths before trying different attribute keys.
|
||||
Stream.empty
|
||||
}
|
||||
}
|
||||
|
||||
implicit val shrinkStructure: Shrink[Structure] = Shrink { s =>
|
||||
Shrink.shrink(s.env).flatMap { env =>
|
||||
val scopes = s.data.scopes intersect env.allFullScopes.toSet
|
||||
val settings = structureSettings(scopes.toSeq, env)
|
||||
if (settings.nonEmpty) {
|
||||
val currents = env.allProjects.find {
|
||||
case (ref, _) => ref == s.current
|
||||
} match {
|
||||
case Some((current, _)) => Stream(current)
|
||||
case None => env.allProjects.map(_._1).toStream
|
||||
}
|
||||
currents.map(structure(env, settings, _))
|
||||
} else {
|
||||
Stream.empty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
implicit val shrinkEnv: Shrink[Env] = Shrink { env =>
|
||||
val shrunkBuilds = Shrink
|
||||
.shrink(env.builds)
|
||||
.filter(_.nonEmpty)
|
||||
.map(b => env.copy(builds = b))
|
||||
.map(fixProjectRefs)
|
||||
.map(fixConfigurations)
|
||||
val shrunkTasks = shrinkTasks(env.tasks)
|
||||
.map(t => env.copy(tasks = t))
|
||||
shrunkBuilds ++ shrunkTasks
|
||||
}
|
||||
|
||||
private def fixProjectRefs(env: Env): Env = {
|
||||
def fixBuild(build: Build): Build = {
|
||||
build.copy(projects = build.projects.map(fixProject))
|
||||
}
|
||||
def fixProject(project: Proj): Proj = {
|
||||
project.copy(delegates = project.delegates.filter(delegateExists))
|
||||
}
|
||||
def delegateExists(delegate: ProjectRef): Boolean = {
|
||||
env.buildMap
|
||||
.get(delegate.build)
|
||||
.flatMap(_.projectMap.get(delegate.project))
|
||||
.nonEmpty
|
||||
}
|
||||
env.copy(builds = env.builds.map(fixBuild))
|
||||
}
|
||||
|
||||
private def fixConfigurations(env: Env): Env = {
|
||||
val configs = env.allProjects.map {
|
||||
case (_, proj) => proj -> proj.configurations.toSet
|
||||
}.toMap
|
||||
|
||||
def fixBuild(build: Build): Build = {
|
||||
build.copy(projects = build.projects.map(fixProject(build.uri)))
|
||||
}
|
||||
def fixProject(buildURI: URI)(project: Proj): Proj = {
|
||||
val projConfigs = configs(project)
|
||||
project.copy(configurations = project.configurations.map(fixConfig(projConfigs)))
|
||||
}
|
||||
def fixConfig(projConfigs: Set[Configuration])(config: Configuration): Configuration = {
|
||||
import config.{ name => configName, _ }
|
||||
val extendsConfigs = config.extendsConfigs.filter(projConfigs.contains)
|
||||
Configuration.of(id, configName, description, isPublic, extendsConfigs, transitive)
|
||||
}
|
||||
env.copy(builds = env.builds.map(fixBuild))
|
||||
}
|
||||
|
||||
implicit val shrinkBuild: Shrink[Build] = Shrink { build =>
|
||||
Shrink
|
||||
.shrink(build.projects)
|
||||
.filter(_.nonEmpty)
|
||||
.map(p => build.copy(projects = p))
|
||||
// Could also shrink the URI here but that requires updating all the references.
|
||||
}
|
||||
|
||||
implicit val shrinkProject: Shrink[Proj] = Shrink { project =>
|
||||
val shrunkDelegates = Shrink
|
||||
.shrink(project.delegates)
|
||||
.map(d => project.copy(delegates = d))
|
||||
val shrunkConfigs = Shrink
|
||||
.shrink(project.configurations)
|
||||
.map(c => project.copy(configurations = c))
|
||||
val shrunkID = shrinkID(project.id)
|
||||
.map(id => project.copy(id = id))
|
||||
shrunkDelegates ++ shrunkConfigs ++ shrunkID
|
||||
}
|
||||
|
||||
implicit val shrinkDelegate: Shrink[ProjectRef] = Shrink { delegate =>
|
||||
val shrunkBuild = Shrink
|
||||
.shrink(delegate.build)
|
||||
.map(b => delegate.copy(build = b))
|
||||
val shrunkProject = Shrink
|
||||
.shrink(delegate.project)
|
||||
.map(p => delegate.copy(project = p))
|
||||
shrunkBuild ++ shrunkProject
|
||||
}
|
||||
|
||||
implicit val shrinkConfiguration: Shrink[Configuration] = Shrink { configuration =>
|
||||
import configuration.{ name => configName, _ }
|
||||
val shrunkExtends = Shrink
|
||||
.shrink(configuration.extendsConfigs)
|
||||
.map(configuration.withExtendsConfigs)
|
||||
val shrunkID = Shrink.shrink(id.tail).map { tail =>
|
||||
Configuration
|
||||
.of(id.head + tail, configName, description, isPublic, extendsConfigs, transitive)
|
||||
}
|
||||
shrunkExtends ++ shrunkID
|
||||
}
|
||||
|
||||
val shrinkStringLength: Shrink[String] = Shrink { s =>
|
||||
// Only change the string length don't change the characters.
|
||||
implicit val shrinkChar: Shrink[Char] = Shrink.shrinkAny
|
||||
Shrink.shrinkContainer[List, Char].shrink(s.toList).map(_.mkString)
|
||||
}
|
||||
|
||||
def shrinkID(id: String): Stream[String] = {
|
||||
Shrink.shrink(id).filter(DefaultParsers.validID)
|
||||
}
|
||||
|
||||
def shrinkTasks(tasks: Vector[Taskk]): Stream[Vector[Taskk]] = {
|
||||
Shrink.shrink(tasks)
|
||||
}
|
||||
|
||||
implicit val shrinkTask: Shrink[Taskk] = Shrink { task =>
|
||||
Shrink.shrink((task.delegates, task.key)).map {
|
||||
case (delegates, key) => Taskk(key, delegates)
|
||||
}
|
||||
}
|
||||
|
||||
// if both a project and a key share the same name (e.g. "foo")
|
||||
// then a scoped key like `foo/<conf>/foo/name` would render as `foo/name`
|
||||
// which would be interpreted as `foo/Zero/Zero/name`
|
||||
// so we mitigate this by explicitly displaying the configuration axis set to Zero
|
||||
def hasAmbiguousLowercaseAxes(key: ScopedKey[_]) = PartialFunction.cond(key.scope) {
|
||||
case Scope(Select(ProjectRef(_, proj)), _, Select(key), _) => proj == key.label
|
||||
def hasAmbiguousLowercaseAxes(key: ScopedKey[_], structure: Structure): Boolean = {
|
||||
val label = key.key.label
|
||||
val allProjects = for {
|
||||
uri <- structure.keyIndex.buildURIs
|
||||
project <- structure.keyIndex.projects(uri)
|
||||
} yield project
|
||||
allProjects(label)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,43 +5,36 @@
|
|||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
|
||||
import java.net.URI
|
||||
|
||||
import org.scalatest.matchers.MatchResult
|
||||
import org.scalatest.prop.PropertyChecks
|
||||
import org.scalatest.{ Matchers, PropSpec }
|
||||
import sbt.Def._
|
||||
import sbt._
|
||||
import sbt.internal.TestBuild
|
||||
import sbt.internal.TestBuild._
|
||||
import sbt.internal.util.AttributeKey
|
||||
import sbt.internal.util.complete.DefaultParsers
|
||||
import sbt.librarymanagement.Configuration
|
||||
import hedgehog._
|
||||
import hedgehog.runner._
|
||||
|
||||
class ParserSpec extends PropSpec with PropertyChecks with Matchers {
|
||||
property("can parse any build") {
|
||||
forAll(TestBuild.uriGen) { uri =>
|
||||
parse(buildURI = uri)
|
||||
}
|
||||
}
|
||||
|
||||
property("can parse any project") {
|
||||
forAll(TestBuild.nonEmptyId) { id =>
|
||||
parse(projectID = id)
|
||||
}
|
||||
}
|
||||
|
||||
property("can parse any configuration") {
|
||||
forAll(TestBuild.nonEmptyId.map(_.capitalize)) { name =>
|
||||
parse(configName = name)
|
||||
}
|
||||
}
|
||||
|
||||
property("can parse any attribute") {
|
||||
forAll(TestBuild.kebabIdGen) { name =>
|
||||
parse(attributeName = name)
|
||||
}
|
||||
}
|
||||
object ParserSpec extends Properties {
|
||||
override def tests: List[Test] =
|
||||
List(
|
||||
property("can parse any build", TestBuild.uriGen.forAll.map { uri =>
|
||||
parse(buildURI = uri)
|
||||
}),
|
||||
property("can parse any project", TestBuild.nonEmptyId.forAll.map { id =>
|
||||
parse(projectID = id)
|
||||
}),
|
||||
property("can parse any configuration", TestBuild.nonEmptyId.map(_.capitalize).forAll.map {
|
||||
name =>
|
||||
parse(configName = name)
|
||||
}),
|
||||
property("can parse any attribute", TestBuild.kebabIdGen.forAll.map { name =>
|
||||
parse(attributeName = name)
|
||||
})
|
||||
)
|
||||
|
||||
private def parse(
|
||||
buildURI: URI = new java.net.URI("file", "///path/", null),
|
||||
|
|
@ -71,14 +64,9 @@ class ParserSpec extends PropSpec with PropertyChecks with Matchers {
|
|||
val structure = TestBuild.structure(env, settings, build.allProjects.head._1)
|
||||
val string = displayMasked(scopedKey, ScopeMask())
|
||||
val parser = makeParser(structure)
|
||||
string should { left =>
|
||||
val result = DefaultParsers.result(parser, left)
|
||||
val resultStr = result.fold(_ => "<parse error>", _.toString)
|
||||
MatchResult(
|
||||
result == Right(scopedKey),
|
||||
s"$left parsed back to $resultStr rather than $scopedKey",
|
||||
s"$left parsed back to $scopedKey",
|
||||
)
|
||||
}
|
||||
val result = DefaultParsers.result(parser, string)
|
||||
val resultStr = result.fold(_ => "<parse error>", _.toString)
|
||||
(result ==== Right(scopedKey))
|
||||
.log(s"$string parsed back to $resultStr rather than $scopedKey")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,16 +10,15 @@ package internal
|
|||
|
||||
import Def.{ ScopedKey, Setting }
|
||||
import sbt.internal.util.{ AttributeKey, AttributeMap, Relation, Settings }
|
||||
import sbt.internal.util.Types.const
|
||||
import sbt.internal.util.Types.{ const, some }
|
||||
import sbt.internal.util.complete.Parser
|
||||
import sbt.librarymanagement.Configuration
|
||||
|
||||
import java.net.URI
|
||||
import org.scalacheck._
|
||||
import Gen._
|
||||
|
||||
// Notes:
|
||||
// Generator doesn't produce cross-build project dependencies or do anything with the 'extra' axis
|
||||
import hedgehog._
|
||||
import hedgehog.predef.sequence
|
||||
|
||||
object TestBuild extends TestBuild
|
||||
abstract class TestBuild {
|
||||
val MaxTasks = 6
|
||||
|
|
@ -30,25 +29,26 @@ abstract class TestBuild {
|
|||
val MaxDeps = 8
|
||||
val KeysPerEnv = 10
|
||||
|
||||
val MaxTasksGen = chooseShrinkable(1, MaxTasks)
|
||||
val MaxProjectsGen = chooseShrinkable(1, MaxProjects)
|
||||
val MaxConfigsGen = chooseShrinkable(1, MaxConfigs)
|
||||
val MaxBuildsGen = chooseShrinkable(1, MaxBuilds)
|
||||
val MaxDepsGen = chooseShrinkable(0, MaxDeps)
|
||||
val MaxTasksGen = Range.linear(1, MaxTasks)
|
||||
val MaxProjectsGen = Range.linear(1, MaxProjects)
|
||||
val MaxConfigsGen = Range.linear(1, MaxConfigs)
|
||||
val MaxBuildsGen = Range.linear(1, MaxBuilds)
|
||||
val MaxDepsGen = Range.linear(0, MaxDeps)
|
||||
val MaxIDSizeGen = Range.linear(0, MaxIDSize)
|
||||
|
||||
def chooseShrinkable(min: Int, max: Int): Gen[Int] =
|
||||
sized(sz => choose(min, (max min sz) max 1))
|
||||
def alphaLowerChar: Gen[Char] = Gen.char('a', 'z')
|
||||
def alphaUpperChar: Gen[Char] = Gen.char('A', 'Z')
|
||||
def numChar: Gen[Char] = Gen.char('0', '9')
|
||||
def alphaNumChar: Gen[Char] =
|
||||
Gen.frequency1(8 -> alphaLowerChar, 1 -> alphaUpperChar, 1 -> numChar)
|
||||
|
||||
val nonEmptyId = for {
|
||||
c <- alphaLowerChar
|
||||
cs <- listOfN(MaxIDSize, alphaNumChar)
|
||||
cs <- Gen.list(alphaNumChar, MaxIDSizeGen)
|
||||
} yield (c :: cs).mkString
|
||||
|
||||
implicit val cGen = Arbitrary {
|
||||
genConfigs(nonEmptyId.map(_.capitalize), MaxDepsGen, MaxConfigsGen)
|
||||
}
|
||||
implicit val tGen = Arbitrary { genTasks(kebabIdGen, MaxDepsGen, MaxTasksGen) }
|
||||
val seed = rng.Seed.random
|
||||
def cGen = genConfigs(nonEmptyId map { _.capitalize }, MaxDepsGen, MaxConfigsGen)
|
||||
def tGen = genTasks(kebabIdGen, MaxDepsGen, MaxTasksGen)
|
||||
|
||||
class TestKeys(val env: Env, val scopes: Seq[Scope]) {
|
||||
override def toString = env + "\n" + scopes.mkString("Scopes:\n\t", "\n\t", "")
|
||||
|
|
@ -194,12 +194,11 @@ abstract class TestBuild {
|
|||
(f(t), t)
|
||||
} toMap;
|
||||
|
||||
implicit lazy val arbKeys: Arbitrary[TestKeys] = Arbitrary(keysGen)
|
||||
lazy val keysGen: Gen[TestKeys] = for {
|
||||
env <- mkEnv
|
||||
keyCount <- chooseShrinkable(1, KeysPerEnv)
|
||||
keys <- listOfN(keyCount, scope(env))
|
||||
} yield new TestKeys(env, keys)
|
||||
lazy val keysGen: Gen[TestKeys] =
|
||||
for {
|
||||
env <- mkEnv
|
||||
keys <- scope(env).list(Range.linear(1, KeysPerEnv))
|
||||
} yield new TestKeys(env, keys)
|
||||
|
||||
def scope(env: Env): Gen[Scope] =
|
||||
for {
|
||||
|
|
@ -207,11 +206,16 @@ abstract class TestBuild {
|
|||
project <- oneOf(build.projects)
|
||||
cAxis <- oneOrGlobal(project.configurations map toConfigKey)
|
||||
tAxis <- oneOrGlobal(env.tasks map getKey)
|
||||
pAxis <- orGlobal(frequency((1, BuildRef(build.uri)), (3, ProjectRef(build.uri, project.id))))
|
||||
pAxis <- orGlobal(
|
||||
Gen.frequency1(
|
||||
(1, Gen.constant[Reference](BuildRef(build.uri))),
|
||||
(3, Gen.constant[Reference](ProjectRef(build.uri, project.id)))
|
||||
)
|
||||
)
|
||||
} yield Scope(pAxis, cAxis, tAxis, Zero)
|
||||
|
||||
def orGlobal[T](gen: Gen[T]): Gen[ScopeAxis[T]] =
|
||||
frequency((1, gen map Select.apply), (1, Zero))
|
||||
Gen.frequency1((1, gen map Select.apply), (1, Gen.constant(Zero)))
|
||||
def oneOrGlobal[T](gen: Seq[T]): Gen[ScopeAxis[T]] = orGlobal(oneOf(gen))
|
||||
|
||||
def makeParser(structure: Structure): Parser[ScopedKey[_]] = {
|
||||
|
|
@ -227,7 +231,7 @@ abstract class TestBuild {
|
|||
}
|
||||
|
||||
def structure(env: Env, settings: Seq[Setting[_]], current: ProjectRef): Structure = {
|
||||
implicit val display = Def.showRelativeKey2(current)
|
||||
val display = Def.showRelativeKey2(current)
|
||||
if (settings.isEmpty) {
|
||||
try {
|
||||
sys.error("settings is empty")
|
||||
|
|
@ -249,54 +253,56 @@ abstract class TestBuild {
|
|||
Structure(env, current, data, KeyIndex(keys, projectsMap, confMap), keyMap)
|
||||
}
|
||||
|
||||
implicit lazy val mkEnv: Gen[Env] = {
|
||||
implicit val pGen = (uri: URI) =>
|
||||
genProjects(uri)(nonEmptyId, MaxDepsGen, MaxProjectsGen, cGen.arbitrary)
|
||||
envGen(buildGen(uriGen, pGen), tGen.arbitrary)
|
||||
lazy val mkEnv: Gen[Env] = {
|
||||
val pGen = (uri: URI) => genProjects(uri)(nonEmptyId, MaxDepsGen, MaxProjectsGen, cGen)
|
||||
envGen(buildGen(uriGen, pGen), tGen)
|
||||
}
|
||||
|
||||
implicit def maskGen(implicit arbBoolean: Arbitrary[Boolean]): Gen[ScopeMask] = {
|
||||
val b = arbBoolean.arbitrary
|
||||
def maskGen: Gen[ScopeMask] = {
|
||||
val b = Gen.boolean
|
||||
for (p <- b; c <- b; t <- b; x <- b)
|
||||
yield ScopeMask(project = p, config = c, task = t, extra = x)
|
||||
}
|
||||
|
||||
val kebabIdGen: Gen[String] = for {
|
||||
c <- alphaLowerChar
|
||||
cs <- listOfN(MaxIDSize - 2, frequency(MaxIDSize -> alphaNumChar, 1 -> Gen.const('-')))
|
||||
cs <- Gen.list(
|
||||
Gen.frequency(MaxIDSize -> alphaNumChar, List(1 -> Gen.constant('-'))),
|
||||
Range.linear(0, MaxIDSize - 2)
|
||||
)
|
||||
end <- alphaNumChar
|
||||
} yield (List(c) ++ cs ++ List(end)).mkString
|
||||
|
||||
val uriChar: Gen[Char] = {
|
||||
frequency(9 -> alphaNumChar, 1 -> oneOf("/?-".toSeq))
|
||||
}
|
||||
val optIDGen: Gen[Option[String]] = Gen.choice1(nonEmptyId.map(some.fn), Gen.constant(None))
|
||||
|
||||
val optIDGen: Gen[Option[String]] =
|
||||
Gen.oneOf(nonEmptyId.map(x => Some(x)), Gen.const(None))
|
||||
val pathGen = for {
|
||||
c <- alphaLowerChar
|
||||
cs <- Gen.list(alphaNumChar, Range.linear(6, MaxIDSize))
|
||||
} yield (c :: cs).mkString
|
||||
|
||||
val uriGen: Gen[URI] = {
|
||||
for {
|
||||
ssp <- nonEmptyId
|
||||
ssp <- pathGen
|
||||
frag <- optIDGen
|
||||
} yield new URI("file", "///" + ssp + "/", frag.orNull)
|
||||
}
|
||||
|
||||
implicit def envGen(implicit bGen: Gen[Build], tasks: Gen[Vector[Taskk]]): Gen[Env] =
|
||||
for (i <- MaxBuildsGen; bs <- containerOfN[Vector, Build](i, bGen); ts <- tasks)
|
||||
def envGen(bGen: Gen[Build], tasks: Gen[Vector[Taskk]]): Gen[Env] =
|
||||
for (bs <- bGen.list(MaxBuildsGen).map(_.toVector); ts <- tasks)
|
||||
yield new Env(bs, ts)
|
||||
implicit def buildGen(implicit uGen: Gen[URI], pGen: URI => Gen[Seq[Proj]]): Gen[Build] =
|
||||
def buildGen(uGen: Gen[URI], pGen: URI => Gen[Vector[Proj]]): Gen[Build] =
|
||||
for (u <- uGen; ps <- pGen(u)) yield new Build(u, ps)
|
||||
|
||||
def nGen[T](igen: Gen[Int])(implicit g: Gen[T]): Gen[Vector[T]] = igen flatMap { ig =>
|
||||
containerOfN[Vector, T](ig, g)
|
||||
def nGen[T](igen: Gen[Int])(g: Gen[T]): Gen[Vector[T]] = igen flatMap { ig =>
|
||||
g.list(Range.linear(ig, ig)).map(_.toVector)
|
||||
}
|
||||
|
||||
implicit def genProjects(build: URI)(
|
||||
implicit genID: Gen[String],
|
||||
maxDeps: Gen[Int],
|
||||
count: Gen[Int],
|
||||
confs: Gen[Seq[Configuration]]
|
||||
): Gen[Seq[Proj]] =
|
||||
def genProjects(build: URI)(
|
||||
genID: Gen[String],
|
||||
maxDeps: Range[Int],
|
||||
count: Range[Int],
|
||||
confs: Gen[Vector[Configuration]]
|
||||
): Gen[Vector[Proj]] =
|
||||
genAcyclic(maxDeps, genID, count) { (id: String) =>
|
||||
for (cs <- confs) yield { (deps: Seq[Proj]) =>
|
||||
new Proj(id, deps.map { dep =>
|
||||
|
|
@ -307,8 +313,8 @@ abstract class TestBuild {
|
|||
|
||||
def genConfigs(
|
||||
implicit genName: Gen[String],
|
||||
maxDeps: Gen[Int],
|
||||
count: Gen[Int]
|
||||
maxDeps: Range[Int],
|
||||
count: Range[Int]
|
||||
): Gen[Vector[Configuration]] =
|
||||
genAcyclicDirect[Configuration, String](maxDeps, genName, count)(
|
||||
(key, deps) =>
|
||||
|
|
@ -319,35 +325,34 @@ abstract class TestBuild {
|
|||
|
||||
def genTasks(
|
||||
implicit genName: Gen[String],
|
||||
maxDeps: Gen[Int],
|
||||
count: Gen[Int]
|
||||
maxDeps: Range[Int],
|
||||
count: Range[Int]
|
||||
): Gen[Vector[Taskk]] =
|
||||
genAcyclicDirect[Taskk, String](maxDeps, genName, count)(
|
||||
(key, deps) => new Taskk(AttributeKey[String](key), deps)
|
||||
)
|
||||
|
||||
def genAcyclicDirect[A, T](maxDeps: Gen[Int], keyGen: Gen[T], max: Gen[Int])(
|
||||
def genAcyclicDirect[A, T](maxDeps: Range[Int], keyGen: Gen[T], max: Range[Int])(
|
||||
make: (T, Vector[A]) => A
|
||||
): Gen[Vector[A]] =
|
||||
genAcyclic[A, T](maxDeps, keyGen, max) { t =>
|
||||
Gen.const { deps =>
|
||||
Gen.constant { deps =>
|
||||
make(t, deps.toVector)
|
||||
}
|
||||
}
|
||||
|
||||
def genAcyclic[A, T](maxDeps: Gen[Int], keyGen: Gen[T], max: Gen[Int])(
|
||||
def genAcyclic[A, T](maxDeps: Range[Int], keyGen: Gen[T], max: Range[Int])(
|
||||
make: T => Gen[Vector[A] => A]
|
||||
): Gen[Vector[A]] =
|
||||
max flatMap { count =>
|
||||
containerOfN[Vector, T](count, keyGen) flatMap { keys =>
|
||||
genAcyclic(maxDeps, keys.distinct)(make)
|
||||
}
|
||||
): Gen[Vector[A]] = {
|
||||
keyGen.list(max) flatMap { keys =>
|
||||
genAcyclic(maxDeps, keys.distinct.toVector)(make)
|
||||
}
|
||||
def genAcyclic[A, T](maxDeps: Gen[Int], keys: Vector[T])(
|
||||
}
|
||||
def genAcyclic[A, T](maxDeps: Range[Int], keys: Vector[T])(
|
||||
make: T => Gen[Vector[A] => A]
|
||||
): Gen[Vector[A]] =
|
||||
genAcyclic(maxDeps, keys, Vector()) flatMap { pairs =>
|
||||
sequence(pairs.map { case (key, deps) => mapMake(key, deps, make) }) flatMap { inputs =>
|
||||
sequence(pairs.map { case (key, deps) => mapMake(key, deps, make) }.toList) map { inputs =>
|
||||
val made = new collection.mutable.HashMap[T, A]
|
||||
for ((key, deps, mk) <- inputs)
|
||||
made(key) = mk(deps map made)
|
||||
|
|
@ -361,22 +366,36 @@ abstract class TestBuild {
|
|||
}
|
||||
|
||||
def genAcyclic[T](
|
||||
maxDeps: Gen[Int],
|
||||
maxDeps: Range[Int],
|
||||
names: Vector[T],
|
||||
acc: Vector[Gen[(T, Vector[T])]]
|
||||
): Gen[Vector[(T, Vector[T])]] =
|
||||
names match {
|
||||
case Vector() => sequence(acc)
|
||||
case Vector() => sequence(acc.toList).map(_.toVector)
|
||||
case Vector(x, xs @ _*) =>
|
||||
val next =
|
||||
for (depCount <- maxDeps; d <- pick(depCount min xs.size, xs))
|
||||
for (depCount <- Gen.int(maxDeps); d <- pick(depCount, xs))
|
||||
yield (x, d.toVector)
|
||||
genAcyclic(maxDeps, xs.toVector, next +: acc)
|
||||
}
|
||||
def sequence[T](gs: Vector[Gen[T]]): Gen[Vector[T]] = Gen.parameterized { prms =>
|
||||
delay(gs map { g =>
|
||||
g(prms, seed) getOrElse sys.error("failed generator")
|
||||
})
|
||||
}
|
||||
|
||||
type Inputs[A, T] = (T, Vector[T], Vector[A] => A)
|
||||
|
||||
def oneOf[A](a: Seq[A]): Gen[A] =
|
||||
Gen.element(a.head, a.tail.toList)
|
||||
|
||||
// TODO Should move to hedgehog possible?
|
||||
def pick[A](n: Int, as: Seq[A]): Gen[Seq[A]] = {
|
||||
if (n >= as.length) {
|
||||
Gen.constant(as)
|
||||
} else {
|
||||
def go(m: Int, bs: Set[Int], cs: Set[Int]): Gen[Set[Int]] =
|
||||
if (m == 0)
|
||||
Gen.constant(cs)
|
||||
else
|
||||
Gen.element(bs.head, bs.tail.toList).flatMap(a => go(m - 1, bs - a, cs + a))
|
||||
go(n, as.indices.toSet, Set())
|
||||
.map(is => as.zipWithIndex.flatMap(a => if (is(a._2)) Seq(a._1) else Seq()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,4 +141,6 @@ object Dependencies {
|
|||
val log4jDependencies = Vector(log4jApi, log4jCore, log4jSlf4jImpl)
|
||||
|
||||
val scalaCacheCaffeine = "com.github.cb372" %% "scalacache-caffeine" % "0.20.0"
|
||||
|
||||
val hedgehog = "hedgehog" %% "hedgehog-sbt" % "0.1.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ object NightlyPlugin extends AutoPlugin {
|
|||
|
||||
def testDependencies = libraryDependencies ++= (
|
||||
if (includeTestDependencies.value)
|
||||
Seq(scalacheck % Test, specs2 % Test, junit % Test, scalatest % Test)
|
||||
Seq(scalacheck % Test, specs2 % Test, junit % Test, scalatest % Test, hedgehog % Test)
|
||||
else Seq()
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue