Skip to content

Commit

Permalink
Merge pull request #15 from CodaFi/assertiveness-training
Browse files Browse the repository at this point in the history
Assertive Properties
  • Loading branch information
CodaFi committed May 17, 2015
2 parents 12476b3 + 7de1f0f commit a2b6d1e
Show file tree
Hide file tree
Showing 13 changed files with 211 additions and 83 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ DerivedData
.idea/
*.hmap
*.xcuserstate
Carthage/
22 changes: 18 additions & 4 deletions SwiftCheck-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
84572C291A6DBAB600241F68 /* Testable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84572C281A6DBAB600241F68 /* Testable.swift */; };
84572C311A6DC21100241F68 /* Check.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84572C301A6DC21100241F68 /* Check.swift */; };
84572C381A6DC25000241F68 /* DiscardSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84572C321A6DC23100241F68 /* DiscardSpec.swift */; };
84572C391A6DC25200241F68 /* PrimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84572C331A6DC23100241F68 /* PrimeSpec.swift */; };
84572C3A1A6DC25500241F68 /* SimpleSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84572C341A6DC23100241F68 /* SimpleSpec.swift */; };
8480AB401A7B2AA100C6162D /* ModifierSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8480AB3E1A7B2A9A00C6162D /* ModifierSpec.swift */; };
84F2C4F41A7AD43900316E5F /* Modifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2C4F31A7AD43900316E5F /* Modifiers.swift */; };
Expand Down Expand Up @@ -57,7 +56,6 @@
84572C281A6DBAB600241F68 /* Testable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Testable.swift; path = SwiftCheck/Testable.swift; sourceTree = SOURCE_ROOT; };
84572C301A6DC21100241F68 /* Check.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Check.swift; path = SwiftCheck/Check.swift; sourceTree = SOURCE_ROOT; };
84572C321A6DC23100241F68 /* DiscardSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DiscardSpec.swift; path = SwiftCheckTests/DiscardSpec.swift; sourceTree = SOURCE_ROOT; };
84572C331A6DC23100241F68 /* PrimeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PrimeSpec.swift; path = SwiftCheckTests/PrimeSpec.swift; sourceTree = SOURCE_ROOT; };
84572C341A6DC23100241F68 /* SimpleSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SimpleSpec.swift; path = SwiftCheckTests/SimpleSpec.swift; sourceTree = SOURCE_ROOT; };
8480AB3E1A7B2A9A00C6162D /* ModifierSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ModifierSpec.swift; path = SwiftCheckTests/ModifierSpec.swift; sourceTree = SOURCE_ROOT; };
84F2C4F31A7AD43900316E5F /* Modifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Modifiers.swift; path = SwiftCheck/Modifiers.swift; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -134,7 +132,6 @@
isa = PBXGroup;
children = (
84572C321A6DC23100241F68 /* DiscardSpec.swift */,
84572C331A6DC23100241F68 /* PrimeSpec.swift */,
8480AB3E1A7B2A9A00C6162D /* ModifierSpec.swift */,
84572C341A6DC23100241F68 /* SimpleSpec.swift */,
841FAE4119FB1BA800AF4EA2 /* Supporting Files */,
Expand Down Expand Up @@ -277,7 +274,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
84572C391A6DC25200241F68 /* PrimeSpec.swift in Sources */,
8480AB401A7B2AA100C6162D /* ModifierSpec.swift in Sources */,
84572C3A1A6DC25500241F68 /* SimpleSpec.swift in Sources */,
84572C381A6DC25000241F68 /* DiscardSpec.swift in Sources */,
Expand Down Expand Up @@ -387,13 +383,22 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(DEVELOPER_FRAMEWORKS_DIR)",
"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks",
);
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = SwiftCheck/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = (
"-framework",
XCTest,
);
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
Expand All @@ -406,9 +411,18 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(DEVELOPER_FRAMEWORKS_DIR)",
"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks",
);
INFOPLIST_FILE = SwiftCheck/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = (
"-framework",
XCTest,
);
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
Expand Down
20 changes: 16 additions & 4 deletions SwiftCheck.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
844FCCC9198EFF9B00EB242A /* SimpleSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844FCCC8198EFF9B00EB242A /* SimpleSpec.swift */; };
844FCCCF198F24CF00EB242A /* Combinators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844FCCCE198F24CF00EB242A /* Combinators.swift */; };
8450D24A1AF8003800095EF6 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8450D2491AF8003700095EF6 /* Operators.swift */; };
84572C1B1A6DB9D800241F68 /* PrimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84572C1A1A6DB9D800241F68 /* PrimeSpec.swift */; };
84572C211A6DBA1C00241F68 /* DiscardSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84572C201A6DBA1C00241F68 /* DiscardSpec.swift */; };
84572C251A6DBAA800241F68 /* Check.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84572C241A6DBAA800241F68 /* Check.swift */; };
84572C2B1A6DBABA00241F68 /* Testable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84572C2A1A6DBABA00241F68 /* Testable.swift */; };
Expand Down Expand Up @@ -56,7 +55,6 @@
844FCCC8198EFF9B00EB242A /* SimpleSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleSpec.swift; sourceTree = "<group>"; };
844FCCCE198F24CF00EB242A /* Combinators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Combinators.swift; sourceTree = "<group>"; };
8450D2491AF8003700095EF6 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; };
84572C1A1A6DB9D800241F68 /* PrimeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimeSpec.swift; sourceTree = "<group>"; };
84572C201A6DBA1C00241F68 /* DiscardSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscardSpec.swift; sourceTree = "<group>"; };
84572C241A6DBAA800241F68 /* Check.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Check.swift; sourceTree = "<group>"; };
84572C2A1A6DBABA00241F68 /* Testable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Testable.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -136,7 +134,6 @@
isa = PBXGroup;
children = (
844FCCC8198EFF9B00EB242A /* SimpleSpec.swift */,
84572C1A1A6DB9D800241F68 /* PrimeSpec.swift */,
84572C201A6DBA1C00241F68 /* DiscardSpec.swift */,
8480AB2C1A7B0A9700C6162D /* ModifierSpec.swift */,
848076671AF5D6A100CBE3EF /* BooleanIdentitySpec.swift */,
Expand Down Expand Up @@ -281,7 +278,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
84572C1B1A6DB9D800241F68 /* PrimeSpec.swift in Sources */,
844FCCC9198EFF9B00EB242A /* SimpleSpec.swift in Sources */,
848076681AF5D6A100CBE3EF /* BooleanIdentitySpec.swift in Sources */,
84572C211A6DBA1C00241F68 /* DiscardSpec.swift in Sources */,
Expand Down Expand Up @@ -390,10 +386,18 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(DEVELOPER_FRAMEWORKS_DIR)",
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
);
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = SwiftCheck/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = (
"-framework",
XCTest,
);
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
Expand All @@ -409,10 +413,18 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(DEVELOPER_FRAMEWORKS_DIR)",
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
);
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = SwiftCheck/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = (
"-framework",
XCTest,
);
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
Expand Down
3 changes: 3 additions & 0 deletions SwiftCheck.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 28 additions & 4 deletions SwiftCheck/Check.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,40 @@
// Copyright (c) 2015 Robert Widmann. All rights reserved.
//

/// The interface to the SwiftCheck testing mechanism. To test a proposition one subscripts into
/// this variable with a description of the property being tested like so:
import XCTest

/// The main interface for the SwiftCheck testing mechanism. To test a program property one
/// subscripts into this variable with a description of the property being tested like so:
///
/// property["Integer Equality is Reflexive"] = forAll { (i : Int8) in
/// return i == i
/// }
///
public var property : QuickCheck = QuickCheck()
/// SwiftCheck will report all failures through the XCTest mechanism like a normal testing assert,
/// but with minimal failing case reported as well.
public var property : AssertiveQuickCheck = AssertiveQuickCheck()

public struct AssertiveQuickCheck {
public subscript(s : String) -> Testable {
get {
fatalError("Assertive proposition '\(s)' has an undefined test case")
}
set(test) {
switch quickCheckWithResult(stdArgs(name: s), test) {
case let .Failure(numTests, numShrinks, usedSeed, usedSize, reason, labels, output):
XCTFail(reason)
default:
return
}
}
}
}

/// The interface for properties to be run through SwiftCheck without an XCTest assert. The
/// property will still generated console output during a test.
public var reportProperty : ReportiveQuickCheck = ReportiveQuickCheck()

public struct QuickCheck {
public struct ReportiveQuickCheck {
public subscript(s : String) -> Testable {
get {
fatalError("Proposition '\(s)' has an undefined test case")
Expand Down
122 changes: 122 additions & 0 deletions SwiftCheck/SwiftCheck.playground/Contents.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//: ## SwiftCheck - noun: QuickCheck for Swift.
//:
//: SwiftCheck is a testing library that automatically generates random data for testing of program
//: properties.

import SwiftCheck

//: For example, if we wanted to test the property that every Integer is equal to itself, we would
//: express it as such and SwiftCheck will handle the rest:
property["Integer equality obeys reflexivity"] = forAll { (i : Int) in
return i == i
}

//: ## Shrinking
//: What makes QuickCheck unique is the notion of shrinking test cases. When fuzz testing with
//: arbitrary data, rather than simply halt on a failing test, SwiftCheck will begin whittling the
//: data that causes the test to fail down to a minimal counterexample.
//:
//: For example, the following function uses the Sieve of Eratosthenes to generate a list of primes
//: less than some n:

// The Sieve of Eratosthenes:
//
// To find all the prime numbers less than or equal to a given integer n:
// - let l = [2...n]
// - let p = 2
// - for i in [(2 * p) through n by p] {
// mark l[i]
// }
// - Remaining indices of unmarked numbers are primes
func sieve(n : Int) -> [Int] {
if n <= 1 {
return [Int]()
}

var marked : [Bool] = (0...n).map({ _ in false })
marked[0] = true
marked[1] = true

for p in 2..<n {
for i in stride(from: 2 * p, to: n, by: p) {
marked[i] = true
}
}

var primes : [Int] = []
for (t, i) in Zip2(marked, 0...n) {
if !t {
primes.append(i)
}
}
return primes
}

// Trial Division
//
// Short and sweet check if a number is prime by enumerating from 2...⌈√(x)⌉ and checking
// for a nonzero modulus.
func isPrime(n : Int) -> Bool {
if n == 0 || n == 1 {
return false
} else if n == 2 {
return true
}

let max = Int(ceil(sqrt(Double(n))))
for i in 2...max {
if n % i == 0 {
return false
}
}
return true
}

//: We would like to test whether our sieve works properly, so we run it through SwiftCheck with the
//: following property:

property["All Prime"] = forAll { (n : Int) in
return sieve(n).filter(isPrime) == sieve(n)
}

//: Which produces the following in our testing log:
//:
//: > Test Case '-[SwiftCheckTests.PrimeSpec testAll]' started.
//: > *** Failed! Falsifiable (after 10 tests):
//: > 4
//:
//: Indicating that our sieve has failed on the input number 4. A quick look back at the comments
//: describing the sieve reveals the mistake immediately:
//:
//: > - for i in stride(from: 2 * p, to: n, by: p) {
//: > + for i in stride(from: 2 * p, through: n, by: p) {
//:
//: Running SwiftCheck again reports a successful sieve of all 100 random cases:

func sieveProperly(n : Int) -> [Int] {
if n <= 1 {
return [Int]()
}

var marked : [Bool] = (0...n).map({ _ in false })
marked[0] = true
marked[1] = true

for p in 2..<n {
for i in stride(from: 2 * p, through: n, by: p) {
marked[i] = true
}
}

var primes : [Int] = []
for (t, i) in Zip2(marked, 0...n) {
if !t {
primes.append(i)
}
}
return primes
}

property["All Prime"] = forAll { (n : Int) in
return sieveProperly(n).filter(isPrime) == sieveProperly(n)
}
3 changes: 3 additions & 0 deletions SwiftCheck/SwiftCheck.playground/Sources/SupportCode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//
// This file (and all other Swift source files in the Sources directory of this playground) will be precompiled into a framework which is automatically made available to SwiftCheck.playground.
//
4 changes: 4 additions & 0 deletions SwiftCheck/SwiftCheck.playground/contents.xcplayground
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='osx' display-mode='rendered'>
<timeline fileName='timeline.xctimeline'/>
</playground>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions SwiftCheck/SwiftCheck.playground/timeline.xctimeline
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
2 changes: 2 additions & 0 deletions SwiftCheck/Testable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public struct Prop : Testable {
public struct Discard : Testable {
public var exhaustive : Bool { return true }

public init() { }

public func property() -> Property {
return rejected().property()
}
Expand Down
2 changes: 1 addition & 1 deletion SwiftCheckTests/ModifierSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ModifierSpec : XCTestCase {
return x.getNonNegative >= 0
}

property["ArrayOf modifiers nest"] = forAll { (xxxs : ArrayOf<ArrayOf<ArrayOf<Int>>>) in
property["ArrayOf modifiers nest"] = forAll { (xxxs : ArrayOf<ArrayOf<Int8>>) in
return true
}
}
Expand Down
Loading

0 comments on commit a2b6d1e

Please sign in to comment.