From c55ae4fd1823d09fa5b75d5d6bd3c3a4625087c9 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Wed, 3 Sep 2014 23:10:22 +0200 Subject: [PATCH] Fix random Analysis generator for ScalaCheck Unit tests in incremental-compiler subproject use a generator to create random Analysis objects. This generator was unfortunately not working properly and generated only empty Analyses (it failed to generate any non-empty Analysis because of a bug in the `unique` generator). --- .../src/test/scala/sbt/inc/AnalysisTest.scala | 4 +- .../scala/sbt/inc/TestCaseGenerators.scala | 40 +++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/compile/inc/src/test/scala/sbt/inc/AnalysisTest.scala b/compile/inc/src/test/scala/sbt/inc/AnalysisTest.scala index 13a2b1cc6..437176881 100644 --- a/compile/inc/src/test/scala/sbt/inc/AnalysisTest.scala +++ b/compile/inc/src/test/scala/sbt/inc/AnalysisTest.scala @@ -65,7 +65,9 @@ object AnalysisTest extends Properties("Analysis") { // 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) => + // Note that the generated Analyses have nameHashing = false (Grouping of Analyses with name hashing enabled + // is not supported right now) + property("Complex Merge and Split") = forAllNoShrink(genAnalysis(nameHashing = false), 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(false)) val splits = (Range(0, numSplits) map getGroup).toList diff --git a/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala b/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala index f260a0fbf..2c8d7a505 100644 --- a/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala +++ b/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala @@ -25,9 +25,18 @@ object TestCaseGenerators { // 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] + val used1 = scala.collection.mutable.Set.empty[String] + val used2 = scala.collection.mutable.Set.empty[String] - def unique[T](g: Gen[T]) = g retryUntil { o: T => used.add(o.toString) } + // When using `retryUntil`, the condition is actually tested twice (see implementation in ScalaCheck), + // which is why we need to insert twice the element. + // If the element is present in both sets, then it has already been used. + def unique[T](g: Gen[T]) = g retryUntil { o: T => + if (used1.add(o.toString)) + true + else + used2.add(o.toString) + } def identifier: Gen[String] = sized { size => resize(Math.max(size, 3), Gen.identifier) @@ -134,6 +143,18 @@ object TestCaseGenerators { external <- someOf(src.external.all.toList) } yield Relations.makeSource(Relation.empty ++ internal, Relation.empty ++ external) + def genRSourceDependencies(srcs: List[File]): Gen[Relations.SourceDependencies] = for { + internal <- listOfN(srcs.length, someOf(srcs)) + external <- genStringRelation(srcs) + } yield Relations.makeSourceDependencies( + Relation.reconstruct((srcs zip (internal map { _.toSet }) map { case (a, b) => (a, b - a) }).toMap), + external) + + def genSubRSourceDependencies(src: Relations.SourceDependencies): Gen[Relations.SourceDependencies] = for { + internal <- someOf(src.internal.all.toList) + external <- someOf(src.external.all.toList) + } yield Relations.makeSourceDependencies(Relation.empty ++ internal, Relation.empty ++ external) + def genRelations: Gen[Relations] = for { numSrcs <- choose(0, maxSources) srcs <- listOfN(numSrcs, genFile) @@ -145,8 +166,19 @@ object TestCaseGenerators { } yield Relations.make(srcProd, binaryDep, direct, publicInherited, classes) - def genAnalysis: Gen[Analysis] = for { - rels <- genRelations + def genRelationsNameHashing: Gen[Relations] = for { + numSrcs <- choose(0, maxSources) + srcs <- listOfN(numSrcs, genFile) + srcProd <- genFileRelation(srcs) + binaryDep <- genFileRelation(srcs) + memberRef <- genRSourceDependencies(srcs) + inheritance <- genSubRSourceDependencies(memberRef) + classes <- genStringRelation(srcs) + names <- genStringRelation(srcs) + } yield Relations.make(srcProd, binaryDep, memberRef, inheritance, classes, names) + + def genAnalysis(nameHashing: Boolean): Gen[Analysis] = for { + rels <- if (nameHashing) genRelationsNameHashing else genRelations stamps <- genStamps(rels) apis <- genAPIs(rels) } yield new MAnalysis(stamps, apis, rels, SourceInfos.empty, Compilations.empty)