blob: 738eff87630d7c007ab2f0c99b51fc67d32ea2e2 [file] [log] [blame] [edit]
//
// 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