Skip to content

Commit 7b19c5d

Browse files
committed
Document conj and disj
1 parent 3d97f5a commit 7b19c5d

File tree

2 files changed

+29
-7
lines changed

2 files changed

+29
-7
lines changed

Sources/SwiftCheck/Property.swift

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,18 @@ private func printLabels(_ st : TestResult) {
671671
}
672672
}
673673

674+
/// Takes a sequence of trees of test results from several properties into
675+
/// a single labelled rose tree for the conjunction.
676+
///
677+
/// This function is written in continuation passsing style to facilitate the
678+
/// construction of a lazy rose tree that preserves the labels and callbacks
679+
/// associated with the sub-properties.
680+
///
681+
/// The general idea is that the properties are tested from first to last. If
682+
/// any of them fails, the conjunct need not evaluate further and returns that
683+
/// first failing result. Else, each passing sub-property continues on to the
684+
/// next sub-property and decorates the overall result with its callbacks and
685+
/// labels. Discards act to skip that sub-property, as expected.
674686
private func conj(_ k : @escaping (TestResult) -> TestResult, xs : [Rose<TestResult>]) -> Rose<TestResult> {
675687
guard let p = xs.first else {
676688
return Rose.mkRose({ k(TestResult.succeeded) }, { [] })
@@ -679,16 +691,20 @@ private func conj(_ k : @escaping (TestResult) -> TestResult, xs : [Rose<TestRes
679691
let rose = p.reduce
680692
switch rose {
681693
case .mkRose(let result, _):
682-
if !result().expect {
694+
guard result().expect else {
683695
return Rose.pure(TestResult.failed("expectFailure may not occur inside a conjunction"))
684696
}
685697

686698
switch result().ok {
687699
case .some(true):
700+
// Passed - Adjoin the labels and test the next property.
688701
return conj(comp(addLabels(result()), comp(addCallbacks(result()), k)), xs: [Rose<TestResult>](xs[1..<xs.endIndex]))
689702
case .some(false):
703+
// Failed - Return the first failure and don't continue
704+
// evaluating the other conjuncts.
690705
return rose
691706
case .none:
707+
// Discard - Try the next property.
692708
let rose2 = conj(comp(addCallbacks(result()), k), xs: [Rose<TestResult>](xs[1..<xs.endIndex])).reduce
693709
switch rose2 {
694710
case .mkRose(let result2, _):
@@ -711,23 +727,28 @@ private func conj(_ k : @escaping (TestResult) -> TestResult, xs : [Rose<TestRes
711727
}))
712728
}
713729

730+
/// Computes the disjunction of two test results.
714731
private func disj(_ p : Rose<TestResult>, q : Rose<TestResult>) -> Rose<TestResult> {
715732
return p.flatMap { result1 in
716-
if !result1.expect {
733+
guard result1.expect else {
717734
return Rose<TestResult>.pure(TestResult.failed("expectFailure may not occur inside a disjunction"))
718735
}
719736
switch result1.ok {
720737
case .some(true):
738+
// Passed - Don't evaluate the other tree.
721739
return Rose<TestResult>.pure(result1)
722740
case .some(false):
723-
return q.flatMap { (result2 : TestResult) in
724-
if !result2.expect {
741+
// Failed - Try the other tree.
742+
return q.flatMap { result2 in
743+
guard result2.expect else {
725744
return Rose<TestResult>.pure(TestResult.failed("expectFailure may not occur inside a disjunction"))
726745
}
727746
switch result2.ok {
728747
case .some(true):
729748
return Rose<TestResult>.pure(result2)
730749
case .some(false):
750+
// Failed - Combine the exceptions and callbacks from each
751+
// sub-property.
731752
let callbacks : [Callback] = [.afterFinalFailure(kind: .counterexample, { (_, _) in return print("") })]
732753
return Rose<TestResult>.pure(TestResult(
733754
ok: .some(false),
@@ -745,8 +766,9 @@ private func disj(_ p : Rose<TestResult>, q : Rose<TestResult>) -> Rose<TestResu
745766
}
746767
}
747768
case .none:
748-
return q.flatMap { (result2 : TestResult) in
749-
if !result2.expect {
769+
// Discarded - Try the other property.
770+
return q.flatMap { result2 in
771+
guard result2.expect else {
750772
return Rose<TestResult>.pure(TestResult.failed("expectFailure may not occur inside a disjunction"))
751773
}
752774
switch result2.ok {

Sources/SwiftCheck/Test.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,7 @@ private func runATest(_ st : CheckerState, caseGen : (StdGen, Int) -> Prop) -> E
768768
// Attempt a shrink.
769769
let (numShrinks, _, _) = findMinimalFailingTestCase(st, res: res, ts: ts())
770770

771-
if !expect {
771+
guard expect else {
772772
let s = QuickCheckResult.success(numTests: (st.successfulTestCount + 1), labels: summary(st), output: "+++ OK, failed as expected. ")
773773
return .left((s, st))
774774
}

0 commit comments

Comments
 (0)