RACSchedulerSpec.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. //
  2. // RACSchedulerSpec.m
  3. // ReactiveCocoa
  4. //
  5. // Created by Josh Abernathy on 11/29/12.
  6. // Copyright (c) 2012 GitHub, Inc. All rights reserved.
  7. //
  8. #import <Quick/Quick.h>
  9. #import <Nimble/Nimble.h>
  10. #import "RACScheduler.h"
  11. #import "RACScheduler+Private.h"
  12. #import "RACQueueScheduler+Subclass.h"
  13. #import "RACDisposable.h"
  14. #import <ReactiveCocoa/EXTScope.h>
  15. #import "RACTestExampleScheduler.h"
  16. #import <libkern/OSAtomic.h>
  17. // This shouldn't be used directly. Use the `expectCurrentSchedulers` block
  18. // below instead.
  19. static void expectCurrentSchedulersInner(NSArray *schedulers, NSMutableArray *currentSchedulerArray) {
  20. if (schedulers.count > 0) {
  21. RACScheduler *topScheduler = schedulers[0];
  22. [topScheduler schedule:^{
  23. RACScheduler *currentScheduler = RACScheduler.currentScheduler;
  24. if (currentScheduler != nil) [currentSchedulerArray addObject:currentScheduler];
  25. expectCurrentSchedulersInner([schedulers subarrayWithRange:NSMakeRange(1, schedulers.count - 1)], currentSchedulerArray);
  26. }];
  27. }
  28. }
  29. QuickSpecBegin(RACSchedulerSpec)
  30. qck_it(@"should know its current scheduler", ^{
  31. // Recursively schedules a block in each of the given schedulers and records
  32. // the +currentScheduler at each step. It then expects the array of
  33. // +currentSchedulers and the expected array to be equal.
  34. //
  35. // schedulers - The array of schedulers to recursively schedule.
  36. // expectedCurrentSchedulers - The array of +currentSchedulers to expect.
  37. void (^expectCurrentSchedulers)(NSArray *, NSArray *) = ^(NSArray *schedulers, NSArray *expectedCurrentSchedulers) {
  38. NSMutableArray *currentSchedulerArray = [NSMutableArray array];
  39. expectCurrentSchedulersInner(schedulers, currentSchedulerArray);
  40. expect(currentSchedulerArray).toEventually(equal(expectedCurrentSchedulers));
  41. };
  42. RACScheduler *backgroundScheduler = [RACScheduler scheduler];
  43. expectCurrentSchedulers(@[ backgroundScheduler, RACScheduler.immediateScheduler ], @[ backgroundScheduler, backgroundScheduler ]);
  44. expectCurrentSchedulers(@[ backgroundScheduler, RACScheduler.subscriptionScheduler ], @[ backgroundScheduler, backgroundScheduler ]);
  45. NSArray *mainThreadJumper = @[ RACScheduler.mainThreadScheduler, backgroundScheduler, RACScheduler.mainThreadScheduler ];
  46. expectCurrentSchedulers(mainThreadJumper, mainThreadJumper);
  47. NSArray *backgroundJumper = @[ backgroundScheduler, RACScheduler.mainThreadScheduler, backgroundScheduler ];
  48. expectCurrentSchedulers(backgroundJumper, backgroundJumper);
  49. });
  50. qck_describe(@"+mainThreadScheduler", ^{
  51. qck_it(@"should cancel scheduled blocks when disposed", ^{
  52. __block BOOL firstBlockRan = NO;
  53. __block BOOL secondBlockRan = NO;
  54. RACDisposable *disposable = [RACScheduler.mainThreadScheduler schedule:^{
  55. firstBlockRan = YES;
  56. }];
  57. expect(disposable).notTo(beNil());
  58. [RACScheduler.mainThreadScheduler schedule:^{
  59. secondBlockRan = YES;
  60. }];
  61. [disposable dispose];
  62. expect(@(secondBlockRan)).to(beFalsy());
  63. expect(@(secondBlockRan)).toEventually(beTruthy());
  64. expect(@(firstBlockRan)).to(beFalsy());
  65. });
  66. qck_it(@"should schedule future blocks", ^{
  67. __block BOOL done = NO;
  68. [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{
  69. done = YES;
  70. }];
  71. expect(@(done)).to(beFalsy());
  72. expect(@(done)).toEventually(beTruthy());
  73. });
  74. qck_it(@"should cancel future blocks when disposed", ^{
  75. __block BOOL firstBlockRan = NO;
  76. __block BOOL secondBlockRan = NO;
  77. RACDisposable *disposable = [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{
  78. firstBlockRan = YES;
  79. }];
  80. expect(disposable).notTo(beNil());
  81. [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{
  82. secondBlockRan = YES;
  83. }];
  84. [disposable dispose];
  85. expect(@(secondBlockRan)).to(beFalsy());
  86. expect(@(secondBlockRan)).toEventually(beTruthy());
  87. expect(@(firstBlockRan)).to(beFalsy());
  88. });
  89. qck_it(@"should schedule recurring blocks", ^{
  90. __block NSUInteger count = 0;
  91. RACDisposable *disposable = [RACScheduler.mainThreadScheduler after:[NSDate date] repeatingEvery:0.05 withLeeway:0 schedule:^{
  92. count++;
  93. }];
  94. expect(@(count)).to(equal(@0));
  95. expect(@(count)).toEventually(beGreaterThanOrEqualTo(@1));
  96. expect(@(count)).toEventually(beGreaterThanOrEqualTo(@2));
  97. expect(@(count)).toEventually(beGreaterThanOrEqualTo(@3));
  98. [disposable dispose];
  99. [NSRunLoop.mainRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
  100. expect(@(count)).to(beGreaterThanOrEqualTo(@3));
  101. });
  102. });
  103. qck_describe(@"+scheduler", ^{
  104. __block RACScheduler *scheduler;
  105. __block NSDate * (^futureDate)(void);
  106. qck_beforeEach(^{
  107. scheduler = [RACScheduler scheduler];
  108. futureDate = ^{
  109. return [NSDate dateWithTimeIntervalSinceNow:0.01];
  110. };
  111. });
  112. qck_it(@"should cancel scheduled blocks when disposed", ^{
  113. __block BOOL firstBlockRan = NO;
  114. __block BOOL secondBlockRan = NO;
  115. // Start off on the scheduler so the enqueued blocks won't run until we
  116. // return.
  117. [scheduler schedule:^{
  118. RACDisposable *disposable = [scheduler schedule:^{
  119. firstBlockRan = YES;
  120. }];
  121. expect(disposable).notTo(beNil());
  122. [scheduler schedule:^{
  123. secondBlockRan = YES;
  124. }];
  125. [disposable dispose];
  126. }];
  127. expect(@(secondBlockRan)).toEventually(beTruthy());
  128. expect(@(firstBlockRan)).to(beFalsy());
  129. });
  130. qck_it(@"should schedule future blocks", ^{
  131. __block BOOL done = NO;
  132. [scheduler after:futureDate() schedule:^{
  133. done = YES;
  134. }];
  135. expect(@(done)).to(beFalsy());
  136. expect(@(done)).toEventually(beTruthy());
  137. });
  138. qck_it(@"should cancel future blocks when disposed", ^{
  139. __block BOOL firstBlockRan = NO;
  140. __block BOOL secondBlockRan = NO;
  141. NSDate *date = futureDate();
  142. RACDisposable *disposable = [scheduler after:date schedule:^{
  143. firstBlockRan = YES;
  144. }];
  145. expect(disposable).notTo(beNil());
  146. [disposable dispose];
  147. [scheduler after:date schedule:^{
  148. secondBlockRan = YES;
  149. }];
  150. expect(@(secondBlockRan)).to(beFalsy());
  151. expect(@(secondBlockRan)).toEventually(beTruthy());
  152. expect(@(firstBlockRan)).to(beFalsy());
  153. });
  154. qck_it(@"should schedule recurring blocks", ^{
  155. __block NSUInteger count = 0;
  156. RACDisposable *disposable = [scheduler after:[NSDate date] repeatingEvery:0.05 withLeeway:0 schedule:^{
  157. count++;
  158. }];
  159. expect(@(count)).to(beGreaterThanOrEqualTo(@0));
  160. expect(@(count)).toEventually(beGreaterThanOrEqualTo(@1));
  161. expect(@(count)).toEventually(beGreaterThanOrEqualTo(@2));
  162. expect(@(count)).toEventually(beGreaterThanOrEqualTo(@3));
  163. [disposable dispose];
  164. [NSThread sleepForTimeInterval:0.1];
  165. expect(@(count)).to(beGreaterThanOrEqualTo(@3));
  166. });
  167. });
  168. qck_describe(@"+subscriptionScheduler", ^{
  169. qck_describe(@"setting +currentScheduler", ^{
  170. __block RACScheduler *currentScheduler;
  171. qck_beforeEach(^{
  172. currentScheduler = nil;
  173. });
  174. qck_it(@"should be the +mainThreadScheduler when scheduled from the main queue", ^{
  175. dispatch_async(dispatch_get_main_queue(), ^{
  176. [RACScheduler.subscriptionScheduler schedule:^{
  177. currentScheduler = RACScheduler.currentScheduler;
  178. }];
  179. });
  180. expect(currentScheduler).toEventually(equal(RACScheduler.mainThreadScheduler));
  181. });
  182. qck_it(@"should be a +scheduler when scheduled from an unknown queue", ^{
  183. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  184. [RACScheduler.subscriptionScheduler schedule:^{
  185. currentScheduler = RACScheduler.currentScheduler;
  186. }];
  187. });
  188. expect(currentScheduler).toEventuallyNot(beNil());
  189. expect(currentScheduler).notTo(equal(RACScheduler.mainThreadScheduler));
  190. });
  191. qck_it(@"should equal the background scheduler from which the block was scheduled", ^{
  192. RACScheduler *backgroundScheduler = [RACScheduler scheduler];
  193. [backgroundScheduler schedule:^{
  194. [RACScheduler.subscriptionScheduler schedule:^{
  195. currentScheduler = RACScheduler.currentScheduler;
  196. }];
  197. }];
  198. expect(currentScheduler).toEventually(equal(backgroundScheduler));
  199. });
  200. });
  201. qck_it(@"should execute scheduled blocks immediately if it's in a scheduler already", ^{
  202. __block BOOL done = NO;
  203. __block BOOL executedImmediately = NO;
  204. [[RACScheduler scheduler] schedule:^{
  205. [RACScheduler.subscriptionScheduler schedule:^{
  206. executedImmediately = YES;
  207. }];
  208. done = YES;
  209. }];
  210. expect(@(done)).toEventually(beTruthy());
  211. expect(@(executedImmediately)).to(beTruthy());
  212. });
  213. });
  214. qck_describe(@"+immediateScheduler", ^{
  215. qck_it(@"should immediately execute scheduled blocks", ^{
  216. __block BOOL executed = NO;
  217. RACDisposable *disposable = [RACScheduler.immediateScheduler schedule:^{
  218. executed = YES;
  219. }];
  220. expect(disposable).to(beNil());
  221. expect(@(executed)).to(beTruthy());
  222. });
  223. qck_it(@"should block for future scheduled blocks", ^{
  224. __block BOOL executed = NO;
  225. RACDisposable *disposable = [RACScheduler.immediateScheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{
  226. executed = YES;
  227. }];
  228. expect(@(executed)).to(beTruthy());
  229. expect(disposable).to(beNil());
  230. });
  231. });
  232. qck_describe(@"-scheduleRecursiveBlock:", ^{
  233. qck_describe(@"with a synchronous scheduler", ^{
  234. qck_it(@"should behave like a normal block when it doesn't invoke itself", ^{
  235. __block BOOL executed = NO;
  236. [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) {
  237. expect(@(executed)).to(beFalsy());
  238. executed = YES;
  239. }];
  240. expect(@(executed)).to(beTruthy());
  241. });
  242. qck_it(@"should reschedule itself after the caller completes", ^{
  243. __block NSUInteger count = 0;
  244. [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) {
  245. NSUInteger thisCount = ++count;
  246. if (thisCount < 3) {
  247. recurse();
  248. // The block shouldn't have been invoked again yet, only
  249. // scheduled.
  250. expect(@(count)).to(equal(@(thisCount)));
  251. }
  252. }];
  253. expect(@(count)).to(equal(@3));
  254. });
  255. qck_it(@"should unroll deep recursion", ^{
  256. static const NSUInteger depth = 100000;
  257. __block NSUInteger scheduleCount = 0;
  258. [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) {
  259. scheduleCount++;
  260. if (scheduleCount < depth) recurse();
  261. }];
  262. expect(@(scheduleCount)).to(equal(@(depth)));
  263. });
  264. });
  265. qck_describe(@"with an asynchronous scheduler", ^{
  266. qck_it(@"should behave like a normal block when it doesn't invoke itself", ^{
  267. __block BOOL executed = NO;
  268. [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) {
  269. expect(@(executed)).to(beFalsy());
  270. executed = YES;
  271. }];
  272. expect(@(executed)).toEventually(beTruthy());
  273. });
  274. qck_it(@"should reschedule itself after the caller completes", ^{
  275. __block NSUInteger count = 0;
  276. [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) {
  277. NSUInteger thisCount = ++count;
  278. if (thisCount < 3) {
  279. recurse();
  280. // The block shouldn't have been invoked again yet, only
  281. // scheduled.
  282. expect(@(count)).to(equal(@(thisCount)));
  283. }
  284. }];
  285. expect(@(count)).toEventually(equal(@3));
  286. });
  287. qck_it(@"should reschedule when invoked asynchronously", ^{
  288. __block NSUInteger count = 0;
  289. RACScheduler *asynchronousScheduler = [RACScheduler scheduler];
  290. [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) {
  291. [asynchronousScheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{
  292. NSUInteger thisCount = ++count;
  293. if (thisCount < 3) {
  294. recurse();
  295. // The block shouldn't have been invoked again yet, only
  296. // scheduled.
  297. expect(@(count)).to(equal(@(thisCount)));
  298. }
  299. }];
  300. }];
  301. expect(@(count)).toEventually(equal(@3));
  302. });
  303. qck_it(@"shouldn't reschedule itself when disposed", ^{
  304. __block NSUInteger count = 0;
  305. __block RACDisposable *disposable = [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) {
  306. ++count;
  307. expect(disposable).notTo(beNil());
  308. [disposable dispose];
  309. recurse();
  310. }];
  311. expect(@(count)).toEventually(equal(@1));
  312. });
  313. });
  314. });
  315. qck_describe(@"subclassing", ^{
  316. __block RACTestExampleScheduler *scheduler;
  317. qck_beforeEach(^{
  318. scheduler = [[RACTestExampleScheduler alloc] initWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
  319. });
  320. qck_it(@"should invoke blocks scheduled with -schedule:", ^{
  321. __block BOOL invoked = NO;
  322. [scheduler schedule:^{
  323. invoked = YES;
  324. }];
  325. expect(@(invoked)).toEventually(beTruthy());
  326. });
  327. qck_it(@"should invoke blocks scheduled with -after:schedule:", ^{
  328. __block BOOL invoked = NO;
  329. [scheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{
  330. invoked = YES;
  331. }];
  332. expect(@(invoked)).toEventually(beTruthy());
  333. });
  334. qck_it(@"should set a valid current scheduler", ^{
  335. __block RACScheduler *currentScheduler;
  336. [scheduler schedule:^{
  337. currentScheduler = RACScheduler.currentScheduler;
  338. }];
  339. expect(currentScheduler).toEventually(equal(scheduler));
  340. });
  341. });
  342. QuickSpecEnd