-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of github.com:justeattakeaway/Genything
- Loading branch information
Showing
17 changed files
with
283 additions
and
60 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
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,45 @@ | ||
import Foundation | ||
|
||
/// Storage required in order to index all exhaustive values before moving on to the `then` generator | ||
/// Bounded by the lifetime of the `Context` | ||
/// | ||
/// - SeeAlso: `Gen.exhaust(values:then:)` | ||
private class ExhaustiveStorage { | ||
/// Map of generator identifier to Index | ||
private var data: [UUID:Int] = [:] | ||
|
||
/// Returns: The current exhaustive index for the provided generator's ID | ||
func index(_ id: UUID) -> Int { | ||
data[id] ?? 0 | ||
} | ||
|
||
/// Increments the index for the provided generator's ID | ||
func incrementIndex(_ id: UUID) { | ||
data[id] = index(id) + 1 | ||
} | ||
} | ||
|
||
public extension Gen { | ||
/// Returns: A generator which produces (in-order) all of the values from the provided list, then randomly from the provided generator | ||
/// | ||
/// - Parameters: | ||
/// - values: A list of values we will always draw from first | ||
/// - then: A generator to produce values after we have exhausted the `values` list | ||
/// | ||
/// - Returns: The generator | ||
static func exhaust(_ values: [T], then: Gen<T>) -> Gen<T> { | ||
let id = UUID() | ||
return Gen<T> { ctx in | ||
let store = ctx.services.service { | ||
ExhaustiveStorage() | ||
} | ||
let i = store.index(id) | ||
if i < values.count { | ||
store.incrementIndex(id) | ||
return values[i] | ||
} else { | ||
return then.generate(context: ctx) | ||
} | ||
} | ||
} | ||
} |
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
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,28 @@ | ||
import Foundation | ||
|
||
// MARK: Combine | ||
|
||
public extension Gen { | ||
/// Returns: A generator which randomly selects values from either the receiver or the `other` generator | ||
/// | ||
/// - Parameters: | ||
/// - other: Another generator which may get selected to produce values | ||
/// - otherProbability: The probability that the the right generator will be selected from | ||
/// | ||
/// - Returns: The generator | ||
func or(_ other: Gen<T>, otherProbability: Double = 0.5) -> Gen<T> { | ||
let probabilityRange = 0.0...1.0 | ||
assert( | ||
probabilityRange.contains(otherProbability), | ||
"A probability between 0.0 and 1.0 must be specified. Found: \(otherProbability)" | ||
) | ||
|
||
return Gen<Double>.from(probabilityRange).flatMap { | ||
if $0 <= otherProbability { | ||
return other | ||
} else { | ||
return self | ||
} | ||
} | ||
} | ||
} |
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
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,23 @@ | ||
import Foundation | ||
|
||
/// A service locator and cache which allows a context to hold onto arbitrary services for it's lifetime | ||
final class ContextServiceLocator { | ||
private var store = [Int : Any]() | ||
|
||
/// Locates a service of type `Service`, creating it via `factory` if it does not exist already | ||
/// | ||
/// - Parameter factory: A factory capable of creating the service | ||
/// | ||
/// - Returns: The service | ||
func service<Service>(_ factory: @escaping () -> Service) -> Service { | ||
let key = Int(bitPattern: ObjectIdentifier(Service.self)) | ||
|
||
if let service = store[key] as? Service { | ||
return service | ||
} else { | ||
let service = factory() | ||
store[key] = service | ||
return service | ||
} | ||
} | ||
} |
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
12 changes: 12 additions & 0 deletions
12
Tests/GenythingTests/assertions/assertAcceptableDelta.swift
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,12 @@ | ||
import Foundation | ||
import XCTest | ||
|
||
func assertAcceptableDelta(total: Int, actual: Int, acceptablePercentDelta: Double) { | ||
let expected = total / 2 | ||
let delta = abs(expected - actual) | ||
|
||
XCTAssert( | ||
/// Ensure that we generate nearly 50% of each value | ||
0...Int(Double(actual) * acceptablePercentDelta) ~= delta, | ||
"Expected to generate true with ~0.5 probability (± \(acceptablePercentDelta), received ± \(Double(delta) / Double(total))") | ||
} |
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,45 @@ | ||
import XCTest | ||
import Genything | ||
import GenythingTest | ||
|
||
final internal class GenExhaustTests: XCTestCase { | ||
func test_the_exhaustion_of_values() { | ||
let gen = Gen.exhaust([0, 1, 2], then: .from(3...10)) | ||
|
||
gen.assertForAll { 0...10 ~= $0 } | ||
|
||
let count = 100 | ||
let values = gen.take(count: count) | ||
|
||
XCTAssertEqual(0, values[0]) | ||
XCTAssertEqual(1, values[1]) | ||
XCTAssertEqual(2, values[2]) | ||
|
||
for i in 3..<count { | ||
XCTAssertTrue(3...10 ~= values[i]) | ||
} | ||
} | ||
|
||
func test_n_exhaustives_1_context() { | ||
let other = Gen.from(3...10) | ||
let gen1 = Gen.exhaust([0, 1, 2], then: other) | ||
let gen2 = Gen.exhaust([0, 1, 2], then: other) | ||
|
||
let count = 100 | ||
let values = gen1.zip(with: gen2).take(count: count) | ||
|
||
XCTAssertEqual(0, values[0].0) | ||
XCTAssertEqual(0, values[0].1) | ||
|
||
XCTAssertEqual(1, values[1].0) | ||
XCTAssertEqual(1, values[1].1) | ||
|
||
XCTAssertEqual(2, values[2].0) | ||
XCTAssertEqual(2, values[2].1) | ||
|
||
for i in 3..<count { | ||
XCTAssertTrue(3...10 ~= values[i].0) | ||
XCTAssertTrue(3...10 ~= values[i].1) | ||
} | ||
} | ||
} |
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
Oops, something went wrong.