Add PromiseKit dependency

- Added PromiseKit dependency
This commit is contained in:
2018-11-15 22:08:00 -04:00
parent 2689d86c18
commit be7b6b5881
541 changed files with 46282 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
import PromiseKit
import Dispatch
import XCTest
enum Error: Swift.Error {
case dummy // we reject with this when we don't intend to test against it
case sentinel(UInt32)
}
private let timeout: TimeInterval = 10
extension XCTestCase {
func describe(_ description: String, file: StaticString = #file, line: UInt = #line, body: () throws -> Void) {
PromiseKit.conf.Q.map = .main
do {
try body()
} catch {
XCTFail(description, file: file, line: line)
}
}
func specify(_ description: String, file: StaticString = #file, line: UInt = #line, body: ((promise: Promise<Void>, fulfill: () -> Void, reject: (Error) -> Void), XCTestExpectation) throws -> Void) {
let expectation = self.expectation(description: description)
let (pending, seal) = Promise<Void>.pending()
do {
try body((pending, { seal.fulfill(()) }, seal.reject), expectation)
waitForExpectations(timeout: timeout) { err in
if let _ = err {
XCTFail("wait failed: \(description)", file: file, line: line)
}
}
} catch {
XCTFail(description, file: file, line: line)
}
}
func testFulfilled(file: StaticString = #file, line: UInt = #line, body: @escaping (Promise<UInt32>, XCTestExpectation, UInt32) -> Void) {
testFulfilled(withExpectationCount: 1, file: file, line: line) {
body($0, $1.first!, $2)
}
}
func testRejected(file: StaticString = #file, line: UInt = #line, body: @escaping (Promise<UInt32>, XCTestExpectation, UInt32) -> Void) {
testRejected(withExpectationCount: 1, file: file, line: line) {
body($0, $1.first!, $2)
}
}
func testFulfilled(withExpectationCount: Int, file: StaticString = #file, line: UInt = #line, body: @escaping (Promise<UInt32>, [XCTestExpectation], UInt32) -> Void) {
let specify = mkspecify(withExpectationCount, file: file, line: line, body: body)
specify("already-fulfilled") { value in
return (.value(value), {})
}
specify("immediately-fulfilled") { value in
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
seal.fulfill(value)
})
}
specify("eventually-fulfilled") { value in
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
after(ticks: 5) {
seal.fulfill(value)
}
})
}
}
func testRejected(withExpectationCount: Int, file: StaticString = #file, line: UInt = #line, body: @escaping (Promise<UInt32>, [XCTestExpectation], UInt32) -> Void) {
let specify = mkspecify(withExpectationCount, file: file, line: line, body: body)
specify("already-rejected") { sentinel in
return (Promise(error: Error.sentinel(sentinel)), {})
}
specify("immediately-rejected") { sentinel in
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
seal.reject(Error.sentinel(sentinel))
})
}
specify("eventually-rejected") { sentinel in
let (promise, seal) = Promise<UInt32>.pending()
return (promise, {
after(ticks: 50) {
seal.reject(Error.sentinel(sentinel))
}
})
}
}
/////////////////////////////////////////////////////////////////////////
private func mkspecify(_ numberOfExpectations: Int, file: StaticString, line: UInt, body: @escaping (Promise<UInt32>, [XCTestExpectation], UInt32) -> Void) -> (String, _ feed: (UInt32) -> (Promise<UInt32>, () -> Void)) -> Void {
return { desc, feed in
let value = arc4random()
let (promise, executeAfter) = feed(value)
let expectations = (1...numberOfExpectations).map {
self.expectation(description: "\(desc) (\($0))")
}
body(promise, expectations, value)
executeAfter()
self.waitForExpectations(timeout: timeout) { err in
if let _ = err {
XCTFail("timed out: \(desc)", file: file, line: line)
}
}
}
}
func mkex() -> XCTestExpectation {
return expectation(description: "")
}
}
func after(ticks: Int, execute body: @escaping () -> Void) {
precondition(ticks > 0)
var ticks = ticks
func f() {
DispatchQueue.main.async {
ticks -= 1
if ticks == 0 {
body()
} else {
f()
}
}
}
f()
}
extension Promise {
func test(onFulfilled: @escaping () -> Void, onRejected: @escaping () -> Void) {
tap { result in
switch result {
case .fulfilled:
onFulfilled()
case .rejected:
onRejected()
}
}.silenceWarning()
}
}
prefix func ++(a: inout Int) -> Int {
a += 1
return a
}
extension Promise {
func silenceWarning() {}
}
#if os(Linux)
import func Glibc.random
func arc4random() -> UInt32 {
return UInt32(random())
}
extension XCTestExpectation {
func fulfill() {
fulfill(#file, line: #line)
}
}
extension XCTestCase {
func wait(for: [XCTestExpectation], timeout: TimeInterval, file: StaticString = #file, line: UInt = #line) {
#if !(swift(>=4.0) && !swift(>=4.1))
let line = Int(line)
#endif
waitForExpectations(timeout: timeout, file: file, line: line)
}
}
#endif

View File

@@ -0,0 +1,26 @@
import PromiseKit
import XCTest
class Test212: XCTestCase {
func test() {
describe("2.1.2.1: When fulfilled, a promise: must not transition to any other state.") {
testFulfilled { promise, expectation, _ in
promise.test(onFulfilled: expectation.fulfill, onRejected: { XCTFail() })
}
specify("trying to fulfill then immediately reject") { d, expectation in
d.promise.test(onFulfilled: expectation.fulfill, onRejected: { XCTFail() })
d.fulfill()
d.reject(Error.dummy)
}
specify("trying to fulfill then reject, delayed") { d, expectation in
d.promise.test(onFulfilled: expectation.fulfill, onRejected: { XCTFail() })
after(ticks: 1) {
d.fulfill()
d.reject(Error.dummy)
}
}
}
}
}

View File

@@ -0,0 +1,34 @@
import PromiseKit
import XCTest
class Test213: XCTestCase {
func test() {
describe("2.1.3.1: When rejected, a promise: must not transition to any other state.") {
testRejected { promise, expectation, _ in
promise.test(onFulfilled: { XCTFail() }, onRejected: expectation.fulfill)
}
specify("trying to reject then immediately fulfill") { d, expectation in
d.promise.test(onFulfilled: { XCTFail() }, onRejected: expectation.fulfill)
d.reject(Error.dummy)
d.fulfill()
}
specify("trying to reject then fulfill, delayed") { d, expectation in
d.promise.test(onFulfilled: { XCTFail() }, onRejected: expectation.fulfill)
after(ticks: 1) {
d.reject(Error.dummy)
d.fulfill()
}
}
specify("trying to reject immediately then fulfill delayed") { d, expectation in
d.promise.test(onFulfilled: { XCTFail() }, onRejected: expectation.fulfill)
d.reject(Error.dummy)
after(ticks: 1) {
d.fulfill()
}
}
}
}
}

View File

@@ -0,0 +1,92 @@
import PromiseKit
import XCTest
class Test222: XCTestCase {
func test() {
describe("2.2.2: If `onFulfilled` is a function,") {
describe("2.2.2.1: it must be called after `promise` is fulfilled, with `promise`s fulfillment value as its first argument.") {
testFulfilled { promise, expectation, sentinel in
promise.done {
XCTAssertEqual(sentinel, $0)
expectation.fulfill()
}.silenceWarning()
}
}
describe("2.2.2.2: it must not be called before `promise` is fulfilled") {
specify("fulfilled after a delay") { d, expectation in
var called = false
d.promise.done {
called = true
expectation.fulfill()
}.silenceWarning()
after(ticks: 5) {
XCTAssertFalse(called)
d.fulfill()
}
}
specify("never fulfilled") { d, expectation in
d.promise.done{ XCTFail() }.silenceWarning()
after(ticks: 1000, execute: expectation.fulfill)
}
}
describe("2.2.2.3: it must not be called more than once.") {
specify("already-fulfilled") { _, expectation in
let ex = (expectation, mkex())
Promise().done {
ex.0.fulfill()
}.silenceWarning()
after(ticks: 1000) {
ex.1.fulfill()
}
}
specify("trying to fulfill a pending promise more than once, immediately") { d, expectation in
d.promise.done(expectation.fulfill).silenceWarning()
d.fulfill()
d.fulfill()
}
specify("trying to fulfill a pending promise more than once, delayed") { d, expectation in
d.promise.done(expectation.fulfill).silenceWarning()
after(ticks: 5) {
d.fulfill()
d.fulfill()
}
}
specify("trying to fulfill a pending promise more than once, immediately then delayed") { d, expectation in
let ex = (expectation, mkex())
d.promise.done(ex.0.fulfill).silenceWarning()
d.fulfill()
after(ticks: 5) {
d.fulfill()
}
after(ticks: 10, execute: ex.1.fulfill)
}
specify("when multiple `then` calls are made, spaced apart in time") { d, expectation in
let ex = (expectation, self.expectation(description: ""), self.expectation(description: ""), self.expectation(description: ""))
do {
d.promise.done(ex.0.fulfill).silenceWarning()
}
after(ticks: 5) {
d.promise.done(ex.1.fulfill).silenceWarning()
}
after(ticks: 10) {
d.promise.done(ex.2.fulfill).silenceWarning()
}
after(ticks: 15) {
d.fulfill()
ex.3.fulfill()
}
}
specify("when `then` is interleaved with fulfillment") { d, expectation in
let ex = (expectation, self.expectation(description: ""), self)
d.promise.done(ex.0.fulfill).silenceWarning()
d.fulfill()
d.promise.done(ex.1.fulfill).silenceWarning()
}
}
}
}
}

View File

@@ -0,0 +1,93 @@
import PromiseKit
import XCTest
class Test223: XCTestCase {
func test() {
describe("2.2.3: If `onRejected` is a function,") {
describe("2.2.3.1: it must be called after `promise` is rejected, with `promise`s rejection reason as its first argument.") {
testRejected { promise, expectation, sentinel in
promise.catch { error in
if case Error.sentinel(let value) = error {
XCTAssertEqual(value, sentinel)
} else {
XCTFail()
}
expectation.fulfill()
}
}
}
describe("2.2.3.2: it must not be called before `promise` is rejected") {
specify("rejected after a delay") { d, expectation in
var called = false
d.promise.catch { _ in
called = true
expectation.fulfill()
}
after(ticks: 1) {
XCTAssertFalse(called)
d.reject(Error.dummy)
}
}
specify("never rejected") { d, expectation in
d.promise.catch { _ in XCTFail() }
after(ticks: 1, execute: expectation.fulfill)
}
}
describe("2.2.3.3: it must not be called more than once.") {
specify("already-rejected") { d, expectation in
var timesCalled = 0
Promise<Int>(error: Error.dummy).catch { _ in
XCTAssertEqual(++timesCalled, 1)
}
after(ticks: 2) {
XCTAssertEqual(timesCalled, 1)
expectation.fulfill()
}
}
specify("trying to reject a pending promise more than once, immediately") { d, expectation in
d.promise.catch{_ in expectation.fulfill() }
d.reject(Error.dummy)
d.reject(Error.dummy)
}
specify("trying to reject a pending promise more than once, delayed") { d, expectation in
d.promise.catch{_ in expectation.fulfill() }
after(ticks: 1) {
d.reject(Error.dummy)
d.reject(Error.dummy)
}
}
specify("trying to reject a pending promise more than once, immediately then delayed") { d, expectation in
d.promise.catch{_ in expectation.fulfill() }
d.reject(Error.dummy)
after(ticks: 1) {
d.reject(Error.dummy)
}
}
specify("when multiple `then` calls are made, spaced apart in time") { d, expectation in
let mk = { self.expectation(description: "") }
let ex = (expectation, mk(), mk(), mk())
do {
d.promise.catch{ _ in ex.0.fulfill() }
}
after(ticks: 1) {
d.promise.catch{ _ in ex.1.fulfill() }
}
after(ticks: 2) {
d.promise.catch{ _ in ex.2.fulfill() }
}
after(ticks: 3) {
d.reject(Error.dummy)
ex.3.fulfill()
}
}
specify("when `then` is interleaved with rejection") { d, expectation in
let ex = (expectation, self.expectation(description: ""))
d.promise.catch{ _ in ex.0.fulfill() }
d.reject(Error.dummy)
d.promise.catch{ _ in ex.1.fulfill() }
}
}
}
}
}

View File

@@ -0,0 +1,146 @@
import PromiseKit
import XCTest
class Test224: XCTestCase {
func test() {
describe("2.2.4: `onFulfilled` or `onRejected` must not be called until the execution context stack contains only platform code.") {
describe("`then` returns before the promise becomes fulfilled or rejected") {
testFulfilled { promise, expectation, dummy in
var thenHasReturned = false
promise.done { _ in
XCTAssert(thenHasReturned)
expectation.fulfill()
}.silenceWarning()
thenHasReturned = true
}
testRejected { promise, expectation, memo in
var catchHasReturned = false
promise.catch { _->() in
XCTAssert(catchHasReturned)
expectation.fulfill()
}
catchHasReturned = true
}
}
describe("Clean-stack execution ordering tests (fulfillment case)") {
specify("when `onFulfilled` is added immediately before the promise is fulfilled") { d, expectation in
var onFulfilledCalled = false
d.promise.done {
onFulfilledCalled = true
expectation.fulfill()
}.silenceWarning()
d.fulfill()
XCTAssertFalse(onFulfilledCalled)
}
specify("when `onFulfilled` is added immediately after the promise is fulfilled") { d, expectation in
var onFulfilledCalled = false
d.fulfill()
d.promise.done {
onFulfilledCalled = true
expectation.fulfill()
}.silenceWarning()
XCTAssertFalse(onFulfilledCalled)
}
specify("when one `onFulfilled` is added inside another `onFulfilled`") { _, expectation in
var firstOnFulfilledFinished = false
let promise = Promise()
promise.done {
promise.done {
XCTAssertTrue(firstOnFulfilledFinished)
expectation.fulfill()
}.silenceWarning()
firstOnFulfilledFinished = true
}.silenceWarning()
}
specify("when `onFulfilled` is added inside an `onRejected`") { _, expectation in
let promise1 = Promise<Void>(error: Error.dummy)
let promise2 = Promise()
var firstOnRejectedFinished = false
promise1.catch { _ in
promise2.done {
XCTAssertTrue(firstOnRejectedFinished)
expectation.fulfill()
}.silenceWarning()
firstOnRejectedFinished = true
}
}
specify("when the promise is fulfilled asynchronously") { d, expectation in
var firstStackFinished = false
after(ticks: 1) {
d.fulfill()
firstStackFinished = true
}
d.promise.done {
XCTAssertTrue(firstStackFinished)
expectation.fulfill()
}.silenceWarning()
}
}
describe("Clean-stack execution ordering tests (rejection case)") {
specify("when `onRejected` is added immediately before the promise is rejected") { d, expectation in
var onRejectedCalled = false
d.promise.catch { _ in
onRejectedCalled = true
expectation.fulfill()
}
d.reject(Error.dummy)
XCTAssertFalse(onRejectedCalled)
}
specify("when `onRejected` is added immediately after the promise is rejected") { d, expectation in
var onRejectedCalled = false
d.reject(Error.dummy)
d.promise.catch { _ in
onRejectedCalled = true
expectation.fulfill()
}
XCTAssertFalse(onRejectedCalled)
}
specify("when `onRejected` is added inside an `onFulfilled`") { d, expectation in
let promise1 = Promise()
let promise2 = Promise<Void>(error: Error.dummy)
var firstOnFulfilledFinished = false
promise1.done { _ in
promise2.catch { _ in
XCTAssertTrue(firstOnFulfilledFinished)
expectation.fulfill()
}
firstOnFulfilledFinished = true
}.silenceWarning()
}
specify("when one `onRejected` is added inside another `onRejected`") { d, expectation in
let promise = Promise<Void>(error: Error.dummy)
var firstOnRejectedFinished = false;
promise.catch { _ in
promise.catch { _ in
XCTAssertTrue(firstOnRejectedFinished)
expectation.fulfill()
}
firstOnRejectedFinished = true
}
}
specify("when the promise is rejected asynchronously") { d, expectation in
var firstStackFinished = false
after(ticks: 1) {
d.reject(Error.dummy)
firstStackFinished = true
}
d.promise.catch { _ in
XCTAssertTrue(firstStackFinished)
expectation.fulfill()
}
}
}
}
}
}

View File

@@ -0,0 +1,275 @@
import PromiseKit
import XCTest
class Test226: XCTestCase {
func test() {
describe("2.2.6: `then` may be called multiple times on the same promise.") {
describe("2.2.6.1: If/when `promise` is fulfilled, all respective `onFulfilled` callbacks must execute in the order of their originating calls to `then`.") {
describe("multiple boring fulfillment handlers") {
testFulfilled(withExpectationCount: 4) { promise, exes, sentinel -> () in
var orderValidator = 0
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 4)
exes[3].fulfill()
}.silenceWarning()
}
}
describe("multiple fulfillment handlers, one of which throws") {
testFulfilled(withExpectationCount: 4) { promise, exes, sentinel in
var orderValidator = 0
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}.silenceWarning()
promise.catch { _ in XCTFail() }
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
throw Error.dummy
}.silenceWarning()
promise.catch { value in XCTFail() }
promise.done {
XCTAssertEqual($0, sentinel)
XCTAssertEqual(++orderValidator, 4)
exes[3].fulfill()
}.silenceWarning()
}
}
describe("results in multiple branching chains with their own fulfillment values") {
testFulfilled(withExpectationCount: 3) { promise, exes, memo in
let sentinel1 = 671
let sentinel2: UInt32 = 672
let sentinel3 = 673
promise.map { _ in
return sentinel1
}.done { value in
XCTAssertEqual(sentinel1, value)
exes[0].fulfill()
}.silenceWarning()
promise.done { _ in
throw Error.sentinel(sentinel2)
}.catch { err in
switch err {
case Error.sentinel(let err) where err == sentinel2:
break
default:
XCTFail()
}
exes[1].fulfill()
}
promise.map { _ in
sentinel3
}.done {
XCTAssertEqual($0, sentinel3)
exes[2].fulfill()
}.silenceWarning()
}
}
describe("`onFulfilled` handlers are called in the original order") {
testFulfilled(withExpectationCount: 3) { promise, exes, memo in
var orderValidator = 0
promise.done { _ in
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}.silenceWarning()
promise.done { _ in
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}.silenceWarning()
promise.done { _ in
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
}.silenceWarning()
}
}
describe("even when one handler is added inside another handler") {
testFulfilled(withExpectationCount: 3) { promise, exes, memo in
var x = 0
promise.done { _ in
XCTAssertEqual(x, 0)
x += 1
exes[0].fulfill()
promise.done { _ in
XCTAssertEqual(x, 2)
x += 1
exes[1].fulfill()
}.silenceWarning()
}.silenceWarning()
promise.done { _ in
XCTAssertEqual(x, 1)
x += 1
exes[2].fulfill()
}.silenceWarning()
}
}
}
describe("2.2.6.2: If/when `promise` is rejected, all respective `onRejected` callbacks must execute in the order of their originating calls to `then`.") {
describe("multiple boring rejection handlers") {
testRejected(withExpectationCount: 4) { promise, exes, sentinel in
var ticket = 0
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++ticket, 1)
exes[0].fulfill()
}
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++ticket, 2)
exes[1].fulfill()
}
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++ticket, 3)
exes[2].fulfill()
}
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++ticket, 4)
exes[3].fulfill()
}
}
}
describe("multiple rejection handlers, one of which throws") {
testRejected(withExpectationCount: 4) { promise, exes, sentinel in
var orderValidator = 0
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++orderValidator, 1)
exes[0].fulfill()
}
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++orderValidator, 2)
exes[1].fulfill()
}
promise.done { _ in XCTFail() }.silenceWarning()
promise.recover { err -> Promise<UInt32> in
if case Error.sentinel(let x) = err {
XCTAssertEqual(x, sentinel)
} else {
XCTFail()
}
XCTAssertEqual(++orderValidator, 3)
exes[2].fulfill()
throw Error.dummy
}.silenceWarning()
promise.done { _ in XCTFail() }.silenceWarning()
promise.catch { err in
guard case Error.sentinel(let x) = err, x == sentinel else { return XCTFail() }
XCTAssertEqual(++orderValidator, 4)
exes[3].fulfill()
}
}
}
describe("results in multiple branching chains with their own fulfillment values") {
testRejected(withExpectationCount: 3) { promise, exes, memo in
let sentinel1 = arc4random()
let sentinel2 = arc4random()
let sentinel3 = arc4random()
promise.recover { _ in
return .value(sentinel1)
}.done { value in
XCTAssertEqual(sentinel1, value)
exes[0].fulfill()
}
promise.recover { _ -> Promise<UInt32> in
throw Error.sentinel(sentinel2)
}.catch { err in
if case Error.sentinel(let x) = err, x == sentinel2 {
exes[1].fulfill()
}
}
promise.recover { _ in
.value(sentinel3)
}.done { value in
XCTAssertEqual(value, sentinel3)
exes[2].fulfill()
}
}
}
describe("`onRejected` handlers are called in the original order") {
testRejected(withExpectationCount: 3) { promise, exes, memo in
var x = 0
promise.catch { _ in
XCTAssertEqual(x, 0)
x += 1
exes[0].fulfill()
}
promise.catch { _ in
XCTAssertEqual(x, 1)
x += 1
exes[1].fulfill()
}
promise.catch { _ in
XCTAssertEqual(x, 2)
x += 1
exes[2].fulfill()
}
}
}
describe("even when one handler is added inside another handler") {
testRejected(withExpectationCount: 3) { promise, exes, memo in
var x = 0
promise.catch { _ in
XCTAssertEqual(x, 0)
x += 1
exes[0].fulfill()
promise.catch { _ in
XCTAssertEqual(x, 2)
x += 1
exes[1].fulfill()
}
}
promise.catch { _ in
XCTAssertEqual(x, 1)
x += 1
exes[2].fulfill()
}
}
}
}
}
}
}

View File

@@ -0,0 +1,33 @@
import PromiseKit
import XCTest
class Test227: XCTestCase {
func test() {
describe("2.2.7: `then` must return a promise: `promise2 = promise1.then(onFulfilled, onRejected)") {
describe("2.2.7.2: If either `onFulfilled` or `onRejected` throws an exception `e`, `promise2` must be rejected with `e` as the reason.") {
testFulfilled { promise1, expectation, _ in
let sentinel = arc4random()
let promise2 = promise1.done { _ in throw Error.sentinel(sentinel) }
promise2.catch {
if case Error.sentinel(let x) = $0, x == sentinel {
expectation.fulfill()
}
}
}
testRejected { promise1, expectation, _ in
let sentinel = arc4random()
let promise2 = promise1.recover { _ -> Promise<UInt32> in throw Error.sentinel(sentinel) }
promise2.catch { error in
if case Error.sentinel(let x) = error, x == sentinel {
expectation.fulfill()
}
}
}
}
}
}
}

View File

@@ -0,0 +1,31 @@
import PromiseKit
import XCTest
class Test231: XCTestCase {
func test() {
describe("2.3.1: If `promise` and `x` refer to the same object, reject `promise` with a `TypeError' as the reason.") {
specify("via return from a fulfilled promise") { d, expectation in
var promise: Promise<Void>!
promise = Promise().then { () -> Promise<Void> in
return promise
}
promise.catch { err in
if case PMKError.returnedSelf = err {
expectation.fulfill()
}
}
}
specify("via return from a rejected promise") { d, expectation in
var promise: Promise<Void>!
promise = Promise<Void>(error: Error.dummy).recover { _ -> Promise<Void> in
return promise
}
promise.catch { err in
if case PMKError.returnedSelf = err {
expectation.fulfill()
}
}
}
}
}
}

View File

@@ -0,0 +1,116 @@
import PromiseKit
import XCTest
class Test232: XCTestCase {
func test() {
describe("2.3.2: If `x` is a promise, adopt its state") {
describe("2.3.2.1: If `x` is pending, `promise` must remain pending until `x` is fulfilled or rejected.") {
func xFactory() -> Promise<UInt32> {
return Promise.pending().promise
}
testPromiseResolution(factory: xFactory) { promise, expectation in
var wasFulfilled = false;
var wasRejected = false;
promise.test(onFulfilled: { wasFulfilled = true }, onRejected: { wasRejected = true })
after(ticks: 4) {
XCTAssertFalse(wasFulfilled)
XCTAssertFalse(wasRejected)
expectation.fulfill()
}
}
}
describe("2.3.2.2: If/when `x` is fulfilled, fulfill `promise` with the same value.") {
describe("`x` is already-fulfilled") {
let sentinel = arc4random()
func xFactory() -> Promise<UInt32> {
return .value(sentinel)
}
testPromiseResolution(factory: xFactory) { promise, expectation in
promise.done {
XCTAssertEqual($0, sentinel)
expectation.fulfill()
}.silenceWarning()
}
}
describe("`x` is eventually-fulfilled") {
let sentinel = arc4random()
func xFactory() -> Promise<UInt32> {
return Promise { seal in
after(ticks: 2) {
seal.fulfill(sentinel)
}
}
}
testPromiseResolution(factory: xFactory) { promise, expectation in
promise.done {
XCTAssertEqual($0, sentinel)
expectation.fulfill()
}.silenceWarning()
}
}
}
describe("2.3.2.3: If/when `x` is rejected, reject `promise` with the same reason.") {
describe("`x` is already-rejected") {
let sentinel = arc4random()
func xFactory() -> Promise<UInt32> {
return Promise(error: Error.sentinel(sentinel))
}
testPromiseResolution(factory: xFactory) { promise, expectation in
promise.catch { err in
if case Error.sentinel(let value) = err, value == sentinel {
expectation.fulfill()
}
}
}
}
describe("`x` is eventually-rejected") {
let sentinel = arc4random()
func xFactory() -> Promise<UInt32> {
return Promise { seal in
after(ticks: 2) {
seal.reject(Error.sentinel(sentinel))
}
}
}
testPromiseResolution(factory: xFactory) { promise, expectation in
promise.catch { err in
if case Error.sentinel(let value) = err, value == sentinel {
expectation.fulfill()
}
}
}
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////
extension Test232 {
fileprivate func testPromiseResolution(factory: @escaping () -> Promise<UInt32>, line: UInt = #line, test: (Promise<UInt32>, XCTestExpectation) -> Void) {
specify("via return from a fulfilled promise", file: #file, line: line) { d, expectation in
let promise = Promise.value(arc4random()).then { _ in factory() }
test(promise, expectation)
}
specify("via return from a rejected promise", file: #file, line: line) { d, expectation in
let promise: Promise<UInt32> = Promise(error: Error.dummy).recover { _ in factory() }
test(promise, expectation)
}
}
}

View File

@@ -0,0 +1,26 @@
import PromiseKit
import XCTest
class Test234: XCTestCase {
func test() {
describe("2.3.4: If `x` is not an object or function, fulfill `promise` with `x`") {
testFulfilled { promise, exception, _ in
promise.map { value -> UInt32 in
return 1
}.done { value in
XCTAssertEqual(value, 1)
exception.fulfill()
}.silenceWarning()
}
testRejected { promise, expectation, _ in
promise.recover { _ -> Promise<UInt32> in
return .value(UInt32(1))
}.done { value in
XCTAssertEqual(value, 1)
expectation.fulfill()
}.silenceWarning()
}
}
}
}

View File

@@ -0,0 +1,13 @@
Resources
=========
* https://github.com/promises-aplus/promises-tests
Skipped
=======
* 2.3.3: Otherwise, if x is an object or function.
This spec is a NOOP for Swift:
- We have decided not to interact with other Promises A+ implementations
- functions cannot have properties
* 2.3.3.4: If then is not a function, fulfill promise with x.
- See: The 2.3.4 suite.

View File

@@ -0,0 +1,34 @@
@import PromiseKit;
@import XCTest;
#import "Infrastructure.h"
@interface BridgingTests: XCTestCase @end @implementation BridgingTests
- (void)testChainAnyPromiseFromSwiftCode {
XCTestExpectation *ex = [self expectationWithDescription:@""];
AnyPromise *promise = PMKAfter(0.02);
for (int x = 0; x < 100; ++x) {
promise = promise.then(^{
return [[[PromiseBridgeHelper alloc] init] bridge1];
});
}
promise.then(^{
[ex fulfill];
});
[self waitForExpectationsWithTimeout:20 handler:nil];
}
- (void)test626 {
XCTestExpectation *ex = [self expectationWithDescription:@""];
testCase626().then(^{
XCTFail();
}).ensure(^{
[ex fulfill];
});
[self waitForExpectationsWithTimeout:20 handler:nil];
}
@end

View File

@@ -0,0 +1,280 @@
import Foundation
import PromiseKit
import XCTest
class BridgingTests: XCTestCase {
func testCanBridgeAnyObject() {
let sentinel = NSURLRequest()
let p = Promise.value(sentinel)
let ap = AnyPromise(p)
XCTAssertEqual(ap.value(forKey: "value") as? NSURLRequest, sentinel)
}
func testCanBridgeOptional() {
let sentinel: NSURLRequest? = NSURLRequest()
let p = Promise.value(sentinel)
let ap = AnyPromise(p)
XCTAssertEqual(ap.value(forKey: "value") as? NSURLRequest, sentinel)
}
func testCanBridgeSwiftArray() {
let sentinel = [NSString(), NSString(), NSString()]
let p = Promise.value(sentinel)
let ap = AnyPromise(p)
guard let foo = ap.value(forKey: "value") as? [NSString] else { return XCTFail() }
XCTAssertEqual(foo, sentinel)
}
func testCanBridgeSwiftDictionary() {
let sentinel = [NSString(): NSString()]
let p = Promise.value(sentinel)
let ap = AnyPromise(p)
guard let foo = ap.value(forKey: "value") as? [NSString: NSString] else { return XCTFail() }
XCTAssertEqual(foo, sentinel)
}
func testCanBridgeInt() {
let sentinel = 3
let p = Promise.value(sentinel)
let ap = AnyPromise(p)
XCTAssertEqual(ap.value(forKey: "value") as? Int, sentinel)
}
func testCanBridgeString() {
let sentinel = "a"
let p = Promise.value(sentinel)
let ap = AnyPromise(p)
XCTAssertEqual(ap.value(forKey: "value") as? String, sentinel)
}
func testCanBridgeBool() {
let sentinel = true
let p = Promise.value(sentinel)
let ap = AnyPromise(p)
XCTAssertEqual(ap.value(forKey: "value") as? Bool, sentinel)
}
func testCanChainOffAnyPromiseFromObjC() {
let ex = expectation(description: "")
firstly {
.value(1)
}.then { _ -> AnyPromise in
return PromiseBridgeHelper().value(forKey: "bridge2") as! AnyPromise
}.done { value in
XCTAssertEqual(123, value as? Int)
ex.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1)
}
func testCanThenOffAnyPromise() {
let ex = expectation(description: "")
PMKDummyAnyPromise_YES().then { obj -> Promise<Void> in
if let value = obj as? NSNumber {
XCTAssertEqual(value, NSNumber(value: true))
ex.fulfill()
}
return Promise()
}.silenceWarning()
waitForExpectations(timeout: 1)
}
func testCanThenOffManifoldAnyPromise() {
let ex = expectation(description: "")
PMKDummyAnyPromise_Manifold().then { obj -> Promise<Void> in
defer { ex.fulfill() }
XCTAssertEqual(obj as? NSNumber, NSNumber(value: true), "\(obj ?? "nil") is not @YES")
return Promise()
}.silenceWarning()
waitForExpectations(timeout: 1)
}
func testCanAlwaysOffAnyPromise() {
let ex = expectation(description: "")
PMKDummyAnyPromise_YES().then { obj -> Promise<Void> in
ex.fulfill()
return Promise()
}.silenceWarning()
waitForExpectations(timeout: 1)
}
func testCanCatchOffAnyPromise() {
let ex = expectation(description: "")
PMKDummyAnyPromise_Error().catch { err in
ex.fulfill()
}
waitForExpectations(timeout: 1)
}
func testAsPromise() {
#if swift(>=3.1)
XCTAssertTrue(Promise(PMKDummyAnyPromise_Error()).isRejected)
XCTAssertEqual(Promise(PMKDummyAnyPromise_YES()).value as? NSNumber, NSNumber(value: true))
#else
XCTAssertTrue(PMKDummyAnyPromise_Error().asPromise().isRejected)
XCTAssertEqual(PMKDummyAnyPromise_YES().asPromise().value as? NSNumber, NSNumber(value: true))
#endif
}
func testFirstlyReturningAnyPromiseSuccess() {
let ex = expectation(description: "")
firstly {
PMKDummyAnyPromise_Error()
}.catch { error in
ex.fulfill()
}
waitForExpectations(timeout: 1)
}
func testFirstlyReturningAnyPromiseError() {
let ex = expectation(description: "")
firstly {
PMKDummyAnyPromise_YES()
}.done { _ in
ex.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1)
}
func test1() {
let ex = expectation(description: "")
// AnyPromise.then { return x }
let input = after(seconds: 0).map{ 1 }
AnyPromise(input).then { obj -> Promise<Int> in
XCTAssertEqual(obj as? Int, 1)
return .value(2)
}.done { value in
XCTAssertEqual(value, 2)
ex.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1)
}
func test2() {
let ex = expectation(description: "")
// AnyPromise.then { return AnyPromise }
let input = after(seconds: 0).map{ 1 }
AnyPromise(input).then { obj -> AnyPromise in
XCTAssertEqual(obj as? Int, 1)
return AnyPromise(after(seconds: 0).map{ 2 })
}.done { obj in
XCTAssertEqual(obj as? Int, 2)
ex.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1)
}
func test3() {
let ex = expectation(description: "")
// AnyPromise.then { return Promise<Int> }
let input = after(seconds: 0).map{ 1 }
AnyPromise(input).then { obj -> Promise<Int> in
XCTAssertEqual(obj as? Int, 1)
return after(seconds: 0).map{ 2 }
}.done { value in
XCTAssertEqual(value, 2)
ex.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1, handler: nil)
}
// can return AnyPromise (that fulfills) in then handler
func test4() {
let ex = expectation(description: "")
Promise.value(1).then { _ -> AnyPromise in
return AnyPromise(after(seconds: 0).map{ 1 })
}.done { x in
XCTAssertEqual(x as? Int, 1)
ex.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1, handler: nil)
}
// can return AnyPromise (that rejects) in then handler
func test5() {
let ex = expectation(description: "")
Promise.value(1).then { _ -> AnyPromise in
let promise = after(.milliseconds(100)).done{ throw Error.dummy }
return AnyPromise(promise)
}.catch { err in
ex.fulfill()
}
waitForExpectations(timeout: 1)
}
func testStandardSwiftBridgeIsUnambiguous() {
let p = Promise.value(1)
let q = Promise(p)
XCTAssertEqual(p.value, q.value)
}
/// testing NSError to Error for cancelledError types
func testErrorCancellationBridging() {
let ex = expectation(description: "")
let p = Promise().done {
throw LocalError.cancel as NSError
}
p.catch { _ in
XCTFail()
}
p.catch(policy: .allErrors) {
XCTAssertTrue($0.isCancelled)
ex.fulfill()
}
waitForExpectations(timeout: 1)
// here we verify that Swifts NSError bridging works as advertised
XCTAssertTrue(LocalError.cancel.isCancelled)
XCTAssertTrue((LocalError.cancel as NSError).isCancelled)
}
}
private enum Error: Swift.Error {
case dummy
}
extension Promise {
func silenceWarning() {}
}
private enum LocalError: CancellableError {
case notCancel
case cancel
var isCancelled: Bool {
switch self {
case .notCancel: return false
case .cancel: return true
}
}
}

View File

@@ -0,0 +1,14 @@
@import Foundation;
@class AnyPromise;
AnyPromise *PMKDummyAnyPromise_YES(void);
AnyPromise *PMKDummyAnyPromise_Manifold(void);
AnyPromise *PMKDummyAnyPromise_Error(void);
__attribute__((objc_runtime_name("PMKPromiseBridgeHelper")))
__attribute__((objc_subclassing_restricted))
@interface PromiseBridgeHelper: NSObject
- (AnyPromise *)bridge1;
@end
AnyPromise *testCase626(void);

View File

@@ -0,0 +1,38 @@
@import Foundation;
@import PromiseKit;
#import "Infrastructure.h"
AnyPromise *PMKDummyAnyPromise_YES() {
return [AnyPromise promiseWithValue:@YES];
}
AnyPromise *PMKDummyAnyPromise_Manifold() {
return [AnyPromise promiseWithValue:PMKManifold(@YES, @NO, @NO)];
}
AnyPromise *PMKDummyAnyPromise_Error() {
return [AnyPromise promiseWithValue:[NSError errorWithDomain:@"a" code:1 userInfo:nil]];
}
@implementation PromiseBridgeHelper (objc)
- (AnyPromise *)bridge2 {
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
resolve(@123);
});
}];
}
@end
#import "PMKBridgeTests-Swift.h"
AnyPromise *testCase626() {
return PMKWhen(@[[TestPromise626 promise], [TestPromise626 promise]]).then(^(id value){
NSLog(@"Success: %@", value);
}).catch(^(NSError *error) {
NSLog(@"Error: %@", error);
@throw error;
});
}

View File

@@ -0,0 +1,24 @@
import PromiseKit
// for BridgingTests.m
@objc(PMKPromiseBridgeHelper) class PromiseBridgeHelper: NSObject {
@objc func bridge1() -> AnyPromise {
let p = after(.milliseconds(10))
return AnyPromise(p)
}
}
enum MyError: Error {
case PromiseError
}
@objc class TestPromise626: NSObject {
@objc class func promise() -> AnyPromise {
let promise: Promise<String> = Promise { seal in
seal.reject(MyError.PromiseError)
}
return AnyPromise(promise)
}
}

View File

@@ -0,0 +1,896 @@
@import PromiseKit;
@import XCTest;
#import "Infrastructure.h"
#define PMKTestErrorDomain @"PMKTestErrorDomain"
static inline NSError *dummyWithCode(NSInteger code) {
return [NSError errorWithDomain:PMKTestErrorDomain code:rand() userInfo:@{NSLocalizedDescriptionKey: @(code).stringValue}];
}
static inline NSError *dummy() {
return dummyWithCode(rand());
}
static inline AnyPromise *rejectLater() {
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
resolve(dummy());
});
});
}];
}
static inline AnyPromise *fulfillLater() {
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
resolve(@1);
});
}];
}
@interface AnyPromiseTestSuite : XCTestCase @end @implementation AnyPromiseTestSuite
- (void)test_01_resolve {
id ex1 = [self expectationWithDescription:@""];
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@1);
}];
promise.then(^(NSNumber *o){
[ex1 fulfill];
XCTAssertEqual(o.intValue, 1);
});
promise.catch(^{
XCTFail();
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_02_reject {
id ex1 = [self expectationWithDescription:@""];
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(dummyWithCode(2));
}];
promise.then(^{
XCTFail();
});
promise.catch(^(NSError *error){
XCTAssertEqualObjects(error.localizedDescription, @"2");
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_03_return_error {
id ex1 = [self expectationWithDescription:@""];
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@2);
}];
promise.then(^{
return [NSError errorWithDomain:@"a" code:3 userInfo:nil];
}).catch(^(NSError *e){
[ex1 fulfill];
XCTAssertEqual(3, e.code);
});
promise.catch(^{
XCTFail();
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_04_return_error_doesnt_compromise_result {
id ex1 = [self expectationWithDescription:@""];
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@4);
}].then(^{
return dummy();
});
promise.then(^{
XCTFail();
});
promise.catch(^{
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_05_throw_and_bubble {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@5);
}].then(^(id ii){
XCTAssertEqual(5, [ii intValue]);
return [NSError errorWithDomain:@"a" code:[ii intValue] userInfo:nil];
}).catch(^(NSError *e){
XCTAssertEqual(e.code, 5);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_05_throw_and_bubble_more {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@5);
}].then(^{
return dummy();
}).then(^{
//NOOP
}).catch(^(NSError *e){
[ex1 fulfill];
XCTAssertEqualObjects(e.domain, PMKTestErrorDomain);
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_06_return_error {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@5);
}].then(^{
return dummy();
}).catch(^{
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_07_can_then_resolved {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@1);
}].then(^(id o){
[ex1 fulfill];
XCTAssertEqualObjects(@1, o);
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_07a_can_fail_rejected {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(dummyWithCode(1));
}].catch(^(NSError *e){
[ex1 fulfill];
XCTAssertEqualObjects(@"1", e.localizedDescription);
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_09_async {
id ex1 = [self expectationWithDescription:@""];
__block int x = 0;
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@1);
}].then(^{
XCTAssertEqual(x, 0);
x++;
}).then(^{
XCTAssertEqual(x, 1);
x++;
}).then(^{
XCTAssertEqual(x, 2);
x++;
}).then(^{
XCTAssertEqual(x, 3);
x++;
}).then(^{
XCTAssertEqual(x, 4);
x++;
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertEqual(x, 5);
}
- (void)test_10_then_returns_resolved_promise {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@10);
}].then(^(id o){
XCTAssertEqualObjects(@10, o);
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@100);
}];
}).then(^(id o){
XCTAssertEqualObjects(@100, o);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_11_then_returns_pending_promise {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@1);
}].then(^{
return fulfillLater();
}).then(^(id o){
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_12_then_returns_recursive_promises {
id ex1 = [self expectationWithDescription:@""];
id ex2 = [self expectationWithDescription:@""];
__block int x = 0;
fulfillLater().then(^{
NSLog(@"1");
XCTAssertEqual(x++, 0);
return fulfillLater().then(^{
NSLog(@"2");
XCTAssertEqual(x++, 1);
return fulfillLater().then(^{
NSLog(@"3");
XCTAssertEqual(x++, 2);
return fulfillLater().then(^{
NSLog(@"4");
XCTAssertEqual(x++, 3);
[ex2 fulfill];
return @"foo";
});
});
});
}).then(^(id o){
NSLog(@"5");
XCTAssertEqualObjects(@"foo", o);
XCTAssertEqual(x++, 4);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertEqual(x, 5);
}
- (void)test_13_then_returns_recursive_promises_that_fails {
id ex1 = [self expectationWithDescription:@""];
id ex2 = [self expectationWithDescription:@""];
fulfillLater().then(^{
return fulfillLater().then(^{
return fulfillLater().then(^{
return fulfillLater().then(^{
[ex2 fulfill];
return dummy();
});
});
});
}).then(^{
XCTFail();
}).catch(^(NSError *e){
XCTAssertEqualObjects(e.domain, PMKTestErrorDomain);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_14_fail_returns_value {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@1);
}].then(^{
return [NSError errorWithDomain:@"a" code:1 userInfo:nil];
}).catch(^(NSError *e){
XCTAssertEqual(e.code, 1);
return @2;
}).then(^(id o){
XCTAssertEqualObjects(o, @2);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_15_fail_returns_promise {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@1);
}].then(^{
return dummy();
}).catch(^{
return fulfillLater().then(^{
return @123;
});
}).then(^(id o){
XCTAssertEqualObjects(o, @123);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_23_add_another_fail_to_already_rejected {
id ex1 = [self expectationWithDescription:@""];
id ex2 = [self expectationWithDescription:@""];
PMKResolver resolve;
AnyPromise *promise = [[AnyPromise alloc] initWithResolver:&resolve];
promise.then(^{
XCTFail();
}).catch(^(NSError *e){
XCTAssertEqualObjects(e.localizedDescription, @"23");
[ex1 fulfill];
});
resolve(dummyWithCode(23));
promise.then(^{
XCTFail();
}).catch(^(NSError *e){
XCTAssertEqualObjects(e.localizedDescription, @"23");
[ex2 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_25_then_plus_deferred_plus_GCD {
id ex1 = [self expectationWithDescription:@""];
id ex2 = [self expectationWithDescription:@""];
id ex3 = [self expectationWithDescription:@""];
fulfillLater().then(^(id o){
[ex1 fulfill];
return fulfillLater().then(^{
return @YES;
});
}).then(^(id o){
XCTAssertEqualObjects(@YES, o);
[ex2 fulfill];
}).then(^(id o){
XCTAssertNil(o);
[ex3 fulfill];
}).catch(^{
XCTFail();
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_26_promise_then_promise_fail_promise_fail {
id ex1 = [self expectationWithDescription:@""];
fulfillLater().then(^{
return fulfillLater().then(^{
return dummy();
}).catch(^{
return fulfillLater().then(^{
return dummy();
});
});
}).then(^{
XCTFail();
}).catch(^{
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];}
- (void)test_27_eat_failure {
id ex1 = [self expectationWithDescription:@""];
fulfillLater().then(^{
return dummy();
}).catch(^{
return @YES;
}).then(^{
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_28_deferred_rejected_catch_promise {
id ex1 = [self expectationWithDescription:@""];
id ex2 = [self expectationWithDescription:@""];
rejectLater().catch(^{
[ex1 fulfill];
return fulfillLater();
}).then(^(id o){
[ex2 fulfill];
}).catch(^{
XCTFail();
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_29_deferred_rejected_catch_promise {
id ex1 = [self expectationWithDescription:@""];
id ex2 = [self expectationWithDescription:@""];
rejectLater().catch(^{
[ex1 fulfill];
return fulfillLater().then(^{
return dummy();
});
}).then(^{
XCTFail(@"1");
}).catch(^(NSError *error){
[ex2 fulfill];
}).catch(^{
XCTFail(@"2");
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_30_dispatch_returns_pending_promise {
id ex1 = [self expectationWithDescription:@""];
dispatch_promise(^{
return fulfillLater();
}).then(^{
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_31_dispatch_returns_promise {
id ex1 = [self expectationWithDescription:@""];
dispatch_promise(^{
return [AnyPromise promiseWithValue:@1];
}).then(^(id o){
XCTAssertEqualObjects(o, @1);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_32_return_primitive {
id ex1 = [self expectationWithDescription:@""];
__block void (^fulfiller)(id) = nil;
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
fulfiller = resolve;
}].then(^(id o){
XCTAssertEqualObjects(o, @32);
return 3;
}).then(^(id o){
XCTAssertEqualObjects(@3, o);
[ex1 fulfill];
});
fulfiller(@32);
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_33_return_nil {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithValue:@1].then(^(id o){
XCTAssertEqualObjects(o, @1);
return nil;
}).then(^{
return nil;
}).then(^(id o){
XCTAssertNil(o);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_33a_return_nil {
id ex1 = [self expectationWithDescription:@""];
id ex2 = [self expectationWithDescription:@""];
[AnyPromise promiseWithValue:@"HI"].then(^(id o){
XCTAssertEqualObjects(o, @"HI");
[ex1 fulfill];
return nil;
}).then(^{
return nil;
}).then(^{
[ex2 fulfill];
return nil;
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_36_promise_with_value_nil {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithValue:nil].then(^(id o){
XCTAssertNil(o);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_42 {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithValue:@1].then(^{
return fulfillLater();
}).then(^{
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_43_return_promise_from_itself {
id ex1 = [self expectationWithDescription:@""];
AnyPromise *p = fulfillLater().then(^{ return @1; });
p.then(^{
return p;
}).then(^{
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_44_reseal {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(@123);
resolve(@234);
}].then(^(id o){
XCTAssertEqualObjects(o, @123);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_46_test_then_on {
id ex1 = [self expectationWithDescription:@""];
dispatch_queue_t q1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t q2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[AnyPromise promiseWithValue:@1].thenOn(q1, ^{
XCTAssertFalse([NSThread isMainThread]);
return dispatch_get_current_queue();
}).thenOn(q2, ^(id q){
XCTAssertFalse([NSThread isMainThread]);
XCTAssertNotEqualObjects(q, dispatch_get_current_queue());
[ex1 fulfill];
});
#pragma clang diagnostic pop
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_47_finally_plus {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithValue:@1].then(^{
return @1;
}).ensure(^{
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_48_finally_negative {
@autoreleasepool {
id ex1 = [self expectationWithDescription:@"always"];
id ex2 = [self expectationWithDescription:@"errorUnhandler"];
[AnyPromise promiseWithValue:@1].then(^{
return dummy();
}).ensure(^{
[ex1 fulfill];
}).catch(^(NSError *err){
XCTAssertEqualObjects(err.domain, PMKTestErrorDomain);
[ex2 fulfill];
});
}
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_49_finally_negative_later {
id ex1 = [self expectationWithDescription:@""];
__block int x = 0;
[AnyPromise promiseWithValue:@1].then(^{
XCTAssertEqual(++x, 1);
return dummy();
}).catch(^{
XCTAssertEqual(++x, 2);
}).then(^{
XCTAssertEqual(++x, 3);
}).ensure(^{
XCTAssertEqual(++x, 4);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_50_fulfill_with_pending_promise {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(fulfillLater().then(^{ return @"HI"; }));
}].then(^(id hi){
XCTAssertEqualObjects(hi, @"HI");
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_51_fulfill_with_fulfilled_promise {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve([AnyPromise promiseWithValue:@1]);
}].then(^(id o){
XCTAssertEqualObjects(o, @1);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_52_fulfill_with_rejected_promise { //NEEDEDanypr
id ex1 = [self expectationWithDescription:@""];
fulfillLater().then(^{
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve([AnyPromise promiseWithValue:dummy()]);
}];
}).catch(^(NSError *err){
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_53_return_rejected_promise {
id ex1 = [self expectationWithDescription:@""];
fulfillLater().then(^{
return @1;
}).then(^{
return [AnyPromise promiseWithValue:dummy()];
}).catch(^{
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_54_reject_with_rejected_promise {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
id err = [NSError errorWithDomain:@"a" code:123 userInfo:nil];
resolve([AnyPromise promiseWithValue:err]);
}].catch(^(NSError *err){
XCTAssertEqual(err.code, 123);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_58_just_finally {
id ex1 = [self expectationWithDescription:@""];
AnyPromise *promise = fulfillLater().then(^{
return nil;
}).ensure(^{
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
id ex2 = [self expectationWithDescription:@""];
promise.ensure(^{
[ex2 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_59_catch_in_background {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
id err = [NSError errorWithDomain:@"a" code:123 userInfo:nil];
resolve(err);
}].catchInBackground(^(NSError *err){
XCTAssertEqual(err.code, 123);
XCTAssertFalse([NSThread isMainThread]);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_60_catch_on_specific_queue {
id ex1 = [self expectationWithDescription:@""];
NSString *expectedQueueName = @"specific queue 123";
dispatch_queue_t q = dispatch_queue_create(expectedQueueName.UTF8String, DISPATCH_QUEUE_SERIAL);
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
id err = [NSError errorWithDomain:@"a" code:123 userInfo:nil];
resolve(err);
}].catchOn(q, ^(NSError *err){
XCTAssertEqual(err.code, 123);
XCTAssertFalse([NSThread isMainThread]);
NSString *currentQueueName = [NSString stringWithFormat:@"%s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
XCTAssertEqualObjects(expectedQueueName, currentQueueName);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_properties {
XCTAssertEqualObjects([AnyPromise promiseWithValue:@1].value, @1);
XCTAssertEqualObjects([[AnyPromise promiseWithValue:dummyWithCode(2)].value localizedDescription], @"2");
XCTAssertNil([AnyPromise promiseWithResolverBlock:^(id a){}].value);
XCTAssertTrue([AnyPromise promiseWithResolverBlock:^(id a){}].pending);
XCTAssertFalse([AnyPromise promiseWithValue:@1].pending);
XCTAssertTrue([AnyPromise promiseWithValue:@1].fulfilled);
XCTAssertFalse([AnyPromise promiseWithValue:@1].rejected);
}
- (void)test_promiseWithValue {
XCTAssertEqual([AnyPromise promiseWithValue:@1].value, @1);
XCTAssertEqualObjects([[AnyPromise promiseWithValue:dummyWithCode(2)].value localizedDescription], @"2");
XCTAssertEqual([AnyPromise promiseWithValue:[AnyPromise promiseWithValue:@1]].value, @1);
}
- (void)test_race {
id ex = [self expectationWithDescription:@""];
id p = PMKAfter(0.1).then(^{ return @2; });
PMKRace(@[PMKAfter(0.2), PMKAfter(0.5), p]).then(^(id obj){
XCTAssertEqual(2, [obj integerValue]);
[ex fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)testInBackground {
id ex = [self expectationWithDescription:@""];
PMKAfter(0.1).thenInBackground(^{ [ex fulfill]; });
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)testEnsureOn {
id ex = [self expectationWithDescription:@""];
PMKAfter(0.1).ensureOn(dispatch_get_global_queue(0, 0), ^{ [ex fulfill]; });
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)testAdapterBlock {
void (^fetch)(PMKAdapter) = ^(PMKAdapter block){
block(@1, nil);
};
id ex = [self expectationWithDescription:@""];
[AnyPromise promiseWithAdapterBlock:fetch].then(^(id obj){
XCTAssertEqualObjects(obj, @1);
[ex fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)testIntegerAdapterBlock {
void (^fetch)(PMKIntegerAdapter) = ^(PMKIntegerAdapter block){
block(1, nil);
};
id ex = [self expectationWithDescription:@""];
[AnyPromise promiseWithIntegerAdapterBlock:fetch].then(^(id obj){
XCTAssertEqualObjects(obj, @1);
[ex fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)testBooleanAdapterBlock {
void (^fetch)(PMKBooleanAdapter) = ^(PMKBooleanAdapter block){
block(YES, nil);
};
id ex = [self expectationWithDescription:@""];
[AnyPromise promiseWithBooleanAdapterBlock:fetch].then(^(id obj){
XCTAssertEqualObjects(obj, @YES);
[ex fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
static NSHashTable *errorArray;
- (void)setUp {
[super setUp];
errorArray = [NSHashTable weakObjectsHashTable];
}
- (void)testErrorLeaks {
id ex1 = [self expectationWithDescription:@""];
NSError *error = dummyWithCode(1001);
[errorArray addObject:error];
[AnyPromise promiseWithValue:error]
.then(^{
XCTFail();
}).catch(^(NSError *e){
XCTAssertEqual(e.localizedDescription.intValue, 1001);
}).then(^{
NSError *err = dummyWithCode(1002);
[errorArray addObject:err];
return err;
}).catch(^(NSError *e){
XCTAssertEqual(e.localizedDescription.intValue, 1002);
}).then(^{
NSError *err = dummyWithCode(1003);
[errorArray addObject:err];
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve){
resolve(err);
}];
}).catch(^(NSError *e){
XCTAssertEqual(e.localizedDescription.intValue, 1003);
NSError *err = dummyWithCode(1004);
[errorArray addObject:err];
return err;
}).catch(^(NSError *e){
XCTAssertEqual(e.localizedDescription.intValue, 1004);
}).then(^{
NSError *err = dummyWithCode(1005);
[errorArray addObject:err];
// throw will lead to leak, if not use complie flag with "-fobjc-arc-exceptions"
@throw err;
}).catch(^(NSError *e){
XCTAssertEqual(e.localizedDescription.intValue, 1005);
}).ensure(^{
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)tearDown {
XCTAssertEqual(errorArray.allObjects.count, 0);
[super tearDown];
}
//- (void)test_nil_block {
// [AnyPromise promiseWithValue:@1].then(nil);
// [AnyPromise promiseWithValue:@1].thenOn(nil, nil);
// [AnyPromise promiseWithValue:@1].catch(nil);
// [AnyPromise promiseWithValue:@1].always(nil);
// [AnyPromise promiseWithValue:@1].alwaysOn(nil, nil);
//}
@end

View File

@@ -0,0 +1,38 @@
import PromiseKit
import XCTest
class AnyPromiseTests: XCTestCase {
func testFulfilledResult() {
switch AnyPromise(Promise.value(true)).result {
case .fulfilled(let obj as Bool)? where obj:
break
default:
XCTFail()
}
}
func testRejectedResult() {
switch AnyPromise(Promise<Int>(error: PMKError.badInput)).result {
case .rejected(let err)?:
print(err)
break
default:
XCTFail()
}
}
func testPendingResult() {
switch AnyPromise(Promise<Int>.pending().promise).result {
case nil:
break
default:
XCTFail()
}
}
func testCustomStringConvertible() {
XCTAssertEqual("\(AnyPromise(Promise<Int>.pending().promise))", "AnyPromise(…)")
XCTAssertEqual("\(AnyPromise(Promise.value(1)))", "AnyPromise(1)")
XCTAssertEqual("\(AnyPromise(Promise<Int?>.value(nil)))", "AnyPromise(nil)")
}
}

View File

@@ -0,0 +1,13 @@
@import PromiseKit;
@import XCTest;
@interface HangTests: XCTestCase @end @implementation HangTests
- (void)test {
__block int x = 0;
id value = PMKHang(PMKAfter(0.02).then(^{ x++; return 1; }));
XCTAssertEqual(x, 1);
XCTAssertEqualObjects(value, @1);
}
@end

View File

@@ -0,0 +1,90 @@
@import Foundation;
@import PromiseKit;
@import XCTest;
@interface JoinTests: XCTestCase @end @implementation JoinTests
- (void)test_73_join {
XCTestExpectation *ex1 = [self expectationWithDescription:@""];
__block void (^fulfiller)(id) = nil;
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
fulfiller = resolve;
}];
PMKJoin(@[
[AnyPromise promiseWithValue:[NSError errorWithDomain:@"dom" code:1 userInfo:nil]],
promise,
[AnyPromise promiseWithValue:[NSError errorWithDomain:@"dom" code:2 userInfo:nil]]
]).then(^{
XCTFail();
}).catch(^(NSError *error){
id promises = error.userInfo[PMKJoinPromisesKey];
int cume = 0, cumv = 0;
for (AnyPromise *promise in promises) {
if ([promise.value isKindOfClass:[NSError class]]) {
cume |= [promise.value code];
} else {
cumv |= [promise.value unsignedIntValue];
}
}
XCTAssertTrue(cumv == 4);
XCTAssertTrue(cume == 3);
[ex1 fulfill];
});
fulfiller(@4);
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_74_join_no_errors {
XCTestExpectation *ex1 = [self expectationWithDescription:@""];
PMKJoin(@[
[AnyPromise promiseWithValue:@1],
[AnyPromise promiseWithValue:@2]
]).then(^(NSArray *values, id errors) {
XCTAssertEqualObjects(values, (@[@1, @2]));
XCTAssertNil(errors);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_75_join_no_success {
XCTestExpectation *ex1 = [self expectationWithDescription:@""];
PMKJoin(@[
[AnyPromise promiseWithValue:[NSError errorWithDomain:@"dom" code:1 userInfo:nil]],
[AnyPromise promiseWithValue:[NSError errorWithDomain:@"dom" code:2 userInfo:nil]],
]).then(^{
XCTFail();
}).catch(^(NSError *error){
XCTAssertNotNil(error.userInfo[PMKJoinPromisesKey]);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_76_join_fulfills_if_empty_input {
XCTestExpectation *ex1 = [self expectationWithDescription:@""];
PMKJoin(@[]).then(^(id a, id b, id c){
XCTAssertEqualObjects(@[], a);
XCTAssertNil(b);
XCTAssertNil(c);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_join_nil {
NSArray *foo = nil;
NSError *err = PMKJoin(foo).value;
XCTAssertEqual(err.domain, PMKErrorDomain);
XCTAssertEqual(err.code, PMKInvalidUsageError);
}
@end

View File

@@ -0,0 +1,83 @@
@import PromiseKit;
@import XCTest;
@interface PMKManifoldTests: XCTestCase @end @implementation PMKManifoldTests
- (void)test_62_access_extra_elements {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
resolve(PMKManifold(@1));
}].then(^(id o, id m, id n){
XCTAssertNil(m, @"Accessing extra elements should not crash");
XCTAssertNil(n, @"Accessing extra elements should not crash");
XCTAssertEqualObjects(o, @1);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_63_then_manifold {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithValue:@0].then(^{
return PMKManifold(@1, @2, @3);
}).then(^(id o1, id o2, id o3){
XCTAssertEqualObjects(o1, @1);
XCTAssertEqualObjects(o2, @2);
XCTAssertEqualObjects(o3, @3);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_63_then_manifold_with_nil {
id ex1 = [self expectationWithDescription:@""];
[AnyPromise promiseWithValue:@0].then(^{
return PMKManifold(@1, nil, @3);
}).then(^(id o1, id o2, id o3){
XCTAssertEqualObjects(o1, @1);
XCTAssertEqualObjects(o2, nil);
XCTAssertEqualObjects(o3, @3);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_65_manifold_fulfill_value {
id ex1 = [self expectationWithDescription:@""];
AnyPromise *promise = [AnyPromise promiseWithValue:@1].then(^{
return PMKManifold(@123, @2);
});
promise.then(^(id a, id b){
XCTAssertNotNil(a);
XCTAssertNotNil(b);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertEqualObjects(promise.value, @123);
}
- (void)test_37_PMKMany_2 {
id ex1 = [self expectationWithDescription:@""];
PMKAfter(0.02).then(^{
return PMKManifold(@1, @2);
}).then(^(id a, id b){
XCTAssertEqualObjects(a, @1);
XCTAssertEqualObjects(b, @2);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
@end

View File

@@ -0,0 +1,265 @@
@import Foundation;
@import PromiseKit;
@import XCTest;
@interface WhenTests: XCTestCase @end @implementation WhenTests
- (void)testProgress {
id ex = [self expectationWithDescription:@""];
XCTAssertNil([NSProgress currentProgress]);
id p1 = PMKAfter(0.01);
id p2 = PMKAfter(0.02);
id p3 = PMKAfter(0.03);
id p4 = PMKAfter(0.04);
NSProgress *progress = [NSProgress progressWithTotalUnitCount:1];
[progress becomeCurrentWithPendingUnitCount:1];
PMKWhen(@[p1, p2, p3, p4]).then(^{
XCTAssertEqual(progress.completedUnitCount, 1);
[ex fulfill];
});
[progress resignCurrent];
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)testProgressDoesNotExceed100Percent {
id ex1 = [self expectationWithDescription:@""];
id ex2 = [self expectationWithDescription:@""];
XCTAssertNil([NSProgress currentProgress]);
id p1 = PMKAfter(0.01);
id p2 = PMKAfter(0.02).then(^{ return [NSError errorWithDomain:@"a" code:1 userInfo:nil]; });
id p3 = PMKAfter(0.03);
id p4 = PMKAfter(0.04);
id promises = @[p1, p2, p3, p4];
NSProgress *progress = [NSProgress progressWithTotalUnitCount:1];
[progress becomeCurrentWithPendingUnitCount:1];
PMKWhen(promises).catch(^{
[ex2 fulfill];
});
[progress resignCurrent];
PMKJoin(promises).catch(^{
XCTAssertLessThanOrEqual(1, progress.fractionCompleted);
XCTAssertEqual(progress.completedUnitCount, 1);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)testWhenManifolds {
id ex = [self expectationWithDescription:@""];
id p1 = dispatch_promise(^{ return PMKManifold(@1, @2); });
id p2 = dispatch_promise(^{});
PMKWhen(@[p1, p2]).then(^(NSArray *results){
XCTAssertEqualObjects(results[0], @1);
XCTAssertEqualObjects(results[1], [NSNull null]);
[ex fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_55_all_dictionary {
id ex1 = [self expectationWithDescription:@""];
id promises = @{
@1: @2,
@2: @"abc",
@"a": PMKAfter(0.1).then(^{ return @"HI"; })
};
PMKWhen(promises).then(^(NSDictionary *dict){
XCTAssertEqual(dict.count, 3ul);
XCTAssertEqualObjects(dict[@1], @2);
XCTAssertEqualObjects(dict[@2], @"abc");
XCTAssertEqualObjects(dict[@"a"], @"HI");
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_56_empty_array_when {
id ex1 = [self expectationWithDescription:@""];
PMKWhen(@[]).then(^(NSArray *array){
XCTAssertEqual(array.count, 0ul);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_57_empty_array_all {
id ex1 = [self expectationWithDescription:@""];
PMKWhen(@[]).then(^(NSArray *array){
XCTAssertEqual(array.count, 0ul);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_18_when {
id ex1 = [self expectationWithDescription:@""];
id a = PMKAfter(0.02).then(^{ return @345; });
id b = PMKAfter(0.03).then(^{ return @345; });
PMKWhen(@[a, b]).then(^(NSArray *objs){
XCTAssertEqual(objs.count, 2ul);
XCTAssertEqualObjects(objs[0], objs[1]);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_21_recursive_when {
id domain = @"sdjhfg";
id ex1 = [self expectationWithDescription:@""];
id a = PMKAfter(0.03).then(^{
return [NSError errorWithDomain:domain code:123 userInfo:nil];
});
id b = PMKAfter(0.02);
id c = PMKWhen(@[a, b]);
PMKWhen(c).then(^{
XCTFail();
}).catch(^(NSError *e){
XCTAssertEqualObjects(e.userInfo[PMKFailingPromiseIndexKey], @0);
XCTAssertEqualObjects(e.domain, domain);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_22_already_resolved_and_bubble {
id ex1 = [self expectationWithDescription:@""];
id ex2 = [self expectationWithDescription:@""];
PMKResolver resolve;
AnyPromise *promise = [[AnyPromise alloc] initWithResolver:&resolve];
promise.then(^{
XCTFail();
}).catch(^(NSError *e){
[ex1 fulfill];
});
resolve([NSError errorWithDomain:@"a" code:1 userInfo:nil]);
PMKWhen(promise).then(^{
XCTFail();
}).catch(^{
[ex2 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_24_some_edge_case {
id ex1 = [self expectationWithDescription:@""];
id a = PMKAfter(0.02).catch(^{});
id b = PMKAfter(0.03);
PMKWhen(@[a, b]).then(^(NSArray *objs){
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_35_when_nil {
id ex1 = [self expectationWithDescription:@""];
AnyPromise *promise = [AnyPromise promiseWithValue:@"35"].then(^{ return nil; });
PMKWhen(@[PMKAfter(0.02).then(^{ return @1; }), [AnyPromise promiseWithValue:nil], promise]).then(^(NSArray *results){
XCTAssertEqual(results.count, 3ul);
XCTAssertEqualObjects(results[1], [NSNull null]);
[ex1 fulfill];
}).catch(^(NSError *err){
abort();
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_39_when_with_some_values {
id ex1 = [self expectationWithDescription:@""];
id p = PMKAfter(0.02);
id v = @1;
PMKWhen(@[p, v]).then(^(NSArray *aa){
XCTAssertEqual(aa.count, 2ul);
XCTAssertEqualObjects(aa[1], @1);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_40_when_with_all_values {
id ex1 = [self expectationWithDescription:@""];
PMKWhen(@[@1, @2]).then(^(NSArray *aa){
XCTAssertEqualObjects(aa[0], @1);
XCTAssertEqualObjects(aa[1], @2);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_41_when_with_repeated_promises {
id ex1 = [self expectationWithDescription:@""];
id p = PMKAfter(0.02);
id v = @1;
PMKWhen(@[p, v, p, v]).then(^(NSArray *aa){
XCTAssertEqual(aa.count, 4ul);
XCTAssertEqualObjects(aa[1], @1);
XCTAssertEqualObjects(aa[3], @1);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_45_when_which_returns_void {
id ex1 = [self expectationWithDescription:@""];
AnyPromise *promise = [AnyPromise promiseWithValue:@1].then(^{});
PMKWhen(@[promise, [AnyPromise promiseWithValue:@1]]).then(^(NSArray *stuff){
XCTAssertEqual(stuff.count, 2ul);
XCTAssertEqualObjects(stuff[0], [NSNull null]);
[ex1 fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)test_when_nil {
NSArray *foo = nil;
NSError *err = PMKWhen(foo).value;
XCTAssertEqual(err.domain, PMKErrorDomain);
XCTAssertEqual(err.code, PMKInvalidUsageError);
}
- (void)test_when_bad_input {
id foo = @"a";
XCTAssertEqual(PMKWhen(foo).value, foo);
}
@end

View File

@@ -0,0 +1,52 @@
import PromiseKit
import XCTest
class AfterTests: XCTestCase {
func testZero() {
let ex2 = expectation(description: "")
after(seconds: 0).done(ex2.fulfill)
waitForExpectations(timeout: 2, handler: nil)
let ex3 = expectation(description: "")
after(.seconds(0)).done(ex3.fulfill)
waitForExpectations(timeout: 2, handler: nil)
#if !SWIFT_PACKAGE
let ex4 = expectation(description: "")
__PMKAfter(0).done{ _ in ex4.fulfill() }.silenceWarning()
waitForExpectations(timeout: 2, handler: nil)
#endif
}
func testNegative() {
let ex2 = expectation(description: "")
after(seconds: -1).done(ex2.fulfill)
waitForExpectations(timeout: 2, handler: nil)
let ex3 = expectation(description: "")
after(.seconds(-1)).done(ex3.fulfill)
waitForExpectations(timeout: 2, handler: nil)
#if !SWIFT_PACKAGE
let ex4 = expectation(description: "")
__PMKAfter(-1).done{ _ in ex4.fulfill() }.silenceWarning()
waitForExpectations(timeout: 2, handler: nil)
#endif
}
func testPositive() {
let ex2 = expectation(description: "")
after(seconds: 1).done(ex2.fulfill)
waitForExpectations(timeout: 2, handler: nil)
let ex3 = expectation(description: "")
after(.seconds(1)).done(ex3.fulfill)
waitForExpectations(timeout: 2, handler: nil)
#if !SWIFT_PACKAGE
let ex4 = expectation(description: "")
__PMKAfter(1).done{ _ in ex4.fulfill() }.silenceWarning()
waitForExpectations(timeout: 2, handler: nil)
#endif
}
}

View File

@@ -0,0 +1,130 @@
import Foundation
import PromiseKit
import XCTest
class CancellationTests: XCTestCase {
func testCancellation() {
let ex1 = expectation(description: "")
let p = after(seconds: 0).done { _ in
throw LocalError.cancel
}.done {
XCTFail()
}
p.catch { _ in
XCTFail()
}
p.catch(policy: .allErrors) {
XCTAssertTrue($0.isCancelled)
ex1.fulfill()
}
waitForExpectations(timeout: 60)
}
func testThrowCancellableErrorThatIsNotCancelled() {
let expct = expectation(description: "")
after(seconds: 0).done { _ in
throw LocalError.notCancel
}.done {
XCTFail()
}.catch {
XCTAssertFalse($0.isCancelled)
expct.fulfill()
}
waitForExpectations(timeout: 1)
}
func testRecoverWithCancellation() {
let ex1 = expectation(description: "")
let ex2 = expectation(description: "")
let p = after(seconds: 0).done { _ in
throw CocoaError.cancelled
}.recover(policy: .allErrors) { err -> Promise<Void> in
ex1.fulfill()
XCTAssertTrue(err.isCancelled)
throw err
}.done { _ in
XCTFail()
}
p.catch { _ in
XCTFail()
}
p.catch(policy: .allErrors) {
XCTAssertTrue($0.isCancelled)
ex2.fulfill()
}
waitForExpectations(timeout: 1)
}
func testFoundationBridging1() {
let ex = expectation(description: "")
let p = after(seconds: 0).done { _ in
throw CocoaError.cancelled
}
p.catch { _ in
XCTFail()
}
p.catch(policy: .allErrors) {
XCTAssertTrue($0.isCancelled)
ex.fulfill()
}
waitForExpectations(timeout: 1)
}
func testFoundationBridging2() {
let ex = expectation(description: "")
let p = Promise().done {
throw URLError.cancelled
}
p.catch { _ in
XCTFail()
}
p.catch(policy: .allErrors) {
XCTAssertTrue($0.isCancelled)
ex.fulfill()
}
waitForExpectations(timeout: 1)
}
#if swift(>=3.2)
func testIsCancelled() {
XCTAssertTrue(PMKError.cancelled.isCancelled)
XCTAssertTrue(URLError.cancelled.isCancelled)
XCTAssertTrue(CocoaError.cancelled.isCancelled)
XCTAssertFalse(CocoaError(_nsError: NSError(domain: NSCocoaErrorDomain, code: CocoaError.Code.coderInvalidValue.rawValue)).isCancelled)
}
#endif
}
private enum LocalError: CancellableError {
case notCancel
case cancel
var isCancelled: Bool {
switch self {
case .notCancel: return false
case .cancel: return true
}
}
}
private extension URLError {
static var cancelled: URLError {
return .init(_nsError: NSError(domain: NSURLErrorDomain, code: URLError.Code.cancelled.rawValue))
}
}
private extension CocoaError {
static var cancelled: CocoaError {
return .init(_nsError: NSError(domain: NSCocoaErrorDomain, code: CocoaError.Code.userCancelled.rawValue))
}
}

View File

@@ -0,0 +1,267 @@
import PromiseKit
import Dispatch
import XCTest
class CatchableTests: XCTestCase {
func testFinally() {
let finallyQueue = DispatchQueue(label: "\(#file):\(#line)", attributes: .concurrent)
func helper(error: Error, on queue: DispatchQueue = .main, flags: DispatchWorkItemFlags? = nil) {
let ex = (expectation(description: ""), expectation(description: ""))
var x = 0
Promise<Void>(error: error).catch(policy: .allErrors) { _ in
XCTAssertEqual(x, 0)
x += 1
ex.0.fulfill()
}.finally(on: queue, flags: flags) {
if let flags = flags, flags.contains(.barrier) {
dispatchPrecondition(condition: .onQueueAsBarrier(queue))
} else {
dispatchPrecondition(condition: .onQueue(queue))
}
XCTAssertEqual(x, 1)
x += 1
ex.1.fulfill()
}
wait(for: [ex.0, ex.1], timeout: 10)
}
helper(error: Error.dummy)
helper(error: Error.cancelled)
helper(error: Error.dummy, on: finallyQueue)
helper(error: Error.dummy, on: finallyQueue, flags: .barrier)
}
func testCauterize() {
let ex = expectation(description: "")
let p = Promise<Void>(error: Error.dummy)
// cannot test specifically that this outputs to console,
// but code-coverage will note that the line is run
p.cauterize()
p.catch { _ in
ex.fulfill()
}
wait(for: [ex], timeout: 1)
}
}
/// `Promise<Void>.recover`
extension CatchableTests {
func test__void_specialized_full_recover() {
func helper(error: Swift.Error) {
let ex = expectation(description: "")
Promise<Void>(error: error).recover { _ in }.done(ex.fulfill)
wait(for: [ex], timeout: 10)
}
helper(error: Error.dummy)
helper(error: Error.cancelled)
}
func test__void_specialized_full_recover__fulfilled_path() {
let ex = expectation(description: "")
Promise().recover { _ in XCTFail() }.done(ex.fulfill)
wait(for: [ex], timeout: 10)
}
func test__void_specialized_conditional_recover() {
func helper(policy: CatchPolicy, error: Swift.Error, line: UInt = #line) {
let ex = expectation(description: "")
var x = 0
Promise<Void>(error: error).recover(policy: policy) { err in
guard x < 1 else { throw err }
x += 1
}.done(ex.fulfill).silenceWarning()
wait(for: [ex], timeout: 10)
}
for error in [Error.dummy as Swift.Error, Error.cancelled] {
helper(policy: .allErrors, error: error)
}
helper(policy: .allErrorsExceptCancellation, error: Error.dummy)
}
func test__void_specialized_conditional_recover__no_recover() {
func helper(policy: CatchPolicy, error: Error, line: UInt = #line) {
let ex = expectation(description: "")
Promise<Void>(error: error).recover(policy: policy) { err in
throw err
}.catch(policy: .allErrors) {
XCTAssertEqual(error, $0 as? Error)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
for error in [Error.dummy, Error.cancelled] {
helper(policy: .allErrors, error: error)
}
helper(policy: .allErrorsExceptCancellation, error: Error.dummy)
}
func test__void_specialized_conditional_recover__ignores_cancellation_but_fed_cancellation() {
let ex = expectation(description: "")
Promise<Void>(error: Error.cancelled).recover(policy: .allErrorsExceptCancellation) { _ in
XCTFail()
}.catch(policy: .allErrors) {
XCTAssertEqual(Error.cancelled, $0 as? Error)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func test__void_specialized_conditional_recover__fulfilled_path() {
let ex = expectation(description: "")
Promise().recover { _ in
XCTFail()
}.catch { _ in
XCTFail() // this `catch` to ensure we are calling the `recover` variant we think we are
}.finally {
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
}
/// `Promise<T>.recover`
extension CatchableTests {
func test__full_recover() {
func helper(error: Swift.Error) {
let ex = expectation(description: "")
Promise<Int>(error: error).recover { _ in return .value(2) }.done {
XCTAssertEqual($0, 2)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
helper(error: Error.dummy)
helper(error: Error.cancelled)
}
func test__full_recover__fulfilled_path() {
let ex = expectation(description: "")
Promise.value(1).recover { _ in XCTFail(); return .value(2) }.done{
XCTAssertEqual($0, 1)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func test__conditional_recover() {
func helper(policy: CatchPolicy, error: Swift.Error, line: UInt = #line) {
let ex = expectation(description: "")
var x = 0
Promise<Int>(error: error).recover(policy: policy) { err -> Promise<Int> in
guard x < 1 else { throw err }
x += 1
return .value(x)
}.done {
XCTAssertEqual($0, x)
ex.fulfill()
}.silenceWarning()
wait(for: [ex], timeout: 10)
}
for error in [Error.dummy as Swift.Error, Error.cancelled] {
helper(policy: .allErrors, error: error)
}
helper(policy: .allErrorsExceptCancellation, error: Error.dummy)
}
func test__conditional_recover__no_recover() {
func helper(policy: CatchPolicy, error: Error, line: UInt = #line) {
let ex = expectation(description: "")
Promise<Int>(error: error).recover(policy: policy) { err -> Promise<Int> in
throw err
}.catch(policy: .allErrors) {
XCTAssertEqual(error, $0 as? Error)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
for error in [Error.dummy, Error.cancelled] {
helper(policy: .allErrors, error: error)
}
helper(policy: .allErrorsExceptCancellation, error: Error.dummy)
}
func test__conditional_recover__ignores_cancellation_but_fed_cancellation() {
let ex = expectation(description: "")
Promise<Int>(error: Error.cancelled).recover(policy: .allErrorsExceptCancellation) { _ -> Promise<Int> in
XCTFail()
return .value(1)
}.catch(policy: .allErrors) {
XCTAssertEqual(Error.cancelled, $0 as? Error)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func test__conditional_recover__fulfilled_path() {
let ex = expectation(description: "")
Promise.value(1).recover { err -> Promise<Int> in
XCTFail()
throw err
}.done {
XCTAssertEqual($0, 1)
ex.fulfill()
}.catch { _ in
XCTFail() // this `catch` to ensure we are calling the `recover` variant we think we are
}
wait(for: [ex], timeout: 10)
}
func testEnsureThen_Error() {
let ex = expectation(description: "")
Promise.value(1).done {
XCTAssertEqual($0, 1)
throw Error.dummy
}.ensureThen {
after(seconds: 0.01)
}.catch {
XCTAssertEqual(Error.dummy, $0 as? Error)
}.finally {
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testEnsureThen_Value() {
let ex = expectation(description: "")
Promise.value(1).ensureThen {
after(seconds: 0.01)
}.done {
XCTAssertEqual($0, 1)
}.catch { _ in
XCTFail()
}.finally {
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
}
private enum Error: CancellableError {
case dummy
case cancelled
var isCancelled: Bool {
return self == Error.cancelled
}
}

View File

@@ -0,0 +1,70 @@
//
// PMKDefaultDispatchQueue.test.swift
// PromiseKit
//
// Created by David Rodriguez on 4/14/16.
// Copyright © 2016 Max Howell. All rights reserved.
//
import class Foundation.Thread
import PromiseKit
import Dispatch
import XCTest
private enum Error: Swift.Error { case dummy }
class PMKDefaultDispatchQueueTest: XCTestCase {
let myQueue = DispatchQueue(label: "myQueue")
override func setUp() {
// can actually only set the default queue once
// - See: PMKSetDefaultDispatchQueue
conf.Q = (myQueue, myQueue)
}
override func tearDown() {
conf.Q = (.main, .main)
}
func testOverrodeDefaultThenQueue() {
let ex = expectation(description: "resolving")
Promise.value(1).then { _ -> Promise<Void> in
ex.fulfill()
XCTAssertFalse(Thread.isMainThread)
return Promise()
}.silenceWarning()
XCTAssertTrue(Thread.isMainThread)
waitForExpectations(timeout: 1)
}
func testOverrodeDefaultCatchQueue() {
let ex = expectation(description: "resolving")
Promise<Int>(error: Error.dummy).catch { _ in
ex.fulfill()
XCTAssertFalse(Thread.isMainThread)
}
XCTAssertTrue(Thread.isMainThread)
waitForExpectations(timeout: 1)
}
func testOverrodeDefaultAlwaysQueue() {
let ex = expectation(description: "resolving")
Promise.value(1).ensure {
ex.fulfill()
XCTAssertFalse(Thread.isMainThread)
}.silenceWarning()
XCTAssertTrue(Thread.isMainThread)
waitForExpectations(timeout: 1)
}
}

View File

@@ -0,0 +1,22 @@
import PromiseKit
import XCTest
class PMKErrorTests: XCTestCase {
func testCustomStringConvertible() {
XCTAssertNotNil(PMKError.invalidCallingConvention.errorDescription)
XCTAssertNotNil(PMKError.returnedSelf.errorDescription)
XCTAssertNotNil(PMKError.badInput.errorDescription)
XCTAssertNotNil(PMKError.cancelled.errorDescription)
XCTAssertNotNil(PMKError.compactMap(1, Int.self).errorDescription)
XCTAssertNotNil(PMKError.emptySequence.errorDescription)
}
func testCustomDebugStringConvertible() {
XCTAssertFalse(PMKError.invalidCallingConvention.debugDescription.isEmpty)
XCTAssertFalse(PMKError.returnedSelf.debugDescription.isEmpty)
XCTAssertNotNil(PMKError.badInput.debugDescription.isEmpty)
XCTAssertFalse(PMKError.cancelled.debugDescription.isEmpty)
XCTAssertFalse(PMKError.compactMap(1, Int.self).debugDescription.isEmpty)
XCTAssertFalse(PMKError.emptySequence.debugDescription.isEmpty)
}
}

View File

@@ -0,0 +1,33 @@
import PromiseKit
import XCTest
class GuaranteeTests: XCTestCase {
func testInit() {
let ex = expectation(description: "")
Guarantee { seal in
seal(1)
}.done {
XCTAssertEqual(1, $0)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testWait() {
XCTAssertEqual(after(.milliseconds(100)).map(on: nil){ 1 }.wait(), 1)
}
func testThenMap() {
let ex = expectation(description: "")
Guarantee.value([1, 2, 3])
.thenMap { Guarantee.value($0 * 2) }
.done { values in
XCTAssertEqual([2, 4, 6], values)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
}

View File

@@ -0,0 +1,38 @@
import PromiseKit
import XCTest
class HangTests: XCTestCase {
func test() {
let ex = expectation(description: "block executed")
do {
let value = try hang(after(seconds: 0.02).then { _ -> Promise<Int> in
ex.fulfill()
return .value(1)
})
XCTAssertEqual(value, 1)
} catch {
XCTFail("Unexpected error")
}
waitForExpectations(timeout: 0)
}
enum Error: Swift.Error {
case test
}
func testError() {
var value = 0
do {
_ = try hang(after(seconds: 0.02).done {
value = 1
throw Error.test
})
XCTAssertEqual(value, 1)
} catch Error.test {
return
} catch {
XCTFail("Unexpected error (expected Error.test)")
}
XCTFail("Expected error but no error was thrown")
}
}

View File

@@ -0,0 +1,139 @@
import PromiseKit
import Dispatch
import XCTest
class PromiseTests: XCTestCase {
func testIsPending() {
XCTAssertTrue(Promise<Void>.pending().promise.isPending)
XCTAssertFalse(Promise().isPending)
XCTAssertFalse(Promise<Void>(error: Error.dummy).isPending)
}
func testIsResolved() {
XCTAssertFalse(Promise<Void>.pending().promise.isResolved)
XCTAssertTrue(Promise().isResolved)
XCTAssertTrue(Promise<Void>(error: Error.dummy).isResolved)
}
func testIsFulfilled() {
XCTAssertFalse(Promise<Void>.pending().promise.isFulfilled)
XCTAssertTrue(Promise().isFulfilled)
XCTAssertFalse(Promise<Void>(error: Error.dummy).isFulfilled)
}
func testIsRejected() {
XCTAssertFalse(Promise<Void>.pending().promise.isRejected)
XCTAssertTrue(Promise<Void>(error: Error.dummy).isRejected)
XCTAssertFalse(Promise().isRejected)
}
@available(macOS 10.10, iOS 2.0, tvOS 10.0, watchOS 2.0, *)
func testDispatchQueueAsyncExtensionReturnsPromise() {
let ex = expectation(description: "")
DispatchQueue.global().async(.promise) { () -> Int in
XCTAssertFalse(Thread.isMainThread)
return 1
}.done { one in
XCTAssertEqual(one, 1)
ex.fulfill()
}
waitForExpectations(timeout: 1)
}
@available(macOS 10.10, iOS 2.0, tvOS 10.0, watchOS 2.0, *)
func testDispatchQueueAsyncExtensionCanThrowInBody() {
let ex = expectation(description: "")
DispatchQueue.global().async(.promise) { () -> Int in
throw Error.dummy
}.done { _ in
XCTFail()
}.catch { _ in
ex.fulfill()
}
waitForExpectations(timeout: 1)
}
func testCustomStringConvertible() {
XCTAssertEqual(Promise<Int>.pending().promise.debugDescription, "Promise<Int>.pending(handlers: 0)")
XCTAssertEqual(Promise().debugDescription, "Promise<()>.fulfilled(())")
XCTAssertEqual(Promise<String>(error: Error.dummy).debugDescription, "Promise<String>.rejected(Error.dummy)")
XCTAssertEqual("\(Promise<Int>.pending().promise)", "Promise(…Int)")
XCTAssertEqual("\(Promise.value(3))", "Promise(3)")
XCTAssertEqual("\(Promise<Void>(error: Error.dummy))", "Promise(dummy)")
}
func testCannotFulfillWithError() {
// sadly this test proves the opposite :(
// left here so maybe one day we can prevent instantiation of `Promise<Error>`
_ = Promise { seal in
seal.fulfill(Error.dummy)
}
_ = Promise<Error>.pending()
_ = Promise.value(Error.dummy)
_ = Promise().map { Error.dummy }
}
#if swift(>=3.1)
func testCanMakeVoidPromise() {
_ = Promise()
_ = Guarantee()
}
#endif
enum Error: Swift.Error {
case dummy
}
func testThrowInInitializer() {
let p = Promise<Void> { _ in
throw Error.dummy
}
XCTAssertTrue(p.isRejected)
guard let err = p.error, case Error.dummy = err else { return XCTFail() }
}
func testThrowInFirstly() {
let ex = expectation(description: "")
firstly { () -> Promise<Int> in
throw Error.dummy
}.catch {
XCTAssertEqual($0 as? Error, Error.dummy)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testWait() throws {
let p = after(.milliseconds(100)).then(on: nil){ Promise.value(1) }
XCTAssertEqual(try p.wait(), 1)
do {
let p = after(.milliseconds(100)).map(on: nil){ throw Error.dummy }
try p.wait()
XCTFail()
} catch {
XCTAssertEqual(error as? Error, Error.dummy)
}
}
func testPipeForResolved() {
let ex = expectation(description: "")
Promise.value(1).done {
XCTAssertEqual(1, $0)
ex.fulfill()
}.silenceWarning()
wait(for: [ex], timeout: 10)
}
}

View File

@@ -0,0 +1,51 @@
import XCTest
import PromiseKit
class RaceTests: XCTestCase {
func test1() {
let ex = expectation(description: "")
race(after(.milliseconds(10)).then{ Promise.value(1) }, after(seconds: 1).map{ 2 }).done { index in
XCTAssertEqual(index, 1)
ex.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1, handler: nil)
}
func test2() {
let ex = expectation(description: "")
race(after(seconds: 1).map{ 1 }, after(.milliseconds(10)).map{ 2 }).done { index in
XCTAssertEqual(index, 2)
ex.fulfill()
}
waitForExpectations(timeout: 1, handler: nil)
}
func test1Array() {
let ex = expectation(description: "")
let promises = [after(.milliseconds(10)).map{ 1 }, after(seconds: 1).map{ 2 }]
race(promises).done { index in
XCTAssertEqual(index, 1)
ex.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1, handler: nil)
}
func test2Array() {
let ex = expectation(description: "")
race(after(seconds: 1).map{ 1 }, after(.milliseconds(10)).map{ 2 }).done { index in
XCTAssertEqual(index, 2)
ex.fulfill()
}
waitForExpectations(timeout: 1, handler: nil)
}
func testEmptyArray() {
let ex = expectation(description: "")
let empty = [Promise<Int>]()
race(empty).catch {
guard case PMKError.badInput = $0 else { return XCTFail() }
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
}

View File

@@ -0,0 +1,27 @@
import PromiseKit
import XCTest
class RegressionTests: XCTestCase {
func testReturningPreviousPromiseWorks() {
// regression test because we were doing this wrong
// in our A+ tests implementation for spec: 2.3.1
do {
let promise1 = Promise()
let promise2 = promise1.then(on: nil) { promise1 }
promise2.catch(on: nil) { _ in XCTFail() }
}
do {
enum Error: Swift.Error { case dummy }
let promise1 = Promise<Void>(error: Error.dummy)
let promise2 = promise1.recover(on: nil) { _ in promise1 }
promise2.catch(on: nil) { err in
if case PMKError.returnedSelf = err {
XCTFail()
}
}
}
}
}

View File

@@ -0,0 +1,166 @@
import PromiseKit
import XCTest
class WrapTests: XCTestCase {
fileprivate class KittenFetcher {
let value: Int?
let error: Error?
init(value: Int?, error: Error?) {
self.value = value
self.error = error
}
func fetchWithCompletionBlock(block: @escaping(Int?, Error?) -> Void) {
after(.milliseconds(20)).done {
block(self.value, self.error)
}
}
func fetchWithCompletionBlock2(block: @escaping(Error?, Int?) -> Void) {
after(.milliseconds(20)).done {
block(self.error, self.value)
}
}
func fetchWithCompletionBlock3(block: @escaping(Int, Error?) -> Void) {
after(.milliseconds(20)).done {
block(self.value ?? -99, self.error)
}
}
func fetchWithCompletionBlock4(block: @escaping(Error?) -> Void) {
after(.milliseconds(20)).done {
block(self.error)
}
}
}
func testSuccess() {
let ex = expectation(description: "")
let kittenFetcher = KittenFetcher(value: 2, error: nil)
Promise { seal in
kittenFetcher.fetchWithCompletionBlock(block: seal.resolve)
}.done {
XCTAssertEqual($0, 2)
ex.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1)
}
func testError() {
let ex = expectation(description: "")
let kittenFetcher = KittenFetcher(value: nil, error: Error.test)
Promise { seal in
kittenFetcher.fetchWithCompletionBlock(block: seal.resolve)
}.catch { error in
defer { ex.fulfill() }
guard case Error.test = error else {
return XCTFail()
}
}
waitForExpectations(timeout: 1)
}
func testInvalidCallingConvention() {
let ex = expectation(description: "")
let kittenFetcher = KittenFetcher(value: nil, error: nil)
Promise { seal in
kittenFetcher.fetchWithCompletionBlock(block: seal.resolve)
}.catch { error in
defer { ex.fulfill() }
guard case PMKError.invalidCallingConvention = error else {
return XCTFail()
}
}
waitForExpectations(timeout: 1)
}
func testInvertedCallingConvention() {
let ex = expectation(description: "")
let kittenFetcher = KittenFetcher(value: 2, error: nil)
Promise { seal in
kittenFetcher.fetchWithCompletionBlock2(block: seal.resolve)
}.done {
XCTAssertEqual($0, 2)
ex.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1)
}
func testNonOptionalFirstParameter() {
let ex1 = expectation(description: "")
let kf1 = KittenFetcher(value: 2, error: nil)
Promise { seal in
kf1.fetchWithCompletionBlock3(block: seal.resolve)
}.done {
XCTAssertEqual($0, 2)
ex1.fulfill()
}.silenceWarning()
let ex2 = expectation(description: "")
let kf2 = KittenFetcher(value: -100, error: Error.test)
Promise { seal in
kf2.fetchWithCompletionBlock3(block: seal.resolve)
}.catch { _ in ex2.fulfill() }
wait(for: [ex1, ex2] ,timeout: 1)
}
#if swift(>=3.1)
func testVoidCompletionValue() {
let ex1 = expectation(description: "")
let kf1 = KittenFetcher(value: nil, error: nil)
Promise { seal in
kf1.fetchWithCompletionBlock4(block: seal.resolve)
}.done(ex1.fulfill).silenceWarning()
let ex2 = expectation(description: "")
let kf2 = KittenFetcher(value: nil, error: Error.test)
Promise { seal in
kf2.fetchWithCompletionBlock4(block: seal.resolve)
}.catch { _ in ex2.fulfill() }
wait(for: [ex1, ex2], timeout: 1)
}
#endif
func testIsFulfilled() {
XCTAssertTrue(Promise.value(()).result?.isFulfilled ?? false)
XCTAssertFalse(Promise<Int>(error: Error.test).result?.isFulfilled ?? true)
}
func testPendingPromiseDeallocated() {
// NOTE this doesn't seem to register the `deinit` as covered :(
// BUT putting a breakpoint in the deinit CLEARLY shows it getting covered
class Foo {
let p = Promise<Void>.pending()
var ex: XCTestExpectation!
deinit {
after(.milliseconds(100)).done(ex.fulfill)
}
}
let ex = expectation(description: "")
do {
// for code coverage report for `Resolver.deinit` warning
let foo = Foo()
foo.ex = ex
}
wait(for: [ex], timeout: 10)
}
}
private enum Error: Swift.Error {
case test
}

View File

@@ -0,0 +1,78 @@
import PromiseKit
import Dispatch
import XCTest
class StressTests: XCTestCase {
func testThenDataRace() {
let e1 = expectation(description: "")
//will crash if then doesn't protect handlers
stressDataRace(expectation: e1, stressFunction: { promise in
promise.done { s in
XCTAssertEqual("ok", s)
return
}.silenceWarning()
}, fulfill: { "ok" })
waitForExpectations(timeout: 10, handler: nil)
}
@available(macOS 10.10, iOS 2.0, tvOS 10.0, watchOS 2.0, *)
func testThensAreSequentialForLongTime() {
var values = [Int]()
let ex = expectation(description: "")
var promise = DispatchQueue.global().async(.promise){ 0 }
let N = 1000
for x in 1..<N {
promise = promise.then { y -> Guarantee<Int> in
values.append(y)
XCTAssertEqual(x - 1, y)
return DispatchQueue.global().async(.promise) { x }
}
}
promise.done { x in
values.append(x)
XCTAssertEqual(values, (0..<N).map{ $0 })
ex.fulfill()
}
waitForExpectations(timeout: 10, handler: nil)
}
func testZalgoDataRace() {
let e1 = expectation(description: "")
//will crash if zalgo doesn't protect handlers
stressDataRace(expectation: e1, stressFunction: { promise in
promise.done(on: nil) { s in
XCTAssertEqual("ok", s)
}.silenceWarning()
}, fulfill: {
return "ok"
})
waitForExpectations(timeout: 10, handler: nil)
}
}
private enum Error: Swift.Error {
case Dummy
}
private func stressDataRace<T: Equatable>(expectation e1: XCTestExpectation, iterations: Int = 1000, stressFactor: Int = 10, stressFunction: @escaping (Promise<T>) -> Void, fulfill f: @escaping () -> T) {
let group = DispatchGroup()
let queue = DispatchQueue(label: "the.domain.of.Zalgo", attributes: .concurrent)
for _ in 0..<iterations {
let (promise, seal) = Promise<T>.pending()
DispatchQueue.concurrentPerform(iterations: stressFactor) { n in
stressFunction(promise)
}
queue.async(group: group) {
seal.fulfill(f())
}
}
group.notify(queue: queue, execute: e1.fulfill)
}

View File

@@ -0,0 +1,155 @@
import PromiseKit
import Dispatch
import XCTest
class ThenableTests: XCTestCase {
func testGet() {
let ex1 = expectation(description: "")
let ex2 = expectation(description: "")
Promise.value(1).get {
XCTAssertEqual($0, 1)
ex1.fulfill()
}.done {
XCTAssertEqual($0, 1)
ex2.fulfill()
}.silenceWarning()
wait(for: [ex1, ex2], timeout: 10)
}
func testCompactMap() {
let ex = expectation(description: "")
Promise.value(1.0).compactMap {
Int($0)
}.done {
XCTAssertEqual($0, 1)
ex.fulfill()
}.silenceWarning()
wait(for: [ex], timeout: 10)
}
func testCompactMapThrows() {
enum E: Error { case dummy }
let ex = expectation(description: "")
Promise.value("a").compactMap { x -> Int in
throw E.dummy
}.catch {
if case E.dummy = $0 {} else {
XCTFail()
}
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testRejectedPromiseCompactMap() {
enum E: Error { case dummy }
let ex = expectation(description: "")
Promise(error: E.dummy).compactMap {
Int($0)
}.catch {
if case E.dummy = $0 {} else {
XCTFail()
}
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testPMKErrorCompactMap() {
let ex = expectation(description: "")
Promise.value("a").compactMap {
Int($0)
}.catch {
if case PMKError.compactMap = $0 {} else {
XCTFail()
}
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testCompactMapValues() {
let ex = expectation(description: "")
Promise.value(["1","2","a","4"]).compactMapValues {
Int($0)
}.done {
XCTAssertEqual([1,2,4], $0)
ex.fulfill()
}.silenceWarning()
wait(for: [ex], timeout: 10)
}
func testThenMap() {
let ex = expectation(description: "")
Promise.value([1,2,3,4]).thenMap {
Promise.value($0)
}.done {
XCTAssertEqual([1,2,3,4], $0)
ex.fulfill()
}.silenceWarning()
wait(for: [ex], timeout: 10)
}
func testThenFlatMap() {
let ex = expectation(description: "")
Promise.value([1,2,3,4]).thenFlatMap {
Promise.value([$0, $0])
}.done {
XCTAssertEqual([1,1,2,2,3,3,4,4], $0)
ex.fulfill()
}.silenceWarning()
wait(for: [ex], timeout: 10)
}
func testLastValueForEmpty() {
XCTAssertTrue(Promise.value([]).lastValue.isRejected)
}
func testFirstValueForEmpty() {
XCTAssertTrue(Promise.value([]).firstValue.isRejected)
}
func testThenOffRejected() {
// surprisingly missing in our CI, mainly due to
// extensive use of `done` in A+ tests since PMK 5
let ex = expectation(description: "")
Promise<Int>(error: PMKError.badInput).then { x -> Promise<Int> in
XCTFail()
return .value(x)
}.catch { _ in
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testBarrier() {
let ex = expectation(description: "")
let q = DispatchQueue(label: "\(#file):\(#line)", attributes: .concurrent)
Promise.value(1).done(on: q, flags: .barrier) {
XCTAssertEqual($0, 1)
dispatchPrecondition(condition: .onQueueAsBarrier(q))
ex.fulfill()
}.catch { _ in
XCTFail()
}
wait(for: [ex], timeout: 10)
}
func testDispatchFlagsSyntax() {
let ex = expectation(description: "")
let q = DispatchQueue(label: "\(#file):\(#line)", attributes: .concurrent)
Promise.value(1).done(on: q, flags: [.barrier, .inheritQoS]) {
XCTAssertEqual($0, 1)
dispatchPrecondition(condition: .onQueueAsBarrier(q))
ex.fulfill()
}.catch { _ in
XCTFail()
}
wait(for: [ex], timeout: 10)
}
}

View File

@@ -0,0 +1,33 @@
import PromiseKit
extension Promise {
func silenceWarning() {}
}
#if os(Linux)
import func CoreFoundation._CFIsMainThread
extension Thread {
// `isMainThread` is not implemented yet in swift-corelibs-foundation.
static var isMainThread: Bool {
return _CFIsMainThread()
}
}
import XCTest
extension XCTestCase {
func wait(for: [XCTestExpectation], timeout: TimeInterval, file: StaticString = #file, line: UInt = #line) {
#if !(swift(>=4.0) && !swift(>=4.1))
let line = Int(line)
#endif
waitForExpectations(timeout: timeout, file: file, line: line)
}
}
extension XCTestExpectation {
func fulfill() {
fulfill(#file, line: #line)
}
}
#endif

View File

@@ -0,0 +1,159 @@
import XCTest
import PromiseKit
class WhenConcurrentTestCase_Swift: XCTestCase {
func testWhen() {
let e = expectation(description: "")
var numbers = (0..<42).makeIterator()
let squareNumbers = numbers.map { $0 * $0 }
let generator = AnyIterator<Guarantee<Int>> {
guard let number = numbers.next() else {
return nil
}
return after(.milliseconds(10)).map {
return number * number
}
}
when(fulfilled: generator, concurrently: 5).done { numbers in
if numbers == squareNumbers {
e.fulfill()
}
}.silenceWarning()
waitForExpectations(timeout: 3, handler: nil)
}
func testWhenEmptyGenerator() {
let e = expectation(description: "")
let generator = AnyIterator<Promise<Int>> {
return nil
}
when(fulfilled: generator, concurrently: 5).done { numbers in
if numbers.count == 0 {
e.fulfill()
}
}.silenceWarning()
waitForExpectations(timeout: 1, handler: nil)
}
func testWhenGeneratorError() {
enum LocalError: Error {
case Unknown
case DivisionByZero
}
let expectedErrorIndex = 42
let expectedError = LocalError.DivisionByZero
let e = expectation(description: "")
var numbers = (-expectedErrorIndex..<expectedErrorIndex).makeIterator()
let generator = AnyIterator<Promise<Int>> {
guard let number = numbers.next() else {
return nil
}
return after(.milliseconds(10)).then { _ -> Promise<Int> in
if number != 0 {
return Promise(error: expectedError)
} else {
return .value(100500 / number)
}
}
}
when(fulfilled: generator, concurrently: 3)
.catch { error in
guard let error = error as? LocalError else {
return
}
guard case .DivisionByZero = error else {
return
}
e.fulfill()
}
waitForExpectations(timeout: 3, handler: nil)
}
func testWhenConcurrency() {
let expectedConcurrently = 4
var currentConcurrently = 0
var maxConcurrently = 0
let e = expectation(description: "")
var numbers = (0..<42).makeIterator()
let generator = AnyIterator<Promise<Int>> {
currentConcurrently += 1
maxConcurrently = max(maxConcurrently, currentConcurrently)
guard let number = numbers.next() else {
return nil
}
return after(.milliseconds(10)).then(on: .main) { _ -> Promise<Int> in
currentConcurrently -= 1
return .value(number * number)
}
}
when(fulfilled: generator, concurrently: expectedConcurrently).done { _ in
XCTAssertEqual(expectedConcurrently, maxConcurrently)
e.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 3)
}
func testWhenConcurrencyLessThanZero() {
let generator = AnyIterator<Promise<Int>> { XCTFail(); return nil }
let p1 = when(fulfilled: generator, concurrently: 0)
let p2 = when(fulfilled: generator, concurrently: -1)
guard let e1 = p1.error else { return XCTFail() }
guard let e2 = p2.error else { return XCTFail() }
guard case PMKError.badInput = e1 else { return XCTFail() }
guard case PMKError.badInput = e2 else { return XCTFail() }
}
func testStopsDequeueingOnceRejected() {
let ex = expectation(description: "")
enum Error: Swift.Error { case dummy }
var x: UInt = 0
let generator = AnyIterator<Promise<Void>> {
x += 1
switch x {
case 0:
fatalError()
case 1:
return Promise()
case 2:
return Promise(error: Error.dummy)
case _:
XCTFail()
return nil
}
}
when(fulfilled: generator, concurrently: 1).done {
XCTFail("\($0)")
}.catch { error in
ex.fulfill()
}
waitForExpectations(timeout: 3)
}
}

View File

@@ -0,0 +1,41 @@
// Created by Austin Feight on 3/19/16.
// Copyright © 2016 Max Howell. All rights reserved.
import PromiseKit
import XCTest
class JoinTests: XCTestCase {
func testImmediates() {
let successPromise = Promise()
var joinFinished = false
when(resolved: successPromise).done(on: nil) { _ in joinFinished = true }
XCTAssert(joinFinished, "Join immediately finishes on fulfilled promise")
let promise2 = Promise.value(2)
let promise3 = Promise.value(3)
let promise4 = Promise.value(4)
var join2Finished = false
when(resolved: promise2, promise3, promise4).done(on: nil) { _ in join2Finished = true }
XCTAssert(join2Finished, "Join immediately finishes on fulfilled promises")
}
func testFulfilledAfterAllResolve() {
let (promise1, seal1) = Promise<Void>.pending()
let (promise2, seal2) = Promise<Void>.pending()
let (promise3, seal3) = Promise<Void>.pending()
var finished = false
when(resolved: promise1, promise2, promise3).done(on: nil) { _ in finished = true }
XCTAssertFalse(finished, "Not all promises have resolved")
seal1.fulfill(())
XCTAssertFalse(finished, "Not all promises have resolved")
seal2.fulfill(())
XCTAssertFalse(finished, "Not all promises have resolved")
seal3.fulfill(())
XCTAssert(finished, "All promises have resolved")
}
}

View File

@@ -0,0 +1,265 @@
import PromiseKit
import Dispatch
import XCTest
class WhenTests: XCTestCase {
func testEmpty() {
let e1 = expectation(description: "")
let promises: [Promise<Void>] = []
when(fulfilled: promises).done { _ in
e1.fulfill()
}.silenceWarning()
let e2 = expectation(description: "")
when(resolved: promises).done { _ in
e2.fulfill()
}.silenceWarning()
wait(for: [e1, e2], timeout: 1)
}
func testInt() {
let e1 = expectation(description: "")
let p1 = Promise.value(1)
let p2 = Promise.value(2)
let p3 = Promise.value(3)
let p4 = Promise.value(4)
when(fulfilled: [p1, p2, p3, p4]).done { x in
XCTAssertEqual(x[0], 1)
XCTAssertEqual(x[1], 2)
XCTAssertEqual(x[2], 3)
XCTAssertEqual(x[3], 4)
XCTAssertEqual(x.count, 4)
e1.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1, handler: nil)
}
func testDoubleTuple() {
let e1 = expectation(description: "")
let p1 = Promise.value(1)
let p2 = Promise.value("abc")
when(fulfilled: p1, p2).done{ x, y in
XCTAssertEqual(x, 1)
XCTAssertEqual(y, "abc")
e1.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1, handler: nil)
}
func testTripleTuple() {
let e1 = expectation(description: "")
let p1 = Promise.value(1)
let p2 = Promise.value("abc")
let p3 = Promise.value( 1.0)
when(fulfilled: p1, p2, p3).done { u, v, w in
XCTAssertEqual(1, u)
XCTAssertEqual("abc", v)
XCTAssertEqual(1.0, w)
e1.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1, handler: nil)
}
func testQuadrupleTuple() {
let e1 = expectation(description: "")
let p1 = Promise.value(1)
let p2 = Promise.value("abc")
let p3 = Promise.value(1.0)
let p4 = Promise.value(true)
when(fulfilled: p1, p2, p3, p4).done { u, v, w, x in
XCTAssertEqual(1, u)
XCTAssertEqual("abc", v)
XCTAssertEqual(1.0, w)
XCTAssertEqual(true, x)
e1.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1, handler: nil)
}
func testQuintupleTuple() {
let e1 = expectation(description: "")
let p1 = Promise.value(1)
let p2 = Promise.value("abc")
let p3 = Promise.value(1.0)
let p4 = Promise.value(true)
let p5 = Promise.value("a" as Character)
when(fulfilled: p1, p2, p3, p4, p5).done { u, v, w, x, y in
XCTAssertEqual(1, u)
XCTAssertEqual("abc", v)
XCTAssertEqual(1.0, w)
XCTAssertEqual(true, x)
XCTAssertEqual("a" as Character, y)
e1.fulfill()
}.silenceWarning()
waitForExpectations(timeout: 1, handler: nil)
}
func testVoid() {
let e1 = expectation(description: "")
let p1 = Promise.value(1).done { _ in }
let p2 = Promise.value(2).done { _ in }
let p3 = Promise.value(3).done { _ in }
let p4 = Promise.value(4).done { _ in }
when(fulfilled: p1, p2, p3, p4).done(e1.fulfill).silenceWarning()
waitForExpectations(timeout: 1, handler: nil)
}
func testRejected() {
enum Error: Swift.Error { case dummy }
let e1 = expectation(description: "")
let p1 = after(.milliseconds(100)).map{ true }
let p2: Promise<Bool> = after(.milliseconds(200)).map{ throw Error.dummy }
let p3 = Promise.value(false)
when(fulfilled: p1, p2, p3).catch { _ in
e1.fulfill()
}
waitForExpectations(timeout: 1, handler: nil)
}
func testProgress() {
let ex = expectation(description: "")
XCTAssertNil(Progress.current())
let p1 = after(.milliseconds(10))
let p2 = after(.milliseconds(20))
let p3 = after(.milliseconds(30))
let p4 = after(.milliseconds(40))
let progress = Progress(totalUnitCount: 1)
progress.becomeCurrent(withPendingUnitCount: 1)
when(fulfilled: p1, p2, p3, p4).done { _ in
XCTAssertEqual(progress.completedUnitCount, 1)
ex.fulfill()
}.silenceWarning()
progress.resignCurrent()
waitForExpectations(timeout: 1, handler: nil)
}
func testProgressDoesNotExceed100Percent() {
let ex1 = expectation(description: "")
let ex2 = expectation(description: "")
XCTAssertNil(Progress.current())
let p1 = after(.milliseconds(10))
let p2 = after(.milliseconds(20)).done { throw NSError(domain: "a", code: 1, userInfo: nil) }
let p3 = after(.milliseconds(30))
let p4 = after(.milliseconds(40))
let progress = Progress(totalUnitCount: 1)
progress.becomeCurrent(withPendingUnitCount: 1)
let promise = when(fulfilled: p1, p2, p3, p4)
progress.resignCurrent()
promise.catch { _ in
ex2.fulfill()
}
var x = 0
func finally() {
x += 1
if x == 4 {
XCTAssertLessThanOrEqual(1, progress.fractionCompleted)
XCTAssertEqual(progress.completedUnitCount, 1)
ex1.fulfill()
}
}
let q = DispatchQueue.main
p1.done(on: q, finally)
p2.ensure(on: q, finally).silenceWarning()
p3.done(on: q, finally)
p4.done(on: q, finally)
waitForExpectations(timeout: 1, handler: nil)
}
func testUnhandledErrorHandlerDoesNotFire() {
enum Error: Swift.Error {
case test
}
let ex = expectation(description: "")
let p1 = Promise<Void>(error: Error.test)
let p2 = after(.milliseconds(100))
when(fulfilled: p1, p2).done{ _ in XCTFail() }.catch { error in
XCTAssertTrue(error as? Error == Error.test)
ex.fulfill()
}
waitForExpectations(timeout: 1, handler: nil)
}
func testUnhandledErrorHandlerDoesNotFireForStragglers() {
enum Error: Swift.Error {
case test
case straggler
}
let ex1 = expectation(description: "")
let ex2 = expectation(description: "")
let ex3 = expectation(description: "")
let p1 = Promise<Void>(error: Error.test)
let p2 = after(.milliseconds(100)).done { throw Error.straggler }
let p3 = after(.milliseconds(200)).done { throw Error.straggler }
when(fulfilled: p1, p2, p3).catch { error -> Void in
XCTAssertTrue(Error.test == error as? Error)
ex1.fulfill()
}
p2.ensure { after(.milliseconds(100)).done(ex2.fulfill) }.silenceWarning()
p3.ensure { after(.milliseconds(100)).done(ex3.fulfill) }.silenceWarning()
waitForExpectations(timeout: 1, handler: nil)
}
func testAllSealedRejectedFirstOneRejects() {
enum Error: Swift.Error {
case test1
case test2
case test3
}
let ex = expectation(description: "")
let p1 = Promise<Void>(error: Error.test1)
let p2 = Promise<Void>(error: Error.test2)
let p3 = Promise<Void>(error: Error.test3)
when(fulfilled: p1, p2, p3).catch { error in
XCTAssertTrue(error as? Error == Error.test1)
ex.fulfill()
}
waitForExpectations(timeout: 1)
}
func testGuaranteeWhen() {
let ex1 = expectation(description: "")
when(Guarantee(), Guarantee()).done {
ex1.fulfill()
}
let ex2 = expectation(description: "")
when(guarantees: [Guarantee(), Guarantee()]).done {
ex2.fulfill()
}
wait(for: [ex1, ex2], timeout: 10)
}
}

View File

@@ -0,0 +1,59 @@
import XCTest
import PromiseKit
class ZalgoTests: XCTestCase {
func test1() {
var resolved = false
Promise.value(1).done(on: nil) { _ in
resolved = true
}.silenceWarning()
XCTAssertTrue(resolved)
}
func test2() {
let p1 = Promise.value(1).map(on: nil) { x in
return 2
}
XCTAssertEqual(p1.value!, 2)
var x = 0
let (p2, seal) = Promise<Int>.pending()
p2.done(on: nil) { _ in
x = 1
}.silenceWarning()
XCTAssertEqual(x, 0)
seal.fulfill(1)
XCTAssertEqual(x, 1)
}
// returning a pending promise from its own zalgod then handler doesnt hang
func test3() {
let ex = (expectation(description: ""), expectation(description: ""))
var p1: Promise<Void>!
p1 = after(.milliseconds(100)).then(on: nil) { _ -> Promise<Void> in
ex.0.fulfill()
return p1
}
p1.catch { err in
defer{ ex.1.fulfill() }
guard case PMKError.returnedSelf = err else { return XCTFail() }
}
waitForExpectations(timeout: 1)
}
// return a sealed promise from its own zalgod then handler doesnt hang
func test4() {
let ex = expectation(description: "")
let p1 = Promise.value(1)
p1.then(on: nil) { _ -> Promise<Int> in
ex.fulfill()
return p1
}.silenceWarning()
waitForExpectations(timeout: 1)
}
}

View File

@@ -0,0 +1,158 @@
import PromiseKit
import XCTest
class DeprecationTests: XCTestCase {
func testWrap1() {
let dummy = 10
func completion(_ body: (_ a: Int?, _ b: Error?) -> Void) {
body(dummy, nil)
}
let ex = expectation(description: "")
wrap(completion).done {
XCTAssertEqual($0, dummy)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testWrap2() {
let dummy = 10
func completion(_ body: (_ a: Int, _ b: Error?) -> Void) {
body(dummy, nil)
}
let ex = expectation(description: "")
wrap(completion).done {
XCTAssertEqual($0, dummy)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testWrap3() {
let dummy = 10
func completion(_ body: (_ a: Error?, _ b: Int?) -> Void) {
body(nil, dummy)
}
let ex = expectation(description: "")
wrap(completion).done {
XCTAssertEqual($0, dummy)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testWrap4() {
let dummy = 10
func completion(_ body: (_ a: Error?) -> Void) {
body(nil)
}
let ex = expectation(description: "")
wrap(completion).done {
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testWrap5() {
let dummy = 10
func completion(_ body: (_ a: Int) -> Void) {
body(dummy)
}
let ex = expectation(description: "")
wrap(completion).done {
XCTAssertEqual($0, dummy)
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testAlways() {
let ex = expectation(description: "")
Promise.value(1).always(execute: ex.fulfill)
wait(for: [ex], timeout: 10)
}
#if PMKFullDeprecations
func testFlatMap() {
let ex = expectation(description: "")
Promise.value(1).flatMap { _ -> Int? in
nil
}.catch {
//TODO should be `flatMap`, but how to enact that without causing
// compiler to warn when building PromiseKit for end-users? LOL
guard case PMKError.compactMap = $0 else { return XCTFail() }
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testSequenceMap() {
let ex = expectation(description: "")
Promise.value([1, 2]).map {
$0 + 1
}.done {
XCTAssertEqual($0, [2, 3])
ex.fulfill()
}.silenceWarning()
wait(for: [ex], timeout: 10)
}
func testSequenceFlatMap() {
let ex = expectation(description: "")
Promise.value([1, 2]).flatMap {
[$0 + 1, $0 + 2]
}.done {
XCTAssertEqual($0, [2, 3, 3, 4])
ex.fulfill()
}.silenceWarning()
wait(for: [ex], timeout: 10)
}
#endif
func testSequenceFilter() {
let ex = expectation(description: "")
Promise.value([0, 1, 2, 3]).filter {
$0 < 2
}.done {
XCTAssertEqual($0, [0, 1])
ex.fulfill()
}.silenceWarning()
wait(for: [ex], timeout: 10)
}
func testSorted() {
let ex = expectation(description: "")
Promise.value([5, 2, 1, 8]).sorted().done {
XCTAssertEqual($0, [1,2,5,8])
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
func testFirst() {
XCTAssertEqual(Promise.value([1,2]).first.value, 1)
}
func testLast() {
XCTAssertEqual(Promise.value([1,2]).last.value, 2)
}
func testPMKErrorFlatMap() {
XCTAssertNotNil(PMKError.flatMap(1, Int.self).errorDescription)
}
}
extension Promise {
func silenceWarning() {}
}

View File

@@ -0,0 +1,63 @@
# From https://github.com/github/gitignore/blob/master/Node.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next

View File

@@ -0,0 +1,80 @@
//
// AllTests.swift
// PMKJSA+Tests
//
// Created by Lois Di Qual on 2/28/18.
//
import XCTest
import PromiseKit
import JavaScriptCore
class AllTests: XCTestCase {
func testAll() {
let scriptPath = URL(fileURLWithPath: #file).deletingLastPathComponent().appendingPathComponent("build/build.js")
guard FileManager.default.fileExists(atPath: scriptPath.path) else {
return print("Skipping JS-A+: see README for instructions on how to build")
}
guard let script = try? String(contentsOf: scriptPath) else {
return XCTFail("Couldn't read content of test suite JS file")
}
let context = JSUtils.sharedContext
// Add a global exception handler
context.exceptionHandler = { context, exception in
guard let exception = exception else {
return XCTFail("Unknown JS exception")
}
JSUtils.printStackTrace(exception: exception, includeExceptionDescription: true)
}
// Setup mock functions (timers, console.log, etc)
let environment = MockNodeEnvironment()
environment.setup(with: context)
// Expose JSPromise in the javascript context
context.setObject(JSPromise.self, forKeyedSubscript: "JSPromise" as NSString)
// Create adapter
guard let adapter = JSValue(object: NSDictionary(), in: context) else {
fatalError("Couldn't create adapter")
}
adapter.setObject(JSAdapter.resolved, forKeyedSubscript: "resolved" as NSString)
adapter.setObject(JSAdapter.rejected, forKeyedSubscript: "rejected" as NSString)
adapter.setObject(JSAdapter.deferred, forKeyedSubscript: "deferred" as NSString)
// Evaluate contents of `build.js`, which exposes `runTests` in the global context
context.evaluateScript(script)
guard let runTests = context.objectForKeyedSubscript("runTests") else {
return XCTFail("Couldn't find `runTests` in JS context")
}
// Create a callback that's called whenever there's a failure
let onFail: @convention(block) (JSValue, JSValue) -> Void = { test, error in
guard let test = test.toString(), let error = error.toString() else {
return XCTFail("Unknown test failure")
}
XCTFail("\(test) failed: \(error)")
}
let onFailValue: JSValue = JSValue(object: onFail, in: context)
// Create a new callback that we'll send to `runTest` so that it notifies when tests are done running.
let expectation = self.expectation(description: "async")
let onDone: @convention(block) (JSValue) -> Void = { failures in
expectation.fulfill()
}
let onDoneValue: JSValue = JSValue(object: onDone, in: context)
// If there's a need to only run one specific test, uncomment the next line and comment the one after
// let testName: JSValue = JSValue(object: "2.3.1", in: context)
let testName = JSUtils.undefined
// Call `runTests`
runTests.call(withArguments: [adapter, onFailValue, onDoneValue, testName])
self.wait(for: [expectation], timeout: 60)
}
}

View File

@@ -0,0 +1,53 @@
//
// JSAdapter.swift
// PMKJSA+Tests
//
// Created by Lois Di Qual on 3/2/18.
//
import Foundation
import JavaScriptCore
import PromiseKit
enum JSAdapter {
static let resolved: @convention(block) (JSValue) -> JSPromise = { value in
return JSPromise(promise: .value(value))
}
static let rejected: @convention(block) (JSValue) -> JSPromise = { reason in
let error = JSUtils.JSError(reason: reason)
let promise = Promise<JSValue>(error: error)
return JSPromise(promise: promise)
}
static let deferred: @convention(block) () -> JSValue = {
let context = JSContext.current()
guard let object = JSValue(object: NSDictionary(), in: context) else {
fatalError("Couldn't create object")
}
let pendingPromise = Promise<JSValue>.pending()
let jsPromise = JSPromise(promise: pendingPromise.promise)
// promise
object.setObject(jsPromise, forKeyedSubscript: "promise" as NSString)
// resolve
let resolve: @convention(block) (JSValue) -> Void = { value in
pendingPromise.resolver.fulfill(value)
}
object.setObject(resolve, forKeyedSubscript: "resolve" as NSString)
// reject
let reject: @convention(block) (JSValue) -> Void = { reason in
let error = JSUtils.JSError(reason: reason)
pendingPromise.resolver.reject(error)
}
object.setObject(reject, forKeyedSubscript: "reject" as NSString)
return object
}
}

View File

@@ -0,0 +1,94 @@
//
// JSPromise.swift
// PMKJSA+Tests
//
// Created by Lois Di Qual on 3/1/18.
//
import Foundation
import XCTest
import PromiseKit
import JavaScriptCore
@objc protocol JSPromiseProtocol: JSExport {
func then(_: JSValue, _: JSValue) -> JSPromise
}
class JSPromise: NSObject, JSPromiseProtocol {
let promise: Promise<JSValue>
init(promise: Promise<JSValue>) {
self.promise = promise
}
func then(_ onFulfilled: JSValue, _ onRejected: JSValue) -> JSPromise {
// Keep a reference to the returned promise so we can comply to 2.3.1
var returnedPromiseRef: Promise<JSValue>?
let afterFulfill = promise.then { value -> Promise<JSValue> in
// 2.2.1: ignored if not a function
guard JSUtils.isFunction(value: onFulfilled) else {
return .value(value)
}
// Call `onFulfilled`
// 2.2.5: onFulfilled/onRejected must be called as functions (with no `this` value)
guard let returnValue = try JSUtils.call(function: onFulfilled, arguments: [JSUtils.undefined, value]) else {
return .value(value)
}
// Extract JSPromise.promise if available, or use plain return value
if let jsPromise = returnValue.toObjectOf(JSPromise.self) as? JSPromise {
// 2.3.1: if returned value is the promise that `then` returned, throw TypeError
if jsPromise.promise === returnedPromiseRef {
throw JSUtils.JSError(reason: JSUtils.typeError(message: "Returned self"))
}
return jsPromise.promise
} else {
return .value(returnValue)
}
}
let afterReject = promise.recover { error -> Promise<JSValue> in
// 2.2.1: ignored if not a function
guard let jsError = error as? JSUtils.JSError, JSUtils.isFunction(value: onRejected) else {
throw error
}
// Call `onRejected`
// 2.2.5: onFulfilled/onRejected must be called as functions (with no `this` value)
guard let returnValue = try JSUtils.call(function: onRejected, arguments: [JSUtils.undefined, jsError.reason]) else {
throw error
}
// Extract JSPromise.promise if available, or use plain return value
if let jsPromise = returnValue.toObjectOf(JSPromise.self) as? JSPromise {
// 2.3.1: if returned value is the promise that `then` returned, throw TypeError
if jsPromise.promise === returnedPromiseRef {
throw JSUtils.JSError(reason: JSUtils.typeError(message: "Returned self"))
}
return jsPromise.promise
} else {
return .value(returnValue)
}
}
let newPromise = Promise<Result<JSValue>> { resolver in
_ = promise.tap(resolver.fulfill)
}.then(on: nil) { result -> Promise<JSValue> in
switch result {
case .fulfilled: return afterFulfill
case .rejected: return afterReject
}
}
returnedPromiseRef = newPromise
return JSPromise(promise: newPromise)
}
}

View File

@@ -0,0 +1,116 @@
//
// JSUtils.swift
// PMKJSA+Tests
//
// Created by Lois Di Qual on 3/2/18.
//
import Foundation
import JavaScriptCore
enum JSUtils {
class JSError: Error {
let reason: JSValue
init(reason: JSValue) {
self.reason = reason
}
}
static let sharedContext: JSContext = {
guard let context = JSContext() else {
fatalError("Couldn't create JS context")
}
return context
}()
static var undefined: JSValue {
guard let undefined = JSValue(undefinedIn: JSUtils.sharedContext) else {
fatalError("Couldn't create `undefined` value")
}
return undefined
}
static func typeError(message: String) -> JSValue {
let message = message.replacingOccurrences(of: "\"", with: "\\\"")
let script = "new TypeError(\"\(message)\")"
guard let result = sharedContext.evaluateScript(script) else {
fatalError("Couldn't create TypeError")
}
return result
}
// @warning: relies on lodash to be present
static func isFunction(value: JSValue) -> Bool {
guard let context = value.context else {
return false
}
guard let lodash = context.objectForKeyedSubscript("_") else {
fatalError("Couldn't get lodash in JS context")
}
guard let result = lodash.invokeMethod("isFunction", withArguments: [value]) else {
fatalError("Couldn't invoke _.isFunction")
}
return result.toBool()
}
// Calls a JS function using `Function.prototype.call` and throws any potential exception wrapped in a JSError
static func call(function: JSValue, arguments: [JSValue]) throws -> JSValue? {
let context = JSUtils.sharedContext
// Create a new exception handler that will store a potential exception
// thrown in the handler. Save the value of the old handler.
var caughtException: JSValue?
let savedExceptionHandler = context.exceptionHandler
context.exceptionHandler = { context, exception in
caughtException = exception
}
// Call the handler
let returnValue = function.invokeMethod("call", withArguments: arguments)
context.exceptionHandler = savedExceptionHandler
// If an exception was caught, throw it
if let exception = caughtException {
throw JSError(reason: exception)
}
return returnValue
}
static func printCurrentStackTrace() {
guard let exception = JSUtils.sharedContext.evaluateScript("new Error()") else {
return print("Couldn't get current stack trace")
}
printStackTrace(exception: exception, includeExceptionDescription: false)
}
static func printStackTrace(exception: JSValue, includeExceptionDescription: Bool) {
guard let lineNumber = exception.objectForKeyedSubscript("line"),
let column = exception.objectForKeyedSubscript("column"),
let message = exception.objectForKeyedSubscript("message"),
let stacktrace = exception.objectForKeyedSubscript("stack")?.toString() else {
return print("Couldn't print stack trace")
}
if includeExceptionDescription {
print("JS Exception at \(lineNumber):\(column): \(message)")
}
let lines = stacktrace.split(separator: "\n").map { "\t> \($0)" }.joined(separator: "\n")
print(lines)
}
}
#if !swift(>=3.2)
extension String {
func split(separator: Character, omittingEmptySubsequences: Bool = true) -> [String] {
return characters.split(separator: separator, omittingEmptySubsequences: omittingEmptySubsequences).map(String.init)
}
var first: Character? {
return characters.first
}
}
#endif

View File

@@ -0,0 +1,117 @@
//
// MockNodeEnvironment.swift
// PMKJSA+Tests
//
// Created by Lois Di Qual on 3/1/18.
//
import Foundation
import JavaScriptCore
class MockNodeEnvironment {
private var timers: [UInt32: Timer] = [:]
func setup(with context: JSContext) {
// console.log / console.error
setupConsole(context: context)
// setTimeout
let setTimeout: @convention(block) (JSValue, Double) -> UInt32 = { function, intervalMs in
let timerID = self.addTimer(interval: intervalMs / 1000, repeats: false, function: function)
return timerID
}
context.setObject(setTimeout, forKeyedSubscript: "setTimeout" as NSString)
// clearTimeout
let clearTimeout: @convention(block) (JSValue) -> Void = { timeoutID in
guard timeoutID.isNumber else {
return
}
self.removeTimer(timerID: timeoutID.toUInt32())
}
context.setObject(clearTimeout, forKeyedSubscript: "clearTimeout" as NSString)
// setInterval
let setInterval: @convention(block) (JSValue, Double) -> UInt32 = { function, intervalMs in
let timerID = self.addTimer(interval: intervalMs / 1000, repeats: true, function: function)
return timerID
}
context.setObject(setInterval, forKeyedSubscript: "setInterval" as NSString)
// clearInterval
let clearInterval: @convention(block) (JSValue) -> Void = { intervalID in
guard intervalID.isNumber else {
return
}
self.removeTimer(timerID: intervalID.toUInt32())
}
context.setObject(clearInterval, forKeyedSubscript: "clearInterval" as NSString)
}
private func setupConsole(context: JSContext) {
guard let console = context.objectForKeyedSubscript("console") else {
fatalError("Couldn't get global `console` object")
}
let consoleLog: @convention(block) () -> Void = {
guard let arguments = JSContext.currentArguments(), let format = arguments.first as? JSValue else {
return
}
let otherArguments = arguments.dropFirst()
if otherArguments.count == 0 {
print(format)
} else {
let otherArguments = otherArguments.compactMap { $0 as? JSValue }
let format = format.toString().replacingOccurrences(of: "%s", with: "%@")
let expectedTypes = format.split(separator: "%", omittingEmptySubsequences: false).dropFirst().compactMap { $0.first }.map { String($0) }
let typedArguments = otherArguments.enumerated().compactMap { index, value -> CVarArg? in
let expectedType = expectedTypes[index]
let converted: CVarArg
switch expectedType {
case "s": converted = value.toString()
case "d": converted = value.toInt32()
case "f": converted = value.toDouble()
default: converted = value.toString()
}
return converted
}
let output = String(format: format, arguments: typedArguments)
print(output)
}
}
console.setObject(consoleLog, forKeyedSubscript: "log" as NSString)
console.setObject(consoleLog, forKeyedSubscript: "error" as NSString)
}
private func addTimer(interval: TimeInterval, repeats: Bool, function: JSValue) -> UInt32 {
let block = BlockOperation {
DispatchQueue.main.async {
function.call(withArguments: [])
}
}
let timer = Timer.scheduledTimer(timeInterval: interval, target: block, selector: #selector(Operation.main), userInfo: nil, repeats: repeats)
let rawHash = UUID().uuidString.hashValue
#if swift(>=4.0)
let hash = UInt32(truncatingIfNeeded: rawHash)
#else
let hash = UInt32(truncatingBitPattern: rawHash)
#endif
timers[hash] = timer
return hash
}
private func removeTimer(timerID: UInt32) {
guard let timer = timers[timerID] else {
return print("Couldn't find timer \(timerID)")
}
timer.invalidate()
timers[timerID] = nil
}
}

View File

@@ -0,0 +1,75 @@
Promises/A+ Compliance Test Suite (JavaScript)
==============================================
What is this?
-------------
This contains the necessary Swift and JS files to run the Promises/A+ compliance test suite from PromiseKit's unit tests.
- Promise/A+ Spec: <https://promisesaplus.com/>
- Compliance Test Suite: <https://github.com/promises-aplus/promises-tests>
Run tests
---------
```
$ npm install
$ npm run build
```
then open `PromiseKit.xcodeproj` and run the `PMKJSA+Tests` unit test scheme.
Known limitations
-----------------
See `ignoredTests` in `index.js`.
- 2.3.3 is disabled: Otherwise, if x is an object or function. This spec is a NOOP for Swift:
- We have decided not to interact with other Promises A+ implementations
- functions cannot have properties
Upgrade the test suite
----------------------
```
$ npm install --save promises-aplus-tests@latest
$ npm run build
```
Develop
-------
JavaScriptCore is a bit tedious to work with so here are a couple tips in case you're trying to debug the test suite.
If you're editing JS files, enable live rebuilds:
```
$ npm run watch
```
If you're editing Swift files, a couple things you can do:
- You can adjust `testName` in `AllTests.swift` to only run one test suite
- You can call `JSUtils.printCurrentStackTrace()` at any time. It won't contain line numbers but some of the frame names might help.
How it works
------------
The Promises/A+ test suite is written in JavaScript but PromiseKit is written in Swift/ObjC. For the test suite to run against swift code, we expose a promise wrapper `JSPromise` inside a JavaScriptCore context. This is done in a regular XCTestCase.
Since JavaScriptCore doesn't support CommonJS imports, we inline all the JavaScript code into `build/build.js` using webpack. This includes all the npm dependencies (`promises-aplus-tests`, `mocha`, `sinon`, etc) as well as the glue code in `index.js`.
`build.js` exposes one global variable `runTests(adapter, onFail, onDone, [testName])`. In our XCTestCase, a shared JavaScriptCore context is created, `build.js` is evaluated and now `runTests` is accessible from the Swift context.
In our swift test, we create a JS-bridged `JSPromise` which only has one method `then(onFulfilled, onRejected) -> Promise`. It wraps a swift `Promise` and delegates call `then` calls to it.
An [adapter](https://github.com/promises-aplus/promises-tests#adapters) plain JS object which provides `revoled(value), rejected(reason), and deferred()` is passed to `runTests` to run the whole JavaScript test suite.
Errors and end events are reported back to Swift and piped to `XCTFail()` if necessary.
Since JavaScriptCore isn't a node/web environment, there is quite a bit of stubbing necessary for all this to work:
- The `fs` module is stubbed with an empty function
- `console.log` redirects to `Swift.print` and provides only basic format parsing
- `setTimeout/setInterval` are implemented with `Swift.Timer` behind the scenes and stored in a `[TimerID: Timer]` map.

View File

@@ -0,0 +1,42 @@
const _ = require('lodash')
require('mocha')
// Ignored by design
const ignoredTests = [
'2.3.3'
]
module.exports = function(adapter, onFail, onDone, testName) {
global.adapter = adapter
const mocha = new Mocha({ ui: 'bdd' })
// Require all tests
console.log('Loading test files')
const requireTest = require.context('promises-aplus-tests/lib/tests', false, /\.js$/)
requireTest.keys().forEach(file => {
let currentTestName = _.replace(_.replace(file, './', ''), '.js', '')
if (testName && currentTestName !== testName) {
return
}
if (_.includes(ignoredTests, currentTestName)) {
return
}
console.log(`\t${currentTestName}`)
mocha.suite.emit('pre-require', global, file, mocha)
mocha.suite.emit('require', requireTest(file), file, mocha)
mocha.suite.emit('post-require', global, file, mocha)
})
const runner = mocha.run(failures => {
onDone(failures)
})
runner.on('fail', (test, err) => {
console.error(err)
onFail(test.title, err)
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
{
"scripts": {
"build": "webpack-cli",
"watch": "webpack-cli --watch --mode development"
},
"dependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.3",
"babel-preset-env": "^1.6.1",
"lodash": "^4.17.5",
"mocha": "^5.0.1",
"promises-aplus-tests": "^2.1.2",
"sinon": "^4.4.2",
"webpack": "^4.0.1",
"webpack-cli": "^2.0.9"
}
}

View File

@@ -0,0 +1,29 @@
var webpack = require('webpack');
module.exports = {
mode: 'development',
context: __dirname,
entry: './index.js',
output: {
path: __dirname + '/build',
filename: 'build.js',
library: 'runTests'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env']
}
}
}
]
},
node: {
fs: 'empty'
},
};

View File

@@ -0,0 +1,288 @@
// Generated using Sourcery 0.10.0 https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
@testable import CorePromise
@testable import A_
import XCTest
//TODO get this to run on CI and dont have it committed
//NOTE problem is Sourcery doesnt support Linux currently
//USAGE: cd PromiseKit/Sources/.. && sourcery --config .github/sourcery.yml
extension AfterTests {
static var allTests = [
("testZero", AfterTests.testZero),
("testNegative", AfterTests.testNegative),
("testPositive", AfterTests.testPositive),
]
}
extension CancellationTests {
static var allTests = [
("testCancellation", CancellationTests.testCancellation),
("testThrowCancellableErrorThatIsNotCancelled", CancellationTests.testThrowCancellableErrorThatIsNotCancelled),
("testRecoverWithCancellation", CancellationTests.testRecoverWithCancellation),
("testFoundationBridging1", CancellationTests.testFoundationBridging1),
("testFoundationBridging2", CancellationTests.testFoundationBridging2),
("testIsCancelled", CancellationTests.testIsCancelled),
("testIsCancelled", CancellationTests.testIsCancelled),
]
}
extension CatchableTests {
static var allTests = [
("testFinally", CatchableTests.testFinally),
("testCauterize", CatchableTests.testCauterize),
("test__void_specialized_full_recover", CatchableTests.test__void_specialized_full_recover),
("test__void_specialized_full_recover__fulfilled_path", CatchableTests.test__void_specialized_full_recover__fulfilled_path),
("test__void_specialized_conditional_recover", CatchableTests.test__void_specialized_conditional_recover),
("test__void_specialized_conditional_recover__no_recover", CatchableTests.test__void_specialized_conditional_recover__no_recover),
("test__void_specialized_conditional_recover__ignores_cancellation_but_fed_cancellation", CatchableTests.test__void_specialized_conditional_recover__ignores_cancellation_but_fed_cancellation),
("test__void_specialized_conditional_recover__fulfilled_path", CatchableTests.test__void_specialized_conditional_recover__fulfilled_path),
("test__full_recover", CatchableTests.test__full_recover),
("test__full_recover__fulfilled_path", CatchableTests.test__full_recover__fulfilled_path),
("test__conditional_recover", CatchableTests.test__conditional_recover),
("test__conditional_recover__no_recover", CatchableTests.test__conditional_recover__no_recover),
("test__conditional_recover__ignores_cancellation_but_fed_cancellation", CatchableTests.test__conditional_recover__ignores_cancellation_but_fed_cancellation),
("test__conditional_recover__fulfilled_path", CatchableTests.test__conditional_recover__fulfilled_path),
]
}
extension GuaranteeTests {
static var allTests = [
("testInit", GuaranteeTests.testInit),
("testWait", GuaranteeTests.testWait),
]
}
extension HangTests {
static var allTests = [
("test", HangTests.test),
("testError", HangTests.testError),
]
}
extension JoinTests {
static var allTests = [
("testImmediates", JoinTests.testImmediates),
("testFulfilledAfterAllResolve", JoinTests.testFulfilledAfterAllResolve),
]
}
extension PMKDefaultDispatchQueueTest {
static var allTests = [
("testOverrodeDefaultThenQueue", PMKDefaultDispatchQueueTest.testOverrodeDefaultThenQueue),
("testOverrodeDefaultCatchQueue", PMKDefaultDispatchQueueTest.testOverrodeDefaultCatchQueue),
("testOverrodeDefaultAlwaysQueue", PMKDefaultDispatchQueueTest.testOverrodeDefaultAlwaysQueue),
]
}
extension PMKErrorTests {
static var allTests = [
("testCustomStringConvertible", PMKErrorTests.testCustomStringConvertible),
("testCustomDebugStringConvertible", PMKErrorTests.testCustomDebugStringConvertible),
]
}
extension PromiseTests {
static var allTests = [
("testIsPending", PromiseTests.testIsPending),
("testIsResolved", PromiseTests.testIsResolved),
("testIsFulfilled", PromiseTests.testIsFulfilled),
("testIsRejected", PromiseTests.testIsRejected),
("testDispatchQueueAsyncExtensionReturnsPromise", PromiseTests.testDispatchQueueAsyncExtensionReturnsPromise),
("testDispatchQueueAsyncExtensionCanThrowInBody", PromiseTests.testDispatchQueueAsyncExtensionCanThrowInBody),
("testCustomStringConvertible", PromiseTests.testCustomStringConvertible),
("testCannotFulfillWithError", PromiseTests.testCannotFulfillWithError),
("testCanMakeVoidPromise", PromiseTests.testCanMakeVoidPromise),
("testCanMakeVoidPromise", PromiseTests.testCanMakeVoidPromise),
("testThrowInInitializer", PromiseTests.testThrowInInitializer),
("testThrowInFirstly", PromiseTests.testThrowInFirstly),
("testWait", PromiseTests.testWait),
("testPipeForResolved", PromiseTests.testPipeForResolved),
]
}
extension RaceTests {
static var allTests = [
("test1", RaceTests.test1),
("test2", RaceTests.test2),
("test1Array", RaceTests.test1Array),
("test2Array", RaceTests.test2Array),
("testEmptyArray", RaceTests.testEmptyArray),
]
}
extension RegressionTests {
static var allTests = [
("testReturningPreviousPromiseWorks", RegressionTests.testReturningPreviousPromiseWorks),
]
}
extension StressTests {
static var allTests = [
("testThenDataRace", StressTests.testThenDataRace),
("testThensAreSequentialForLongTime", StressTests.testThensAreSequentialForLongTime),
("testZalgoDataRace", StressTests.testZalgoDataRace),
]
}
extension Test212 {
static var allTests = [
("test", Test212.test),
]
}
extension Test213 {
static var allTests = [
("test", Test213.test),
]
}
extension Test222 {
static var allTests = [
("test", Test222.test),
]
}
extension Test223 {
static var allTests = [
("test", Test223.test),
]
}
extension Test224 {
static var allTests = [
("test", Test224.test),
]
}
extension Test226 {
static var allTests = [
("test", Test226.test),
]
}
extension Test227 {
static var allTests = [
("test", Test227.test),
]
}
extension Test231 {
static var allTests = [
("test", Test231.test),
]
}
extension Test232 {
static var allTests = [
("test", Test232.test),
]
}
extension Test234 {
static var allTests = [
("test", Test234.test),
]
}
extension ThenableTests {
static var allTests = [
("testGet", ThenableTests.testGet),
("testCompactMap", ThenableTests.testCompactMap),
("testCompactMapThrows", ThenableTests.testCompactMapThrows),
("testRejectedPromiseCompactMap", ThenableTests.testRejectedPromiseCompactMap),
("testPMKErrorCompactMap", ThenableTests.testPMKErrorCompactMap),
("testCompactMapValues", ThenableTests.testCompactMapValues),
("testThenMap", ThenableTests.testThenMap),
("testThenFlatMap", ThenableTests.testThenFlatMap),
("testLastValueForEmpty", ThenableTests.testLastValueForEmpty),
("testFirstValueForEmpty", ThenableTests.testFirstValueForEmpty),
("testThenOffRejected", ThenableTests.testThenOffRejected),
]
}
extension WhenConcurrentTestCase_Swift {
static var allTests = [
("testWhen", WhenConcurrentTestCase_Swift.testWhen),
("testWhenEmptyGenerator", WhenConcurrentTestCase_Swift.testWhenEmptyGenerator),
("testWhenGeneratorError", WhenConcurrentTestCase_Swift.testWhenGeneratorError),
("testWhenConcurrency", WhenConcurrentTestCase_Swift.testWhenConcurrency),
("testWhenConcurrencyLessThanZero", WhenConcurrentTestCase_Swift.testWhenConcurrencyLessThanZero),
("testStopsDequeueingOnceRejected", WhenConcurrentTestCase_Swift.testStopsDequeueingOnceRejected),
]
}
extension WhenTests {
static var allTests = [
("testEmpty", WhenTests.testEmpty),
("testInt", WhenTests.testInt),
("testDoubleTuple", WhenTests.testDoubleTuple),
("testTripleTuple", WhenTests.testTripleTuple),
("testQuadrupleTuple", WhenTests.testQuadrupleTuple),
("testQuintupleTuple", WhenTests.testQuintupleTuple),
("testVoid", WhenTests.testVoid),
("testRejected", WhenTests.testRejected),
("testProgress", WhenTests.testProgress),
("testProgressDoesNotExceed100Percent", WhenTests.testProgressDoesNotExceed100Percent),
("testUnhandledErrorHandlerDoesNotFire", WhenTests.testUnhandledErrorHandlerDoesNotFire),
("testUnhandledErrorHandlerDoesNotFireForStragglers", WhenTests.testUnhandledErrorHandlerDoesNotFireForStragglers),
("testAllSealedRejectedFirstOneRejects", WhenTests.testAllSealedRejectedFirstOneRejects),
("testGuaranteeWhen", WhenTests.testGuaranteeWhen),
]
}
extension WrapTests {
static var allTests = [
("testSuccess", WrapTests.testSuccess),
("testError", WrapTests.testError),
("testInvalidCallingConvention", WrapTests.testInvalidCallingConvention),
("testInvertedCallingConvention", WrapTests.testInvertedCallingConvention),
("testNonOptionalFirstParameter", WrapTests.testNonOptionalFirstParameter),
("testVoidCompletionValue", WrapTests.testVoidCompletionValue),
("testVoidCompletionValue", WrapTests.testVoidCompletionValue),
("testIsFulfilled", WrapTests.testIsFulfilled),
("testPendingPromiseDeallocated", WrapTests.testPendingPromiseDeallocated),
]
}
extension ZalgoTests {
static var allTests = [
("test1", ZalgoTests.test1),
("test2", ZalgoTests.test2),
("test3", ZalgoTests.test3),
("test4", ZalgoTests.test4),
]
}
XCTMain([
testCase(AfterTests.allTests),
testCase(CancellationTests.allTests),
testCase(CatchableTests.allTests),
testCase(GuaranteeTests.allTests),
testCase(HangTests.allTests),
testCase(JoinTests.allTests),
testCase(PMKDefaultDispatchQueueTest.allTests),
testCase(PMKErrorTests.allTests),
testCase(PromiseTests.allTests),
testCase(RaceTests.allTests),
testCase(RegressionTests.allTests),
testCase(StressTests.allTests),
testCase(Test212.allTests),
testCase(Test213.allTests),
testCase(Test222.allTests),
testCase(Test223.allTests),
testCase(Test224.allTests),
testCase(Test226.allTests),
testCase(Test227.allTests),
testCase(Test231.allTests),
testCase(Test232.allTests),
testCase(Test234.allTests),
testCase(ThenableTests.allTests),
testCase(WhenConcurrentTestCase_Swift.allTests),
testCase(WhenTests.allTests),
testCase(WrapTests.allTests),
testCase(ZalgoTests.allTests),
])