| // | |
| // RACSchedulerSpec.m | |
| // ReactiveCocoa | |
| // | |
| // Created by Josh Abernathy on 11/29/12. | |
| // Copyright (c) 2012 GitHub, Inc. All rights reserved. | |
| // | |
| #import "RACScheduler.h" | |
| #import "RACScheduler+Private.h" | |
| #import "RACQueueScheduler+Subclass.h" | |
| #import "RACDisposable.h" | |
| #import "EXTScope.h" | |
| #import "RACTestExampleScheduler.h" | |
| #import <libkern/OSAtomic.h> | |
| // This shouldn't be used directly. Use the `expectCurrentSchedulers` block | |
| // below instead. | |
| static void expectCurrentSchedulersInner(NSArray *schedulers, NSMutableArray *currentSchedulerArray) { | |
| if (schedulers.count > 0) { | |
| RACScheduler *topScheduler = schedulers[0]; | |
| [topScheduler schedule:^{ | |
| RACScheduler *currentScheduler = RACScheduler.currentScheduler; | |
| if (currentScheduler != nil) [currentSchedulerArray addObject:currentScheduler]; | |
| expectCurrentSchedulersInner([schedulers subarrayWithRange:NSMakeRange(1, schedulers.count - 1)], currentSchedulerArray); | |
| }]; | |
| } | |
| } | |
| SpecBegin(RACScheduler) | |
| it(@"should know its current scheduler", ^{ | |
| // Recursively schedules a block in each of the given schedulers and records | |
| // the +currentScheduler at each step. It then expects the array of | |
| // +currentSchedulers and the expected array to be equal. | |
| // | |
| // schedulers - The array of schedulers to recursively schedule. | |
| // expectedCurrentSchedulers - The array of +currentSchedulers to expect. | |
| void (^expectCurrentSchedulers)(NSArray *, NSArray *) = ^(NSArray *schedulers, NSArray *expectedCurrentSchedulers) { | |
| NSMutableArray *currentSchedulerArray = [NSMutableArray array]; | |
| expectCurrentSchedulersInner(schedulers, currentSchedulerArray); | |
| expect(currentSchedulerArray).will.equal(expectedCurrentSchedulers); | |
| }; | |
| RACScheduler *backgroundScheduler = [RACScheduler scheduler]; | |
| expectCurrentSchedulers(@[ backgroundScheduler, RACScheduler.immediateScheduler ], @[ backgroundScheduler, backgroundScheduler ]); | |
| expectCurrentSchedulers(@[ backgroundScheduler, RACScheduler.subscriptionScheduler ], @[ backgroundScheduler, backgroundScheduler ]); | |
| NSArray *mainThreadJumper = @[ RACScheduler.mainThreadScheduler, backgroundScheduler, RACScheduler.mainThreadScheduler ]; | |
| expectCurrentSchedulers(mainThreadJumper, mainThreadJumper); | |
| NSArray *backgroundJumper = @[ backgroundScheduler, RACScheduler.mainThreadScheduler, backgroundScheduler ]; | |
| expectCurrentSchedulers(backgroundJumper, backgroundJumper); | |
| }); | |
| describe(@"+mainThreadScheduler", ^{ | |
| it(@"should cancel scheduled blocks when disposed", ^{ | |
| __block BOOL firstBlockRan = NO; | |
| __block BOOL secondBlockRan = NO; | |
| RACDisposable *disposable = [RACScheduler.mainThreadScheduler schedule:^{ | |
| firstBlockRan = YES; | |
| }]; | |
| expect(disposable).notTo.beNil(); | |
| [RACScheduler.mainThreadScheduler schedule:^{ | |
| secondBlockRan = YES; | |
| }]; | |
| [disposable dispose]; | |
| expect(secondBlockRan).to.beFalsy(); | |
| expect(secondBlockRan).will.beTruthy(); | |
| expect(firstBlockRan).to.beFalsy(); | |
| }); | |
| it(@"should schedule future blocks", ^{ | |
| __block BOOL done = NO; | |
| [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ | |
| done = YES; | |
| }]; | |
| expect(done).to.beFalsy(); | |
| expect(done).will.beTruthy(); | |
| }); | |
| it(@"should cancel future blocks when disposed", ^{ | |
| __block BOOL firstBlockRan = NO; | |
| __block BOOL secondBlockRan = NO; | |
| RACDisposable *disposable = [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ | |
| firstBlockRan = YES; | |
| }]; | |
| expect(disposable).notTo.beNil(); | |
| [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ | |
| secondBlockRan = YES; | |
| }]; | |
| [disposable dispose]; | |
| expect(secondBlockRan).to.beFalsy(); | |
| expect(secondBlockRan).will.beTruthy(); | |
| expect(firstBlockRan).to.beFalsy(); | |
| }); | |
| it(@"should schedule recurring blocks", ^{ | |
| __block NSUInteger count = 0; | |
| RACDisposable *disposable = [RACScheduler.mainThreadScheduler after:[NSDate date] repeatingEvery:0.05 withLeeway:0 schedule:^{ | |
| count++; | |
| }]; | |
| expect(count).to.equal(0); | |
| expect(count).will.equal(1); | |
| expect(count).will.equal(2); | |
| expect(count).will.equal(3); | |
| [disposable dispose]; | |
| [NSRunLoop.mainRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; | |
| expect(count).to.equal(3); | |
| }); | |
| }); | |
| describe(@"+scheduler", ^{ | |
| __block RACScheduler *scheduler; | |
| __block NSDate * (^futureDate)(void); | |
| beforeEach(^{ | |
| scheduler = [RACScheduler scheduler]; | |
| futureDate = ^{ | |
| return [NSDate dateWithTimeIntervalSinceNow:0.01]; | |
| }; | |
| }); | |
| it(@"should cancel scheduled blocks when disposed", ^{ | |
| __block BOOL firstBlockRan = NO; | |
| __block BOOL secondBlockRan = NO; | |
| // Start off on the scheduler so the enqueued blocks won't run until we | |
| // return. | |
| [scheduler schedule:^{ | |
| RACDisposable *disposable = [scheduler schedule:^{ | |
| firstBlockRan = YES; | |
| }]; | |
| expect(disposable).notTo.beNil(); | |
| [scheduler schedule:^{ | |
| secondBlockRan = YES; | |
| }]; | |
| [disposable dispose]; | |
| }]; | |
| expect(secondBlockRan).will.beTruthy(); | |
| expect(firstBlockRan).to.beFalsy(); | |
| }); | |
| it(@"should schedule future blocks", ^{ | |
| __block BOOL done = NO; | |
| [scheduler after:futureDate() schedule:^{ | |
| done = YES; | |
| }]; | |
| expect(done).to.beFalsy(); | |
| expect(done).will.beTruthy(); | |
| }); | |
| it(@"should cancel future blocks when disposed", ^{ | |
| __block BOOL firstBlockRan = NO; | |
| __block BOOL secondBlockRan = NO; | |
| NSDate *date = futureDate(); | |
| RACDisposable *disposable = [scheduler after:date schedule:^{ | |
| firstBlockRan = YES; | |
| }]; | |
| expect(disposable).notTo.beNil(); | |
| [disposable dispose]; | |
| [scheduler after:date schedule:^{ | |
| secondBlockRan = YES; | |
| }]; | |
| expect(secondBlockRan).to.beFalsy(); | |
| expect(secondBlockRan).will.beTruthy(); | |
| expect(firstBlockRan).to.beFalsy(); | |
| }); | |
| it(@"should schedule recurring blocks", ^{ | |
| __block NSUInteger count = 0; | |
| RACDisposable *disposable = [scheduler after:[NSDate date] repeatingEvery:0.05 withLeeway:0 schedule:^{ | |
| count++; | |
| }]; | |
| expect(count).to.equal(0); | |
| expect(count).will.equal(1); | |
| expect(count).will.equal(2); | |
| expect(count).will.equal(3); | |
| [disposable dispose]; | |
| [NSThread sleepForTimeInterval:0.1]; | |
| expect(count).to.equal(3); | |
| }); | |
| }); | |
| describe(@"+subscriptionScheduler", ^{ | |
| describe(@"setting +currentScheduler", ^{ | |
| __block RACScheduler *currentScheduler; | |
| beforeEach(^{ | |
| currentScheduler = nil; | |
| }); | |
| it(@"should be the +mainThreadScheduler when scheduled from the main queue", ^{ | |
| dispatch_async(dispatch_get_main_queue(), ^{ | |
| [RACScheduler.subscriptionScheduler schedule:^{ | |
| currentScheduler = RACScheduler.currentScheduler; | |
| }]; | |
| }); | |
| expect(currentScheduler).will.equal(RACScheduler.mainThreadScheduler); | |
| }); | |
| it(@"should be a +scheduler when scheduled from an unknown queue", ^{ | |
| dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | |
| [RACScheduler.subscriptionScheduler schedule:^{ | |
| currentScheduler = RACScheduler.currentScheduler; | |
| }]; | |
| }); | |
| expect(currentScheduler).willNot.beNil(); | |
| expect(currentScheduler).notTo.equal(RACScheduler.mainThreadScheduler); | |
| }); | |
| it(@"should equal the background scheduler from which the block was scheduled", ^{ | |
| RACScheduler *backgroundScheduler = [RACScheduler scheduler]; | |
| [backgroundScheduler schedule:^{ | |
| [RACScheduler.subscriptionScheduler schedule:^{ | |
| currentScheduler = RACScheduler.currentScheduler; | |
| }]; | |
| }]; | |
| expect(currentScheduler).will.equal(backgroundScheduler); | |
| }); | |
| }); | |
| it(@"should execute scheduled blocks immediately if it's in a scheduler already", ^{ | |
| __block BOOL done = NO; | |
| __block BOOL executedImmediately = NO; | |
| [[RACScheduler scheduler] schedule:^{ | |
| [RACScheduler.subscriptionScheduler schedule:^{ | |
| executedImmediately = YES; | |
| }]; | |
| done = YES; | |
| }]; | |
| expect(done).will.beTruthy(); | |
| expect(executedImmediately).to.beTruthy(); | |
| }); | |
| }); | |
| describe(@"+immediateScheduler", ^{ | |
| it(@"should immediately execute scheduled blocks", ^{ | |
| __block BOOL executed = NO; | |
| RACDisposable *disposable = [RACScheduler.immediateScheduler schedule:^{ | |
| executed = YES; | |
| }]; | |
| expect(disposable).to.beNil(); | |
| expect(executed).to.beTruthy(); | |
| }); | |
| it(@"should block for future scheduled blocks", ^{ | |
| __block BOOL executed = NO; | |
| RACDisposable *disposable = [RACScheduler.immediateScheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ | |
| executed = YES; | |
| }]; | |
| expect(executed).to.beTruthy(); | |
| expect(disposable).to.beNil(); | |
| }); | |
| }); | |
| describe(@"-scheduleRecursiveBlock:", ^{ | |
| describe(@"with a synchronous scheduler", ^{ | |
| it(@"should behave like a normal block when it doesn't invoke itself", ^{ | |
| __block BOOL executed = NO; | |
| [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { | |
| expect(executed).to.beFalsy(); | |
| executed = YES; | |
| }]; | |
| expect(executed).to.beTruthy(); | |
| }); | |
| it(@"should reschedule itself after the caller completes", ^{ | |
| __block NSUInteger count = 0; | |
| [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { | |
| NSUInteger thisCount = ++count; | |
| if (thisCount < 3) { | |
| recurse(); | |
| // The block shouldn't have been invoked again yet, only | |
| // scheduled. | |
| expect(count).to.equal(thisCount); | |
| } | |
| }]; | |
| expect(count).to.equal(3); | |
| }); | |
| }); | |
| describe(@"with an asynchronous scheduler", ^{ | |
| it(@"should behave like a normal block when it doesn't invoke itself", ^{ | |
| __block BOOL executed = NO; | |
| [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { | |
| expect(executed).to.beFalsy(); | |
| executed = YES; | |
| }]; | |
| expect(executed).will.beTruthy(); | |
| }); | |
| it(@"should reschedule itself after the caller completes", ^{ | |
| __block NSUInteger count = 0; | |
| [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { | |
| NSUInteger thisCount = ++count; | |
| if (thisCount < 3) { | |
| recurse(); | |
| // The block shouldn't have been invoked again yet, only | |
| // scheduled. | |
| expect(count).to.equal(thisCount); | |
| } | |
| }]; | |
| expect(count).will.equal(3); | |
| }); | |
| it(@"should reschedule when invoked asynchronously", ^{ | |
| __block NSUInteger count = 0; | |
| RACScheduler *asynchronousScheduler = [RACScheduler scheduler]; | |
| [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { | |
| [asynchronousScheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ | |
| NSUInteger thisCount = ++count; | |
| if (thisCount < 3) { | |
| recurse(); | |
| // The block shouldn't have been invoked again yet, only | |
| // scheduled. | |
| expect(count).to.equal(thisCount); | |
| } | |
| }]; | |
| }]; | |
| expect(count).will.equal(3); | |
| }); | |
| it(@"shouldn't reschedule itself when disposed", ^{ | |
| __block NSUInteger count = 0; | |
| __block RACDisposable *disposable = [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { | |
| ++count; | |
| expect(disposable).notTo.beNil(); | |
| [disposable dispose]; | |
| recurse(); | |
| }]; | |
| expect(count).will.equal(1); | |
| }); | |
| }); | |
| }); | |
| describe(@"subclassing", ^{ | |
| __block RACTestExampleScheduler *scheduler; | |
| beforeEach(^{ | |
| scheduler = [[RACTestExampleScheduler alloc] initWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; | |
| }); | |
| it(@"should invoke blocks scheduled with -schedule:", ^{ | |
| __block BOOL invoked = NO; | |
| [scheduler schedule:^{ | |
| invoked = YES; | |
| }]; | |
| expect(invoked).will.beTruthy(); | |
| }); | |
| it(@"should invoke blocks scheduled with -after:schedule:", ^{ | |
| __block BOOL invoked = NO; | |
| [scheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ | |
| invoked = YES; | |
| }]; | |
| expect(invoked).will.beTruthy(); | |
| }); | |
| it(@"should set a valid current scheduler", ^{ | |
| __block RACScheduler *currentScheduler; | |
| [scheduler schedule:^{ | |
| currentScheduler = RACScheduler.currentScheduler; | |
| }]; | |
| expect(currentScheduler).will.equal(scheduler); | |
| }); | |
| }); | |
| SpecEnd |