-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add classification/labelling of test cases
Issue 71: #71 Taken from a combination of the following PRs: - hedgehogqa/haskell-hedgehog#253 - hedgehogqa/haskell-hedgehog#262
- Loading branch information
Showing
6 changed files
with
236 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package hedgehog.core | ||
|
||
/** Whether a test is covered by a classifier, and therefore belongs to a `Class` */ | ||
sealed trait Cover { | ||
|
||
def ++(o: Cover): Cover = | ||
this match { | ||
case Cover.NoCover => | ||
o match { | ||
case Cover.NoCover => | ||
Cover.NoCover | ||
case Cover.Cover => | ||
Cover.Cover | ||
} | ||
case Cover.Cover => | ||
Cover.Cover | ||
} | ||
} | ||
|
||
object Cover { | ||
|
||
case object NoCover extends Cover | ||
case object Cover extends Cover | ||
|
||
implicit def Boolean2Cover(b: Boolean): Cover = | ||
if (b) Cover else NoCover | ||
} | ||
|
||
/** The total number of tests which are covered by a classifier. */ | ||
case class CoverCount(toInt: Int) { | ||
|
||
def +(o: CoverCount): CoverCount = | ||
CoverCount(toInt + o.toInt) | ||
|
||
def percentage(tests: SuccessCount): CoverPercentage = | ||
CoverPercentage(((toInt.toDouble / tests.value.toDouble) * 100 * 10).round / 10) | ||
} | ||
|
||
object CoverCount { | ||
|
||
def fromCover(c: Cover): CoverCount = | ||
c match { | ||
case Cover.NoCover => | ||
CoverCount(0) | ||
case Cover.Cover => | ||
CoverCount(1) | ||
} | ||
} | ||
|
||
/** The relative number of tests which are covered by a classifier. */ | ||
case class CoverPercentage(toDouble: Double) | ||
|
||
object CoverPercentage { | ||
|
||
implicit def Double2CoveragePercentage(d: Double): CoverPercentage = | ||
CoverPercentage(d) | ||
} | ||
|
||
/** The name of a classifier. */ | ||
case class LabelName(render: String) | ||
|
||
object LabelName { | ||
|
||
implicit def String2LabelName(s: String): LabelName = | ||
LabelName(s) | ||
} | ||
|
||
/** | ||
* The extent to which a test is covered by a classifier. | ||
* | ||
* _When a classifier's coverage does not exceed the required minimum, the test will be failed._ | ||
*/ | ||
case class Label[A]( | ||
name : LabelName | ||
, minimum : CoverPercentage | ||
, annotation : A | ||
) | ||
|
||
object Label { | ||
|
||
def covered(label: Label[CoverCount], tests: SuccessCount): Boolean = | ||
label.annotation.percentage(tests).toDouble >= label.minimum.toDouble | ||
} | ||
|
||
case class Coverage[A](labels: Map[LabelName, Label[A]]) | ||
|
||
object Coverage { | ||
|
||
def empty[A]: Coverage[A] = | ||
Coverage(Map.empty[LabelName, Label[A]]) | ||
|
||
def fromLogs(logs: List[Log]): Coverage[CoverCount] = | ||
fromLabels(logs.flatMap { | ||
case Log.LabelX(l) => | ||
List(l) | ||
case _ => | ||
Nil | ||
}) | ||
|
||
def fromLabels(labels: List[Label[Cover]]): Coverage[CoverCount] = { | ||
val cv = labels.map(l => Coverage(Map(l.name -> l))) | ||
.foldLeft(Coverage.empty[Cover])(union(_, _)(_ ++ _)) | ||
cv.copy(labels = cv.labels.mapValues(l => l.copy(annotation = CoverCount.fromCover(l.annotation)))) | ||
} | ||
|
||
def union[A](a: Coverage[A], b: Coverage[A])(append: (A, A) => A): Coverage[A] = | ||
Coverage(b.labels.toList.foldLeft(a.labels) { case (m, (k, v)) => | ||
m + (k -> m.get(k).map(x => x.copy(annotation = append(x.annotation, v.annotation))).getOrElse(v)) | ||
}) | ||
|
||
def uncovered(coverage: Coverage[CoverCount], tests: SuccessCount): List[Label[CoverCount]] = { | ||
val (_, un) = coverage.labels.values.partition(Label.covered(_, tests)) | ||
// FIXME should probably return the covered as well for debugging | ||
un.toList | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
example/src/main/scala/hedgehog/examples/CoverageTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package hedgehog.examples | ||
|
||
import hedgehog._ | ||
import hedgehog.runner._ | ||
|
||
object CoverageTest extends Properties { | ||
|
||
override def tests: List[Test] = | ||
List( | ||
property("collect int", testCollectInt) | ||
, property("cover booleans", testBoolean) | ||
) | ||
|
||
def testCollectInt: Property = | ||
for { | ||
_ <- Gen.int(Range.linear(1, 10)).forAll.collect | ||
} yield Result.success | ||
|
||
def testBoolean: Property = | ||
for { | ||
_ <- Gen.boolean.forAll | ||
.cover(40, "true", x => x) | ||
.cover(40, "false", x => !x) | ||
} yield Result.success | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package hedgehog | ||
|
||
import hedgehog.core._ | ||
import hedgehog.runner._ | ||
|
||
object CoverageTest extends Properties { | ||
|
||
override def tests: List[Test] = | ||
List( | ||
example("test cover passes", testCoverPass) | ||
, example("test cover fails", testCoverFail) | ||
) | ||
|
||
def testCoverPass: Result = { | ||
val g = | ||
for { | ||
_ <- Gen.boolean.forAll | ||
.cover(40, "true", x => x) | ||
.cover(40, "false", x => !x) | ||
} yield Result.success | ||
|
||
val r = Property.checkRandom(PropertyConfig.default, g) | ||
Result.all(List( | ||
r.coverage.labels.keys.toList.sortBy(_.render) ==== List(LabelName("false"), LabelName("true")) | ||
, r.status ==== Status.ok | ||
)) | ||
} | ||
|
||
def testCoverFail: Result = { | ||
val g = | ||
for { | ||
_ <- Gen.boolean.forAll | ||
.cover(60, "true", x => x) | ||
.cover(40, "false", x => !x) | ||
} yield Result.success | ||
|
||
val r = Property.checkRandom(PropertyConfig.default, g) | ||
Result.all(List( | ||
r.coverage.labels.keys.toList.sortBy(_.render) ==== List(LabelName("false"), LabelName("true")) | ||
, r.status match { | ||
case _: Failed => | ||
Result.success | ||
case _ => | ||
Result.failure | ||
} | ||
)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters