| // | |
| // RACBacktraceSpec.m | |
| // ReactiveCocoa | |
| // | |
| // Created by Justin Spahr-Summers on 2012-12-24. | |
| // Copyright (c) 2012 GitHub, Inc. All rights reserved. | |
| // | |
| #import "RACBacktrace.h" | |
| #import "NSArray+RACSequenceAdditions.h" | |
| #import "RACReplaySubject.h" | |
| #import "RACScheduler.h" | |
| #import "RACSequence.h" | |
| #import "RACSignal+Operations.h" | |
| #ifdef DEBUG | |
| static RACBacktrace *previousBacktrace; | |
| static void capturePreviousBacktrace(void *context) { | |
| previousBacktrace = [RACBacktrace backtrace].previousThreadBacktrace; | |
| } | |
| typedef struct { | |
| dispatch_queue_t queue; | |
| NSUInteger i; | |
| __unsafe_unretained RACSubject *doneSubject; | |
| } RACDeepRecursionContext; | |
| static void recurseDeeply(void *ptr) { | |
| RACDeepRecursionContext *context = ptr; | |
| if (context->i++ < 10000) { | |
| rac_dispatch_async_f(context->queue, context, recurseDeeply); | |
| } else { | |
| [context->doneSubject sendCompleted]; | |
| } | |
| } | |
| SpecBegin(RACBacktrace) | |
| __block dispatch_block_t block; | |
| beforeEach(^{ | |
| expect([RACBacktrace backtrace].previousThreadBacktrace).to.beNil(); | |
| previousBacktrace = nil; | |
| block = ^{ | |
| capturePreviousBacktrace(NULL); | |
| }; | |
| }); | |
| it(@"should capture the current backtrace", ^{ | |
| RACBacktrace *backtrace = [RACBacktrace backtrace]; | |
| expect(backtrace).notTo.beNil(); | |
| }); | |
| describe(@"with a GCD queue", ^{ | |
| __block dispatch_queue_t queue; | |
| beforeEach(^{ | |
| queue = dispatch_queue_create("com.github.ReactiveCocoa.RACBacktraceSpec", DISPATCH_QUEUE_SERIAL); | |
| }); | |
| afterEach(^{ | |
| dispatch_barrier_sync(queue, ^{}); | |
| dispatch_release(queue); | |
| }); | |
| it(@"should trace across dispatch_async", ^{ | |
| rac_dispatch_async(queue, block); | |
| expect(previousBacktrace).willNot.beNil(); | |
| }); | |
| it(@"should trace across dispatch_async to the main thread", ^{ | |
| rac_dispatch_async(queue, ^{ | |
| rac_dispatch_async(dispatch_get_main_queue(), block); | |
| }); | |
| expect(previousBacktrace).willNot.beNil(); | |
| }); | |
| it(@"should trace across dispatch_async_f", ^{ | |
| rac_dispatch_async_f(queue, NULL, &capturePreviousBacktrace); | |
| expect(previousBacktrace).willNot.beNil(); | |
| }); | |
| it(@"should trace across dispatch_barrier_async", ^{ | |
| rac_dispatch_barrier_async(queue, block); | |
| expect(previousBacktrace).willNot.beNil(); | |
| }); | |
| it(@"should trace across dispatch_barrier_async_f", ^{ | |
| rac_dispatch_barrier_async_f(queue, NULL, &capturePreviousBacktrace); | |
| expect(previousBacktrace).willNot.beNil(); | |
| }); | |
| it(@"should trace across dispatch_after", ^{ | |
| rac_dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1), queue, block); | |
| expect(previousBacktrace).willNot.beNil(); | |
| }); | |
| it(@"should trace across dispatch_after_f", ^{ | |
| rac_dispatch_after_f(dispatch_time(DISPATCH_TIME_NOW, 1), queue, NULL, &capturePreviousBacktrace); | |
| expect(previousBacktrace).willNot.beNil(); | |
| }); | |
| it(@"shouldn't overflow the stack when deallocating a huge backtrace list", ^{ | |
| RACSubject *doneSubject = [RACReplaySubject subject]; | |
| RACDeepRecursionContext context = { | |
| .queue = queue, | |
| .i = 0, | |
| .doneSubject = doneSubject | |
| }; | |
| rac_dispatch_async_f(queue, &context, &recurseDeeply); | |
| [doneSubject waitUntilCompleted:NULL]; | |
| }); | |
| }); | |
| it(@"should trace across a RACScheduler", ^{ | |
| [[RACScheduler scheduler] schedule:block]; | |
| expect(previousBacktrace).willNot.beNil(); | |
| }); | |
| it(@"shouldn't go bonkers with RACScheduler", ^{ | |
| NSMutableArray *a = [NSMutableArray array]; | |
| for (NSUInteger i = 0; i < 5000; i++) { | |
| [a addObject:@(i)]; | |
| } | |
| [[a.rac_sequence signalWithScheduler:[RACScheduler scheduler]] subscribeCompleted:^{}]; | |
| }); | |
| // Tracing across NSOperationQueue only works on OS X because it depends on | |
| // interposing through dynamic linking | |
| #ifndef __IPHONE_OS_VERSION_MIN_REQUIRED | |
| it(@"should trace across an NSOperationQueue", ^{ | |
| NSOperationQueue *queue = [[NSOperationQueue alloc] init]; | |
| [queue addOperationWithBlock:block]; | |
| expect(previousBacktrace).willNot.beNil(); | |
| }); | |
| #endif | |
| SpecEnd | |
| #endif |