Skip to content

Commit

Permalink
Try the nuclear option
Browse files Browse the repository at this point in the history
  • Loading branch information
CodaFi committed Jan 8, 2016
1 parent 58433d6 commit c1b31bf
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 90 deletions.
11 changes: 10 additions & 1 deletion SwiftCheck/Check.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,28 @@ public struct CheckerArguments {
/// it becomes too small the samples present in the test case will lose diversity.
let maxTestCaseSize : Int

internal let silence : Bool

public init(replay : Optional<(StdGen, Int)> = nil
, maxAllowableSuccessfulTests : Int = 100
, maxAllowableDiscardedTests : Int = 500
, maxTestCaseSize : Int = 100
)
{
self = CheckerArguments(replay: replay, maxAllowableSuccessfulTests: maxAllowableSuccessfulTests, maxAllowableDiscardedTests: maxAllowableDiscardedTests, maxTestCaseSize: maxTestCaseSize, name: "")
self = CheckerArguments( replay: replay
, maxAllowableSuccessfulTests: maxAllowableSuccessfulTests
, maxAllowableDiscardedTests: maxAllowableDiscardedTests
, maxTestCaseSize: maxTestCaseSize
, name: ""
)
}

internal init(replay : Optional<(StdGen, Int)> = nil
, maxAllowableSuccessfulTests : Int = 100
, maxAllowableDiscardedTests : Int = 500
, maxTestCaseSize : Int = 100
, name : String
, silence : Bool = false
)
{

Expand All @@ -121,6 +129,7 @@ public struct CheckerArguments {
self.maxAllowableDiscardedTests = maxAllowableDiscardedTests
self.maxTestCaseSize = maxTestCaseSize
self.name = name
self.silence = silence
}

internal var name : String
Expand Down
13 changes: 13 additions & 0 deletions SwiftCheck/Property.swift
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,19 @@ private func addLabels(result : TestResult) -> TestResult -> TestResult {
}
}

private func printLabels(st : TestResult) {
if st.labels.isEmpty {
print("(.)")
} else if st.labels.count == 1, let pt = st.labels.first {
print("(\(pt.0))")
} else {
let gAllLabels = st.labels.map({ (l, _) in
return l + ", "
}).reduce("", combine: +)
print("(" + gAllLabels[gAllLabels.startIndex..<gAllLabels.endIndex.advancedBy(-2)] + ")")
}
}

private func conj(k : TestResult -> TestResult, xs : [Rose<TestResult>]) -> Rose<TestResult> {
if xs.isEmpty {
return Rose.MkRose({ k(TestResult.succeeded) }, { [] })
Expand Down
7 changes: 6 additions & 1 deletion SwiftCheck/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ public struct CheckerState {
let quantifier : Quantification

let arguments : CheckerArguments

let silence : Bool

public init( name : String
, maxAllowableSuccessfulTests : Int
, maxAllowableDiscardedTests : Int
Expand All @@ -66,7 +69,8 @@ public struct CheckerState {
, failedShrinkStepCount : Int
, shouldAbort : Bool
, quantifier : Quantification
, arguments : CheckerArguments)
, arguments : CheckerArguments
, silence : Bool)
{
self.name = name
self.maxAllowableSuccessfulTests = maxAllowableSuccessfulTests
Expand All @@ -84,5 +88,6 @@ public struct CheckerState {
self.shouldAbort = shouldAbort
self.quantifier = quantifier
self.arguments = arguments
self.silence = silence
}
}
75 changes: 41 additions & 34 deletions SwiftCheck/Test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@ internal func quickCheckWithResult(args : CheckerArguments, _ p : Testable) -> R
, failedShrinkStepCount: 0
, shouldAbort: false
, quantifier: .Universal
, arguments: args)
, arguments: args
, silence: args.silence)
let modP : Property = (p.exhaustive ? p.property.once : p.property)
return test(istate, caseGen: modP.unProperty.unGen)
}
Expand Down Expand Up @@ -492,7 +493,8 @@ internal func runATest(st : CheckerState, caseGen : (StdGen, Int) -> Prop) -> Ei
, failedShrinkStepCount: st.failedShrinkStepCount
, shouldAbort: abort
, quantifier: quantifier
, arguments: st.arguments)
, arguments: st.arguments
, silence: st.silence)
return .Right(nstate)
// Discard
case .MatchResult(.None, let expect, _, _, let labels, _, _, let abort, let quantifier):
Expand All @@ -511,16 +513,17 @@ internal func runATest(st : CheckerState, caseGen : (StdGen, Int) -> Prop) -> Ei
, failedShrinkStepCount: st.failedShrinkStepCount
, shouldAbort: abort
, quantifier: quantifier
, arguments: st.arguments)
, arguments: st.arguments
, silence: st.silence)
return .Right(nstate)
// Fail
case .MatchResult(.Some(false), let expect, _, _, _, _, _, let abort, let quantifier):
if quantifier == .Existential {
// print("")
} else if !expect {
print("+++ OK, failed as expected. ", terminator: "")
printCond(st.silence, "+++ OK, failed as expected. ", terminator: "")
} else {
print("*** Failed! ", terminator: "")
printCond(st.silence, "*** Failed! ", terminator: "")
}

// Failure of an existential is not necessarily failure of the whole
Expand All @@ -541,7 +544,8 @@ internal func runATest(st : CheckerState, caseGen : (StdGen, Int) -> Prop) -> Ei
, failedShrinkStepCount: st.failedShrinkStepCount
, shouldAbort: abort
, quantifier: quantifier
, arguments: st.arguments)
, arguments: st.arguments
, silence: st.silence)

/// However, some existentials outlive their usefulness
if nstate.discardedTestCount >= nstate.maxAllowableDiscardedTests {
Expand Down Expand Up @@ -588,7 +592,8 @@ internal func runATest(st : CheckerState, caseGen : (StdGen, Int) -> Prop) -> Ei
, failedShrinkStepCount: st.failedShrinkStepCount
, shouldAbort: abort
, quantifier: quantifier
, arguments: st.arguments)
, arguments: st.arguments
, silence: st.silence)
return .Left((stat, nstate))
}
default:
Expand All @@ -600,20 +605,20 @@ internal func runATest(st : CheckerState, caseGen : (StdGen, Int) -> Prop) -> Ei
internal func doneTesting(st : CheckerState) -> Result {
if !st.hasFulfilledExpectedFailure {
if insufficientCoverage(st) {
print("+++ OK, failed as expected. ")
print("*** Insufficient coverage after " + "\(st.successfulTestCount)" + pluralize(" test", i: st.successfulTestCount))
printCond(st.silence, "+++ OK, failed as expected. ")
printCond(st.silence, "*** Insufficient coverage after " + "\(st.successfulTestCount)" + pluralize(" test", i: st.successfulTestCount))
printDistributionGraph(st)
return .Success(numTests: st.successfulTestCount, labels: summary(st), output: "")
}

printDistributionGraph(st)
return .NoExpectedFailure(numTests: st.successfulTestCount, labels: summary(st), output: "")
} else if insufficientCoverage(st) {
print("*** Insufficient coverage after " + "\(st.successfulTestCount)" + pluralize(" test", i: st.successfulTestCount))
printCond(st.silence, "*** Insufficient coverage after " + "\(st.successfulTestCount)" + pluralize(" test", i: st.successfulTestCount))
printDistributionGraph(st)
return .InsufficientCoverage(numTests: st.successfulTestCount, labels: summary(st), output: "")
} else {
print("*** Passed " + "\(st.successfulTestCount)" + pluralize(" test", i: st.successfulTestCount))
printCond(st.silence, "*** Passed " + "\(st.successfulTestCount)" + pluralize(" test", i: st.successfulTestCount))
printDistributionGraph(st)
return .Success(numTests: st.successfulTestCount, labels: summary(st), output: "")
}
Expand Down Expand Up @@ -703,16 +708,17 @@ internal func findMinimalFailingTestCase(st : CheckerState, res : TestResult, ts
, failedShrinkStepCount: failedShrinkStepCount
, shouldAbort: st.shouldAbort
, quantifier: st.quantifier
, arguments: st.arguments)
, arguments: st.arguments
, silence: st.silence)
return reportMinimumCaseFound(state, res: lastResult)
}

internal func reportMinimumCaseFound(st : CheckerState, res : TestResult) -> (Int, Int, Int) {
let testMsg = " (after \(st.successfulTestCount.successor()) test"
let shrinkMsg = st.successfulShrinkCount > 1 ? (" and \(st.successfulShrinkCount) shrink") : ""

print("Proposition: " + st.name)
print(res.reason + pluralize(testMsg, i: st.successfulTestCount.successor()) + (st.successfulShrinkCount > 1 ? pluralize(shrinkMsg, i: st.successfulShrinkCount) : "") + "):")
printCond(st.silence, "Proposition: " + st.name)
printCond(st.silence, res.reason + pluralize(testMsg, i: st.successfulTestCount.successor()) + (st.successfulShrinkCount > 1 ? pluralize(shrinkMsg, i: st.successfulShrinkCount) : "") + "):")
dispatchAfterFinalFailureCallbacks(st, res: res)
return (st.successfulShrinkCount, st.failedShrinkStepCount - st.failedShrinkStepDistance, st.failedShrinkStepDistance)
}
Expand All @@ -722,9 +728,9 @@ internal func reportExistentialFailure(st : CheckerState, res : Result) -> Resul
case let .ExistentialFailure(_, _, _, reason, _, _, lastTest):
let testMsg = " (after \(st.discardedTestCount) test"

print("*** Failed! ", terminator: "")
print("Proposition: " + st.name)
print(reason + pluralize(testMsg, i: st.discardedTestCount) + "):")
printCond(st.silence, "*** Failed! ", terminator: "")
printCond(st.silence, "Proposition: " + st.name)
printCond(st.silence, reason + pluralize(testMsg, i: st.discardedTestCount) + "):")
dispatchAfterFinalFailureCallbacks(st, res: lastTest)
return res
default:
Expand All @@ -733,6 +739,10 @@ internal func reportExistentialFailure(st : CheckerState, res : Result) -> Resul
}

internal func dispatchAfterTestCallbacks(st : CheckerState, res : TestResult) {
guard !st.silence else {
return
}

res.callbacks.forEach { c in
switch c {
case let .AfterTest(_, f):
Expand All @@ -744,6 +754,10 @@ internal func dispatchAfterTestCallbacks(st : CheckerState, res : TestResult) {
}

internal func dispatchAfterFinalFailureCallbacks(st : CheckerState, res : TestResult) {
guard !st.silence else {
return
}

res.callbacks.forEach { c in
switch c {
case let .AfterFinalFailure(_, f):
Expand All @@ -765,19 +779,6 @@ private func labelPercentage(l : String, st : CheckerState) -> Int {
return (100 * occur.count) / st.maxAllowableSuccessfulTests
}

internal func printLabels(st : TestResult) {
if st.labels.isEmpty {
print("(.)")
} else if st.labels.count == 1, let pt = st.labels.first {
print("(\(pt.0))")
} else {
let gAllLabels = st.labels.map({ (l, _) in
return l + ", "
}).reduce("", combine: +)
print("(" + gAllLabels[gAllLabels.startIndex..<gAllLabels.endIndex.advancedBy(-2)] + ")")
}
}

private func showP(n : Int) -> String {
return (n < 10 ? " " : "") + "\(n)" + "%"
}
Expand All @@ -800,13 +801,13 @@ private func printDistributionGraph(st : CheckerState) {

let all = covers + allLabels
if all.isEmpty {
print(".")
printCond(st.silence, ".")
} else if all.count == 1, let pt = all.first {
print("(\(pt))")
printCond(st.silence, "(\(pt))")
} else {
print(":")
printCond(st.silence, ":")
all.forEach { pt in
print(pt)
printCond(st.silence, pt)
}
}
}
Expand All @@ -829,6 +830,12 @@ private func insufficientCoverage(st : CheckerState) -> Bool {
.reduce(false, combine: { $0 || $1 })
}

private func printCond(cond : Bool, _ str : String, terminator : String = "\n") {
if !cond {
print(str, terminator: terminator)
}
}

extension Array {
private func groupBy(p : (Element , Element) -> Bool) -> [[Element]] {
func span(list : [Element], p : (Element -> Bool)) -> ([Element], [Element]) {
Expand Down
78 changes: 24 additions & 54 deletions SwiftCheckTests/PropertySpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,35 @@

@testable import SwiftCheck

// From http://stackoverflow.com/a/4832902/945847
func shutup<T>(f : () -> T) -> T {
var bak : Int32 = 0
var bake : Int32 = 0
var new : Int32 = 0
var newe : Int32 = 0
fflush(stdout)
fflush(stderr)
bak = dup(1)
bake = dup(2)
new = open("/dev/null", O_WRONLY)
dup2(new, 1)
close(new)
newe = open("/dev/null", O_WRONLY)
dup2(newe, 2)
close(newe)
let res = f()
fflush(stdout)
fflush(stderr)
dup2(bak, 1)
dup2(bake, 2)
close(bak)
close(bake)
return res
}

func ==(l : Property, r : Property) -> Bool {
return shutup {
let res1 = quickCheckWithResult(CheckerArguments(name: ""), l)
let res2 = quickCheckWithResult(CheckerArguments(name: ""), r)

switch (res1, res2) {
case (.Success(_, _, _), .Success(_, _, _)):
return true
case (.GaveUp(_, _, _), .GaveUp(_, _, _)):
return true
case (.Failure(_, _, _, _, _, _, _), .Failure(_, _, _, _, _, _, _)):
return true
case (.ExistentialFailure(_, _, _, _, _, _, _), .ExistentialFailure(_, _, _, _, _, _, _)):
return true
case (.NoExpectedFailure(_, _, _), .NoExpectedFailure(_, _, _)):
return true
case (.InsufficientCoverage(_, _, _), .InsufficientCoverage(_, _, _)):
return true
default:
return false
}
let res1 = quickCheckWithResult(CheckerArguments(name: "", silence: true), l)
let res2 = quickCheckWithResult(CheckerArguments(name: "", silence: true), r)

switch (res1, res2) {
case (.Success(_, _, _), .Success(_, _, _)):
return true
case (.GaveUp(_, _, _), .GaveUp(_, _, _)):
return true
case (.Failure(_, _, _, _, _, _, _), .Failure(_, _, _, _, _, _, _)):
return true
case (.ExistentialFailure(_, _, _, _, _, _, _), .ExistentialFailure(_, _, _, _, _, _, _)):
return true
case (.NoExpectedFailure(_, _, _), .NoExpectedFailure(_, _, _)):
return true
case (.InsufficientCoverage(_, _, _), .InsufficientCoverage(_, _, _)):
return true
default:
return false
}
}

func ==(l : Property, r : Bool) -> Bool {
return shutup {
let res1 = quickCheckWithResult(CheckerArguments(name: ""), l)
switch res1 {
case .Success(_, _, _):
return r == true
default:
return r == false
}
let res1 = quickCheckWithResult(CheckerArguments(name: "", silence: true), l)
switch res1 {
case .Success(_, _, _):
return r == true
default:
return r == false
}
}

Expand Down

0 comments on commit c1b31bf

Please sign in to comment.