diff --git a/PromiseKit.xcodeproj/project.pbxproj b/PromiseKit.xcodeproj/project.pbxproj index 87230d543..ea9963422 100644 --- a/PromiseKit.xcodeproj/project.pbxproj +++ b/PromiseKit.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 63CF6D80203CD19200EC8927 /* ThenableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63CF6D7F203CD19200EC8927 /* ThenableTests.swift */; }; 63D9B2EF203385FD0075C00B /* race.m in Sources */ = {isa = PBXBuildFile; fileRef = 63D9B2EE203385FD0075C00B /* race.m */; }; 63D9B2F120338D5D0075C00B /* Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D9B2F020338D5D0075C00B /* Deprecations.swift */; }; + 9E66231626FE5A8C00FA25CB /* RaceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E66231526FE5A8C00FA25CB /* RaceTests.m */; }; C013F7382048E3B6006B57B1 /* MockNodeEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F7372048E3B6006B57B1 /* MockNodeEnvironment.swift */; }; C013F73A2049076A006B57B1 /* JSPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F7392049076A006B57B1 /* JSPromise.swift */; }; C013F73C20494291006B57B1 /* JSAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013F73B20494291006B57B1 /* JSAdapter.swift */; }; @@ -223,6 +224,7 @@ 63CF6D7F203CD19200EC8927 /* ThenableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThenableTests.swift; sourceTree = ""; }; 63D9B2EE203385FD0075C00B /* race.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = race.m; path = Sources/race.m; sourceTree = ""; }; 63D9B2F020338D5D0075C00B /* Deprecations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Deprecations.swift; path = Sources/Deprecations.swift; sourceTree = ""; }; + 9E66231526FE5A8C00FA25CB /* RaceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RaceTests.m; sourceTree = ""; }; C013F7372048E3B6006B57B1 /* MockNodeEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MockNodeEnvironment.swift; path = "Tests/JS-A+/MockNodeEnvironment.swift"; sourceTree = ""; }; C013F7392049076A006B57B1 /* JSPromise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = JSPromise.swift; path = "Tests/JS-A+/JSPromise.swift"; sourceTree = ""; }; C013F73B20494291006B57B1 /* JSAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = JSAdapter.swift; path = "Tests/JS-A+/JSAdapter.swift"; sourceTree = ""; }; @@ -291,6 +293,7 @@ 630A8053203CEF6800D25F23 /* JoinTests.m */, 630A8054203CEF6800D25F23 /* HangTests.m */, 630A8055203CEF6800D25F23 /* WhenTests.m */, + 9E66231526FE5A8C00FA25CB /* RaceTests.m */, ); name = CoreObjC; path = Tests/CoreObjC; @@ -684,6 +687,7 @@ 630A805B203CF67800D25F23 /* DefaultDispatchQueueTests.swift in Sources */, 635D641D1D59635300BC0AF5 /* PromiseTests.swift in Sources */, 63CF6D7C203CCDAB00EC8927 /* GuaranteeTests.swift in Sources */, + 9E66231626FE5A8C00FA25CB /* RaceTests.m in Sources */, 639BF757203DF03100FA577B /* Utilities.swift in Sources */, 635D64261D59635300BC0AF5 /* WhenResolvedTests.swift in Sources */, 635D64231D59635300BC0AF5 /* AfterTests.swift in Sources */, diff --git a/Sources/race.m b/Sources/race.m index accf80dc9..92afed630 100644 --- a/Sources/race.m +++ b/Sources/race.m @@ -6,6 +6,9 @@ // ^^ OSAtomicDecrement32 is deprecated on watchOS AnyPromise *PMKRace(NSArray *promises) { + if (promises == nil || promises.count == 0) + return [AnyPromise promiseWithValue:[NSError errorWithDomain:PMKErrorDomain code:PMKInvalidUsageError userInfo:@{NSLocalizedDescriptionKey: @"PMKRace(nil)"}]]; + return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) { for (AnyPromise *promise in promises) { [promise __pipe:resolve]; @@ -21,6 +24,9 @@ @return The promise that was fulfilled first. */ AnyPromise *PMKRaceFulfilled(NSArray *promises) { + if (promises == nil || promises.count == 0) + return [AnyPromise promiseWithValue:[NSError errorWithDomain:PMKErrorDomain code:PMKInvalidUsageError userInfo:@{NSLocalizedDescriptionKey: @"PMKRaceFulfilled(nil)"}]]; + __block int32_t countdown = (int32_t)[promises count]; return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) { diff --git a/Tests/CoreObjC/AnyPromiseTests.m b/Tests/CoreObjC/AnyPromiseTests.m index 0e6fe2993..d0726cf78 100644 --- a/Tests/CoreObjC/AnyPromiseTests.m +++ b/Tests/CoreObjC/AnyPromiseTests.m @@ -789,54 +789,6 @@ - (void)test_promiseWithValue { 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(10), PMKAfter(20), p]).then(^(id obj){ - XCTAssertEqual(2, [obj integerValue]); - [ex fulfill]; - }); - [self waitForExpectationsWithTimeout:1 handler:nil]; -} - -- (void)test_race_fullfilled { - id ex = [self expectationWithDescription:@""]; - NSArray* promises = @[ - PMKAfter(1).then(^{ return dummyWithCode(1); }), - PMKAfter(2).then(^{ return dummyWithCode(2); }), - PMKAfter(5).then(^{ return @1; }), - PMKAfter(4).then(^{ return @2; }), - PMKAfter(3).then(^{ return dummyWithCode(3); }) - ]; - PMKRaceFulfilled(promises).then(^(id obj){ - XCTAssertEqual(2, [obj integerValue]); - [ex fulfill]; - }).catch(^{ - XCTFail(); - [ex fulfill]; - }); - [self waitForExpectationsWithTimeout:10 handler:nil]; -} - -- (void)test_race_fullfilled_with_no_winner { - id ex = [self expectationWithDescription:@""]; - NSArray* promises = @[ - PMKAfter(1).then(^{ return dummyWithCode(1); }), - PMKAfter(2).then(^{ return dummyWithCode(2); }), - PMKAfter(3).then(^{ return dummyWithCode(3); }) - ]; - PMKRaceFulfilled(promises).then(^(id obj){ - XCTFail(); - [ex fulfill]; - }).catch(^(NSError *e){ - XCTAssertEqual(e.domain, PMKErrorDomain); - XCTAssertEqual(e.code, PMKNoWinnerError); - XCTAssertEqualObjects(e.userInfo[NSLocalizedDescriptionKey], @"PMKRaceFulfilled(nil)"); - [ex fulfill]; - }); - [self waitForExpectationsWithTimeout:10 handler:nil]; -} - - (void)testInBackground { id ex = [self expectationWithDescription:@""]; PMKAfter(0.1).thenInBackground(^{ [ex fulfill]; }); diff --git a/Tests/CoreObjC/RaceTests.m b/Tests/CoreObjC/RaceTests.m new file mode 100644 index 000000000..e4764409c --- /dev/null +++ b/Tests/CoreObjC/RaceTests.m @@ -0,0 +1,88 @@ +@import Foundation; +@import PromiseKit; +@import XCTest; +#define PMKTestErrorDomain @"PMKTestErrorDomain" + +static inline NSError *dummyWithCode(NSInteger code) { + return [NSError errorWithDomain:PMKTestErrorDomain code:rand() userInfo:@{NSLocalizedDescriptionKey: @(code).stringValue}]; +} + +@interface RaceTests : XCTestCase @end @implementation RaceTests + +- (void)test_race { + id ex = [self expectationWithDescription:@""]; + id p = PMKAfter(0.1).then(^{ return @2; }); + PMKRace(@[PMKAfter(10), PMKAfter(20), p]).then(^(id obj){ + XCTAssertEqual(2, [obj integerValue]); + [ex fulfill]; + }); + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)test_race_empty { + id ex = [self expectationWithDescription:@""]; + PMKRace(@[]).then(^(NSArray* array){ + XCTFail(); + [ex fulfill]; + }).catch(^(NSError *e){ + XCTAssertEqual(e.domain, PMKErrorDomain); + XCTAssertEqual(e.code, PMKInvalidUsageError); + XCTAssertEqualObjects(e.userInfo[NSLocalizedDescriptionKey], @"PMKRace(nil)"); + [ex fulfill]; + }); + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)test_race_fullfilled { + id ex = [self expectationWithDescription:@""]; + NSArray* promises = @[ + PMKAfter(1).then(^{ return dummyWithCode(1); }), + PMKAfter(2).then(^{ return dummyWithCode(2); }), + PMKAfter(5).then(^{ return @1; }), + PMKAfter(4).then(^{ return @2; }), + PMKAfter(3).then(^{ return dummyWithCode(3); }) + ]; + PMKRaceFulfilled(promises).then(^(id obj){ + XCTAssertEqual(2, [obj integerValue]); + [ex fulfill]; + }).catch(^{ + XCTFail(); + [ex fulfill]; + }); + [self waitForExpectationsWithTimeout:10 handler:nil]; +} + +- (void)test_race_fulfilled_empty { + id ex = [self expectationWithDescription:@""]; + PMKRaceFulfilled(@[]).then(^(NSArray* array){ + XCTFail(); + [ex fulfill]; + }).catch(^(NSError *e){ + XCTAssertEqual(e.domain, PMKErrorDomain); + XCTAssertEqual(e.code, PMKInvalidUsageError); + XCTAssertEqualObjects(e.userInfo[NSLocalizedDescriptionKey], @"PMKRaceFulfilled(nil)"); + [ex fulfill]; + }); + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)test_race_fullfilled_with_no_winner { + id ex = [self expectationWithDescription:@""]; + NSArray* promises = @[ + PMKAfter(1).then(^{ return dummyWithCode(1); }), + PMKAfter(2).then(^{ return dummyWithCode(2); }), + PMKAfter(3).then(^{ return dummyWithCode(3); }) + ]; + PMKRaceFulfilled(promises).then(^(id obj){ + XCTFail(); + [ex fulfill]; + }).catch(^(NSError *e){ + XCTAssertEqual(e.domain, PMKErrorDomain); + XCTAssertEqual(e.code, PMKNoWinnerError); + XCTAssertEqualObjects(e.userInfo[NSLocalizedDescriptionKey], @"PMKRaceFulfilled(nil)"); + [ex fulfill]; + }); + [self waitForExpectationsWithTimeout:10 handler:nil]; +} + +@end