Merge remote-tracking branch 'benjy/analysis_test' into 0.13

This commit is contained in:
Mark Harrah 2013-10-17 15:17:57 -04:00
commit e3e95f902d
3 changed files with 213 additions and 1 deletions

View File

@ -0,0 +1,88 @@
package sbt
package inc
import java.io.File
import scala.math.abs
import sbt.inc.TestCaseGenerators._
import org.scalacheck._
import Gen._
import Prop._
object AnalysisTest extends Properties("Analysis") {
// Merge and split a hard-coded trivial example.
property("Simple Merge and Split") = {
def f(s: String) = new File(s)
val aScala = f("A.scala")
val bScala = f("B.scala")
val aSource = genSource("A" :: "A$" :: Nil).sample.get
val bSource = genSource("B" :: "B$" :: Nil).sample.get
val cSource = genSource("C" :: Nil).sample.get
val exists = new Exists(true)
val sourceInfos = SourceInfos.makeInfo(Nil, Nil)
// a
var a = Analysis.Empty
a = a.addProduct(aScala, f("A.class"), exists, "A")
a = a.addProduct(aScala, f("A$.class"), exists, "A$")
a = a.addSource(aScala, aSource, exists, Nil, Nil, sourceInfos)
a = a.addBinaryDep(aScala, f("x.jar"), "x", exists)
a = a.addExternalDep(aScala, "C", cSource, inherited=false)
// b
var b = Analysis.Empty
b = b.addProduct(bScala, f("B.class"), exists, "B")
b = b.addProduct(bScala, f("B$.class"), exists, "B$")
b = b.addSource(bScala, bSource, exists, Nil, Nil, sourceInfos)
b = b.addBinaryDep(bScala, f("x.jar"), "x", exists)
b = b.addBinaryDep(bScala, f("y.jar"), "y", exists)
b = b.addExternalDep(bScala, "A", aSource, inherited=true)
// ab
var ab = Analysis.Empty
ab = ab.addProduct(aScala, f("A.class"), exists, "A")
ab = ab.addProduct(aScala, f("A$.class"), exists, "A$")
ab = ab.addProduct(bScala, f("B.class"), exists, "B")
ab = ab.addProduct(bScala, f("B$.class"), exists, "B$")
ab = ab.addSource(aScala, aSource, exists, Nil, Nil, sourceInfos)
ab = ab.addSource(bScala, bSource, exists, aScala :: Nil, aScala :: Nil, sourceInfos)
ab = ab.addBinaryDep(aScala, f("x.jar"), "x", exists)
ab = ab.addBinaryDep(bScala, f("x.jar"), "x", exists)
ab = ab.addBinaryDep(bScala, f("y.jar"), "y", exists)
ab = ab.addExternalDep(aScala, "C", cSource, inherited=false)
val split: Map[String, Analysis] = ab.groupBy({ f: File => f.getName.substring(0, 1) })
val aSplit = split.getOrElse("A", Analysis.Empty)
val bSplit = split.getOrElse("B", Analysis.Empty)
val merged = Analysis.merge(a :: b :: Nil)
("split(AB)(A) == A" |: compare(a, aSplit)) &&
("split(AB)(B) == B" |: compare(b, bSplit)) &&
("merge(A, B) == AB" |: compare(merged, ab))
}
// Merge and split large, generated examples.
// Mustn't shrink, as the default Shrink[Int] doesn't respect the lower bound of choose(), which will cause
// a divide-by-zero error masking the original error.
property("Complex Merge and Split") = forAllNoShrink(genAnalysis, choose(1, 10)) { (analysis: Analysis, numSplits: Int) =>
val grouped: Map[Int, Analysis] = analysis.groupBy({ f: File => abs(f.hashCode()) % numSplits})
def getGroup(i: Int): Analysis = grouped.getOrElse(i, Analysis.Empty)
val splits = (Range(0, numSplits) map getGroup).toList
val merged: Analysis = Analysis.merge(splits)
"Merge all" |: compare(analysis, merged)
}
// Compare two analyses with useful labelling when they aren't equal.
private[this] def compare(left: Analysis, right: Analysis) = {
val res = left == right
("UNEQUAL" |: res) ||
((" LEFT: " + left) |: false) ||
(("RIGHT: " + right) |: false) ||
(("STAMPS EQUAL: " + (left.stamps == right.stamps)) |: false) ||
(("APIS EQUAL: " + (left.apis == right.apis)) |: false) ||
(("RELATIONS EQUAL: " + (left.relations == right.relations)) |: false)
}
}

View File

@ -0,0 +1,124 @@
package sbt
package inc
import java.io.File
import org.scalacheck._
import Arbitrary._
import Gen._
import sbt.Relation
import xsbti.api._
import xsbti.SafeLazy
/**
* Scalacheck generators for Analysis objects and their substructures.
* Fairly complex, as Analysis has interconnected state that can't be
* independently generated.
*/
object TestCaseGenerators {
// We restrict sizes, otherwise the generated Analysis objects get huge and the tests take a long time.
val maxSources = 10 // Max number of source files.
val maxRelatives = 10 // Max number of things that a source x can relate to in a single Relation.
val maxPathSegmentLen = 10 // Max number of characters in a path segment.
val maxPathLen = 6 // Max number of path segments in a path.
// Ensure that we generate unique class names and file paths every time.
// Using repeated strings may lead to all sorts of undesirable interactions.
val used = scala.collection.mutable.Set.empty[String]
def unique[T](g: Gen[T]) = g suchThat { o: T => used.add(o.toString) }
def genFilePathSegment: Gen[String] = for {
n <- choose(3, maxPathSegmentLen) // Segments have at least 3 characters.
c <- alphaChar
cs <- listOfN(n - 1, alphaNumChar)
} yield (c::cs).mkString
def genFile: Gen[File] = for {
n <- choose(2, maxPathLen) // Paths have at least 2 segments.
path <- listOfN(n, genFilePathSegment)
} yield new File(path.mkString("/"))
def genStamp: Gen[Stamp] = for {
b <- oneOf(true, false)
} yield new Exists(b)
def zipMap[A, B](a: Seq[A], b: Seq[B]): Map[A, B] = (a zip b).toMap
def genStamps(rel: Relations): Gen[Stamps] = {
val prod = rel.allProducts.toList
val src = rel.allSources.toList
val bin = rel.allBinaryDeps.toList
for {
prodStamps <- listOfN(prod.length, genStamp)
srcStamps <- listOfN(src.length, genStamp)
binStamps <- listOfN(bin.length, genStamp)
binClassNames <- listOfN(bin.length, unique(identifier))
} yield Stamps(zipMap(prod, prodStamps), zipMap(src, srcStamps), zipMap(bin, binStamps), zipMap(bin, binClassNames))
}
// We need "proper" definitions with specific class names, as groupBy use these to pick a representative top-level class when splitting.
private[this] def makeDefinition(name: String): Definition =
new ClassLike(DefinitionType.ClassDef, lzy(new EmptyType()),
lzy(new Structure(lzy(Array()), lzy(Array()), lzy(Array()))), Array(), Array(),
name, new Public(), new Modifiers(false, false, false, false, false, false, false), Array())
private [this] def lzy[T <: AnyRef](x: T) = SafeLazy.strict(x)
def genSource(defns: Seq[String]): Gen[Source] = for {
startTime <- arbitrary[Long]
hashLen <- choose(10, 20) // Requred by SameAPI to be > 0.
hash <- Gen.containerOfN[Array,Byte](hashLen, arbitrary[Byte])
apiHash <- arbitrary[Int]
hasMacro <- arbitrary[Boolean]
} yield new Source(new Compilation(startTime, Array()), hash, new SourceAPI(Array(), Array(defns map makeDefinition:_*)), apiHash, hasMacro)
def genSources(all_defns: Seq[Seq[String]]): Gen[Seq[Source]] = Gen.sequence[List, Source](all_defns.map(genSource))
def genAPIs(rel: Relations): Gen[APIs] = {
val internal = rel.allInternalSrcDeps.toList.sorted
val external = rel.allExternalDeps.toList.sorted
for {
internalSources <- genSources(internal map { f: File => rel.classNames(f).toList.sorted })
externalSources <- genSources(external map { s: String => s :: Nil })
} yield APIs(zipMap(internal, internalSources), zipMap(external, externalSources))
}
def genRelation[T](g: Gen[T])(srcs: List[File]): Gen[Relation[File, T]] = for {
n <- choose(1, maxRelatives)
entries <- listOfN(srcs.length, containerOfN[Set, T](n, g))
} yield Relation.reconstruct(zipMap(srcs, entries))
val genFileRelation = genRelation[File](unique(genFile)) _
val genStringRelation = genRelation[String](unique(identifier)) _
def genRSource(srcs: List[File]): Gen[Relations.Source] = for {
internal <- listOfN(srcs.length, someOf(srcs)) // Internal dep targets must come from list of sources.
external <- genStringRelation(srcs)
} yield Relations.makeSource( // Ensure that we don't generate a dep of some file on itself.
Relation.reconstruct((srcs zip (internal map { _.toSet } ) map {case (a, b) => (a, b - a) }).toMap),
external)
def genSubRSource(src: Relations.Source): Gen[Relations.Source] = for {
internal <- someOf(src.internal.all.toList)
external <- someOf(src.external.all.toList)
} yield Relations.makeSource(Relation.empty ++ internal, Relation.empty ++ external)
def genRelations: Gen[Relations] = for {
numSrcs <- choose(0, maxSources)
srcs <- listOfN(numSrcs, genFile)
srcProd <- genFileRelation(srcs)
binaryDep <- genFileRelation(srcs)
direct <- genRSource(srcs)
publicInherited <- genSubRSource(direct)
classes <- genStringRelation(srcs)
} yield Relations.make(srcProd, binaryDep, direct, publicInherited , classes)
def genAnalysis: Gen[Analysis] = for {
rels <- genRelations
stamps <- genStamps(rels)
apis <- genAPIs(rels)
} yield new MAnalysis(stamps, apis, rels, SourceInfos.empty, Compilations.empty)
}

View File

@ -52,7 +52,7 @@ object RelationTest extends Properties("Relation")
}
property("Groups correctly") = forAll { (entries: List[(Int, Double)], randomInt: Int) =>
val splitInto = randomInt % 10 + 1 // Split into 1-10 groups.
val splitInto = math.abs(randomInt) % 10 + 1 // Split into 1-10 groups.
val rel = Relation.empty[Int, Double] ++ entries
val grouped = rel groupBy (_._1 % splitInto)
all(grouped.toSeq) {