Skip to content

Commit

Permalink
Contain Zalgo
Browse files Browse the repository at this point in the history
  • Loading branch information
timvermeulen committed Nov 10, 2018
1 parent f487210 commit a67585b
Show file tree
Hide file tree
Showing 17 changed files with 110 additions and 72 deletions.
14 changes: 4 additions & 10 deletions Promise.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -423,15 +423,12 @@
isa = XCBuildConfiguration;
buildSettings = {
ENABLE_TESTABILITY = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
);
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Promise.xcodeproj/Promise_Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.4;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
MACOSX_DEPLOYMENT_TARGET = 10.9;
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
Expand All @@ -451,15 +448,12 @@
isa = XCBuildConfiguration;
buildSettings = {
ENABLE_TESTABILITY = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
);
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Promise.xcodeproj/Promise_Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.4;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
MACOSX_DEPLOYMENT_TARGET = 10.9;
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
Expand Down
27 changes: 12 additions & 15 deletions Sources/Promise/BasicFuture/BasicFuture.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
public final class BasicFuture<Value> {
private let state: Atomic<State>
private let context: ExecutionContext?
private let context: ExecutionContext

private init(context: ExecutionContext?) {
private init(context: @escaping ExecutionContext) {
self.state = Atomic(.pending(callbacks: []))
self.context = context
}
Expand All @@ -14,18 +14,10 @@ private extension BasicFuture {
case fulfilled(with: Value)
}

convenience init(context: ExecutionContext?, _ process: (BasicPromise<Value>) -> Void) {
convenience init(context: @escaping ExecutionContext, _ process: (BasicPromise<Value>) -> Void) {
self.init(context: context)
process(BasicPromise(future: self))
}

func perform(_ block: @escaping () -> Void) {
if let context = context {
context { block() }
} else {
block()
}
}
}

internal extension BasicFuture {
Expand All @@ -38,14 +30,19 @@ internal extension BasicFuture {
}

callbacks?.forEach { callback in
perform { callback(value) }
context { callback(value) }
}
}

var testableValue: Value? {
guard case .fulfilled(let value) = state.value else { return nil }
return value
}
}

public extension BasicFuture {
static var pending: BasicFuture {
return BasicFuture(context: nil)
return BasicFuture(context: defaultExecutionContext)
}

static func make() -> (future: BasicFuture, promise: BasicPromise<Value>) {
Expand All @@ -54,7 +51,7 @@ public extension BasicFuture {
}

convenience init(_ process: (BasicPromise<Value>) -> Void) {
self.init(context: nil, process)
self.init(context: defaultExecutionContext, process)
}

@discardableResult
Expand All @@ -73,7 +70,7 @@ public extension BasicFuture {
}

if let value = value {
perform { callback(value) }
context { callback(value) }
}

return self
Expand Down
4 changes: 2 additions & 2 deletions Sources/Promise/Future/Future+Foundation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public extension Future {
}

func await() throws -> Value {
return try map(Result.success)
.mapError(Result.failure)
return try map(Result.value)
.mapError(Result.error)
.await()
.unwrap()
}
Expand Down
12 changes: 9 additions & 3 deletions Sources/Promise/Future/Future.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ private extension Future {
}
}

internal extension Future {
var testableResult: Result<Value>? {
return result.testableValue
}
}

public extension Future {
static var pending: Future {
return Future(result: .pending)
Expand All @@ -33,7 +39,7 @@ public extension Future {
}

convenience init(_ future: BasicFuture<Value>) {
self.init(result: future.map(Result.success))
self.init(result: future.map(Result.value))
}

convenience init(_ process: (Promise<Value>) throws -> Void) {
Expand All @@ -47,7 +53,7 @@ public extension Future {
@discardableResult
func then(_ handler: @escaping (Value) -> Void) -> Future {
result.then { result in
if case .success(let value) = result {
if case .value(let value) = result {
handler(value)
}
}
Expand All @@ -58,7 +64,7 @@ public extension Future {
@discardableResult
func `catch`(_ handler: @escaping (Error) -> Void) -> Future {
result.then { result in
if case .failure(let error) = result {
if case .error(let error) = result {
handler(error)
}
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Promise/Future/Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ public final class Promise<Value> {

public extension Promise {
func fulfill(with value: Value) {
result.fulfill(with: .success(value))
result.fulfill(with: .value(value))
}

func reject(with error: Error) {
result.fulfill(with: .failure(error))
result.fulfill(with: .error(error))
}

func `do`(_ block: () throws -> Void) {
Expand Down
4 changes: 4 additions & 0 deletions Sources/Promise/Miscellaneous/ExecutionContext.swift
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
import Foundation

public typealias ExecutionContext = (@escaping () -> Void) -> Void

internal let defaultExecutionContext: ExecutionContext = { DispatchQueue.main.async(execute: $0) }
8 changes: 4 additions & 4 deletions Sources/Promise/Miscellaneous/Result.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
enum Result<Value> {
case success(Value)
case failure(Error)
case value(Value)
case error(Error)
}

extension Result {
func unwrap() throws -> Value {
switch self {
case .success(let value):
case .value(let value):
return value
case .failure(let error):
case .error(let error):
throw error
}
}
Expand Down
20 changes: 20 additions & 0 deletions Sources/Promise/Miscellaneous/Traversable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,24 @@ public extension Sequence {
func sequence<T>() -> Future<[T]> where Element == Future<T> {
return traverse { $0 }
}

func traverse(_ transform: (Element) -> BasicFuture<Void>) -> BasicFuture<Void> {
return traverse(transform).map { (_: [Void]) in }
}

func traverse(_ transform: (Element) -> Future<Void>) -> Future<Void> {
return traverse(transform).map { (_: [Void]) in }
}
}

public extension Sequence where Element == BasicFuture<Void> {
func sequence() -> BasicFuture<Void> {
return sequence().map { (_: [Void]) in }
}
}

public extension Sequence where Element == Future<Void> {
func sequence() -> Future<Void> {
return sequence().map { (_: [Void]) in }
}
}
9 changes: 3 additions & 6 deletions Tests/PromiseTests/BasicFutureTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,9 @@ final class BasicFutureTests: XCTestCase {
let future = BasicFuture.fulfilled
var thenIsCalled = false

testExpectation { fulfill in
future.then {
XCTAssertFalse(thenIsCalled)
thenIsCalled = true
fulfill()
}
testValue(of: future) {
XCTAssertFalse(thenIsCalled)
thenIsCalled = true
}

wait()
Expand Down
27 changes: 16 additions & 11 deletions Tests/PromiseTests/Helpers.swift
Original file line number Diff line number Diff line change
@@ -1,39 +1,44 @@
import XCTest
import Promise
@testable import Promise

private let defaultTimeout: TimeInterval = 1

extension XCTestCase {
func testExpectation(
func waitTestExpectation(
timeout: TimeInterval = defaultTimeout,
isInverted: Bool = false,
block: (_ fulfill: @escaping () -> Void) throws -> Void
) rethrows {
) rethrows -> () -> Void {
let expectation = self.expectation(description: "")
expectation.isInverted = isInverted
try block { [weak expectation] in expectation?.fulfill() }
wait(for: [expectation], timeout: timeout)
return { self.wait(for: [expectation], timeout: timeout) }
}

func testExpectation(
timeout: TimeInterval = defaultTimeout,
isInverted: Bool = false,
block: (_ fulfill: @escaping () -> Void) throws -> Void
) rethrows {
let wait = try waitTestExpectation(timeout: timeout, isInverted: isInverted, block: block)
wait()
}

func wait(_ timeout: TimeInterval = defaultTimeout) {
testExpectation(timeout: timeout, isInverted: true) { _ in }
}

func value<Value>(of future: BasicFuture<Value>) -> Value? {
var value: Value?
future.then { value = $0 }
return value
return future.testableValue
}

func value<Value>(of future: Future<Value>) -> Value? {
var value: Value?
future.then { value = $0 }
guard case .value(let value)? = future.testableResult else { return nil }
return value
}

func error<Value>(of future: Future<Value>) -> Error? {
var error: Error?
future.catch { error = $0 }
guard case .error(let error)? = future.testableResult else { return nil }
return error
}

Expand Down
4 changes: 2 additions & 2 deletions Tests/PromiseTests/PromiseAllTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ final class PromiseAllTests: XCTestCase {
let future3 = Future.fulfilled(with: 3)

let all = [future1, future2, future3].sequence()
assertIsFulfilled(all, with: [1, 2, 3])
assertWillBeFulfilled(all, with: [1, 2, 3])
}

func testAllWithEmptyArray() {
let futures: [Future<Void>] = []
let all = futures.sequence()
let all: Future<[Void]> = futures.sequence()

testCurrentValue(of: all) { value in
XCTAssert(value.isEmpty)
Expand Down
6 changes: 3 additions & 3 deletions Tests/PromiseTests/PromiseGuardTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ final class PromiseGuardTests: XCTestCase {
if $0 { throw SimpleError() }
}

assertIsRejected(future)
assertWillBeRejected(future)
}

func testGuardSucceeds() {
let future = Future.fulfilled.guard {}
assertIsFulfilled(future)
assertWillBeFulfilled(future)
}

func testGuardOnlyCalledOnSuccess() {
let future = Future.rejected(with: SimpleError()).guard { XCTFail() }
assertIsRejected(future)
assertWillBeRejected(future)
}
}
2 changes: 1 addition & 1 deletion Tests/PromiseTests/PromiseRaceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ final class PromiseRaceTests: XCTestCase {
let future2 = Future.fulfilled.delayed(by: 0.5)

let first = race(future1, future2)
assertIsRejected(first)
assertWillBeRejected(first)
}
}
14 changes: 7 additions & 7 deletions Tests/PromiseTests/PromiseRecoverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,31 @@ import Promise
final class PromiseRecoverTests: XCTestCase {
func testRecover() {
let future = Future<Void>.rejected(with: SimpleError()).delayed(by: 0.1)
let recovered = future.recover { _ in .fulfilled }
let recovered = future.mapError { _ in () }
assertWillBeFulfilled(recovered)
}

func testRecoverInstant() {
let future = Future<Void>.rejected(with: SimpleError())
let recovered = future.recover { _ in .fulfilled }
assertIsFulfilled(recovered)
let recovered = future.mapError { _ in () }
assertWillBeFulfilled(recovered)
}

func testRecoverWithThrowingFunctionError() {
let future = Future<Void>.rejected(with: SimpleError()).delayed(by: 0.1)
let recovered: Future<Void> = future.recover { error in throw SimpleError() }
let recovered: Future<Void> = future.mapError { error in throw SimpleError() }
assertWillBeRejected(recovered)
}

func testIgnoreRecover() {
let future = Future.fulfilled(with: true).delayed(by: 0.1)
let recovered = future.recover { _ in .fulfilled(with: false) }
let recovered = future.mapError { _ in false }
assertWillBeFulfilled(recovered, with: true)
}

func testIgnoreRecoverInstant() {
let future = Future.fulfilled(with: true)
let recovered = future.recover { _ in .fulfilled(with: false) }
assertIsFulfilled(recovered, with: true)
let recovered = future.mapError { _ in false }
assertWillBeFulfilled(recovered, with: true)
}
}
Loading

0 comments on commit a67585b

Please sign in to comment.