aboutsummaryrefslogtreecommitdiff
path: root/Foundation/GTMNSThread+BlocksTest.m
diff options
context:
space:
mode:
Diffstat (limited to 'Foundation/GTMNSThread+BlocksTest.m')
-rw-r--r--Foundation/GTMNSThread+BlocksTest.m168
1 files changed, 115 insertions, 53 deletions
diff --git a/Foundation/GTMNSThread+BlocksTest.m b/Foundation/GTMNSThread+BlocksTest.m
index ff1a1a0..4b685fd 100644
--- a/Foundation/GTMNSThread+BlocksTest.m
+++ b/Foundation/GTMNSThread+BlocksTest.m
@@ -20,7 +20,7 @@
#import "GTMSenTestCase.h"
#import "GTMNSThread+Blocks.h"
-static const NSTimeInterval kTestTimeout = 5;
+#import "GTMFoundationUnitTestingUtilities.h"
@interface GTMNSThread_BlocksTest : GTMTestCase {
@private
@@ -36,46 +36,60 @@ static const NSTimeInterval kTestTimeout = 5;
}
- (void)tearDown {
- [workerThread_ cancel];
+ [workerThread_ stop];
[workerThread_ release];
}
- (void)testPerformBlockOnCurrentThread {
NSThread *currentThread = [NSThread currentThread];
+
+ GTMUnitTestingBooleanRunLoopContext *context =
+ [GTMUnitTestingBooleanRunLoopContext context];
__block NSThread *runThread = nil;
// Straight block runs right away (no runloop spin)
+ runThread = nil;
+ [context setShouldStop:NO];
[currentThread gtm_performBlock:^{
runThread = [NSThread currentThread];
+ [context setShouldStop:YES];
}];
XCTAssertEqualObjects(runThread, currentThread);
+ XCTAssertTrue([context shouldStop]);
// Block with waiting runs immediately as well.
runThread = nil;
+ [context setShouldStop:NO];
[currentThread gtm_performWaitingUntilDone:YES block:^{
runThread = [NSThread currentThread];
+ [context setShouldStop:YES];
}];
XCTAssertEqualObjects(runThread, currentThread);
+ XCTAssertTrue([context shouldStop]);
// Block without waiting requires a runloop spin.
runThread = nil;
- XCTestExpectation *expectation = [self expectationWithDescription:@"BlockRan"];
+ [context setShouldStop:NO];
[currentThread gtm_performWaitingUntilDone:NO block:^{
runThread = [NSThread currentThread];
- [expectation fulfill];
+ [context setShouldStop:YES];
}];
- [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL];
+ XCTAssertTrue([[NSRunLoop currentRunLoop]
+ gtm_runUpToSixtySecondsWithContext:context]);
XCTAssertEqualObjects(runThread, currentThread);
+ XCTAssertTrue([context shouldStop]);
}
- (void)testPerformBlockInBackground {
- XCTestExpectation *expectation = [self expectationWithDescription:@"BlockRan"];
+ GTMUnitTestingBooleanRunLoopContext *context =
+ [GTMUnitTestingBooleanRunLoopContext context];
__block NSThread *runThread = nil;
[NSThread gtm_performBlockInBackground:^{
runThread = [NSThread currentThread];
- [expectation fulfill];
+ [context setShouldStop:YES];
}];
- [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL];
+ XCTAssertTrue([[NSRunLoop currentRunLoop]
+ gtm_runUpToSixtySecondsWithContext:context]);
XCTAssertNotNil(runThread);
XCTAssertNotEqualObjects(runThread, [NSThread currentThread]);
}
@@ -86,103 +100,151 @@ static const NSTimeInterval kTestTimeout = 5;
XCTAssertFalse([worker isExecuting]);
XCTAssertFalse([worker isFinished]);
+ // Unstarted worker can be stopped without error.
+ [worker stop];
+ XCTAssertFalse([worker isExecuting]);
+ XCTAssertTrue([worker isFinished]);
- // Unstarted worker can be cancelled without error.
- [worker cancel];
+ // And can be stopped again
+ [worker stop];
XCTAssertFalse([worker isExecuting]);
- XCTAssertFalse([worker isFinished]);
+ XCTAssertTrue([worker isFinished]);
- // And can be cancelled again
- [worker cancel];
+ // A thread we start can be stopped with correct state.
+ worker = [[GTMSimpleWorkerThread alloc] init];
XCTAssertFalse([worker isExecuting]);
XCTAssertFalse([worker isFinished]);
- [worker release];
+ [worker start];
+ XCTAssertTrue([worker isExecuting]);
+ XCTAssertFalse([worker isFinished]);
+ [worker stop];
+ XCTAssertFalse([worker isExecuting]);
+ XCTAssertTrue([worker isFinished]);
- // A thread we start can be cancelled with correct state.
+ // A cancel is also honored
worker = [[GTMSimpleWorkerThread alloc] init];
XCTAssertFalse([worker isExecuting]);
XCTAssertFalse([worker isFinished]);
- XCTestExpectation *blockPerformed = [self expectationWithDescription:@"BlockIsRunning"];
[worker start];
- [workerThread_ gtm_performWaitingUntilDone:YES block:^{
- [blockPerformed fulfill];
- }];
- [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL];
XCTAssertTrue([worker isExecuting]);
- XCTAssertFalse([worker isCancelled]);
XCTAssertFalse([worker isFinished]);
- NSPredicate *predicate =
- [NSPredicate predicateWithBlock:^BOOL(id workerThread, NSDictionary<NSString *,id> *options) {
- return (BOOL)(![workerThread isExecuting]);
- }];
- [self expectationForPredicate:predicate evaluatedWithObject:worker handler:NULL];
-
[worker cancel];
- [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL];
+ // And after some time we're done. We're generous here, this needs to
+ // exceed the worker thread's runloop timeout.
+ sleep(5);
XCTAssertFalse([worker isExecuting]);
- XCTAssertTrue([worker isCancelled]);
XCTAssertTrue([worker isFinished]);
- [worker release];
+}
+
+- (void)testWorkerThreadStopTiming {
+ // Throw a sleep and make sure that we stop as soon as we can.
+ NSDate *start = [NSDate date];
+ NSConditionLock *threadLock = [[[NSConditionLock alloc] initWithCondition:0]
+ autorelease];
+ [workerThread_ gtm_performBlock:^{
+ [threadLock lock];
+ [threadLock unlockWithCondition:1];
+ [NSThread sleepForTimeInterval:.25];
+ }];
+ [threadLock lockWhenCondition:1];
+ [threadLock unlock];
+ [workerThread_ stop];
+ XCTAssertFalse([workerThread_ isExecuting]);
+ XCTAssertTrue([workerThread_ isFinished]);
+ XCTAssertEqualWithAccuracy(-[start timeIntervalSinceNow], 0.25, 0.25);
}
- (void)testPerformBlockOnWorkerThread {
+ GTMUnitTestingBooleanRunLoopContext *context =
+ [GTMUnitTestingBooleanRunLoopContext context];
__block NSThread *runThread = nil;
// Runs on the other thread
- XCTestExpectation *expectation = [self expectationWithDescription:@"BlockRan"];
+ runThread = nil;
+ [context setShouldStop:NO];
[workerThread_ gtm_performBlock:^{
runThread = [NSThread currentThread];
- [expectation fulfill];
+ [context setShouldStop:YES];
}];
- [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL];
+ XCTAssertTrue([[NSRunLoop currentRunLoop]
+ gtm_runUpToSixtySecondsWithContext:context]);
XCTAssertNotNil(runThread);
XCTAssertEqualObjects(runThread, workerThread_);
// Other thread no wait.
runThread = nil;
- expectation = [self expectationWithDescription:@"BlockRan2"];
+ [context setShouldStop:NO];
[workerThread_ gtm_performWaitingUntilDone:NO block:^{
runThread = [NSThread currentThread];
- [expectation fulfill];
+ [context setShouldStop:YES];
}];
- [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL];
+ XCTAssertTrue([[NSRunLoop currentRunLoop]
+ gtm_runUpToSixtySecondsWithContext:context]);
XCTAssertNotNil(runThread);
XCTAssertEqualObjects(runThread, workerThread_);
// Waiting requires no runloop spin
runThread = nil;
+ [context setShouldStop:NO];
[workerThread_ gtm_performWaitingUntilDone:YES block:^{
runThread = [NSThread currentThread];
+ [context setShouldStop:YES];
}];
+ XCTAssertTrue([context shouldStop]);
XCTAssertNotNil(runThread);
XCTAssertEqualObjects(runThread, workerThread_);
}
-- (void)testExitingBlock {
+- (void)testExitingBlockIsExecuting {
+ NSConditionLock *threadLock = [[[NSConditionLock alloc] initWithCondition:0]
+ autorelease];
[workerThread_ gtm_performWaitingUntilDone:NO block:^{
- pthread_exit(NULL);
+ [threadLock lock];
+ [threadLock unlockWithCondition:1];
+ pthread_exit(NULL);
}];
- NSPredicate *predicate =
- [NSPredicate predicateWithBlock:^BOOL(id workerThread, NSDictionary<NSString *,id> *options) {
- return (BOOL)(![workerThread isExecuting]);
- }];
- [self expectationForPredicate:predicate evaluatedWithObject:workerThread_ handler:NULL];
- [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL];
+ [threadLock lockWhenCondition:1];
+ [threadLock unlock];
+ // Give the pthread_exit() a bit of time
+ [NSThread sleepForTimeInterval:.25];
+ // Did we notice the thread died? Does [... isExecuting] clean up?
+ XCTAssertFalse([workerThread_ isExecuting]);
XCTAssertTrue([workerThread_ isFinished]);
}
-
-
-- (void)testCancelFromThread {
+- (void)testExitingBlockCancel {
+ NSConditionLock *threadLock = [[[NSConditionLock alloc] initWithCondition:0]
+ autorelease];
[workerThread_ gtm_performWaitingUntilDone:NO block:^{
- [workerThread_ cancel];
+ [threadLock lock];
+ [threadLock unlockWithCondition:1];
+ pthread_exit(NULL);
}];
- NSPredicate *predicate =
- [NSPredicate predicateWithBlock:^BOOL(id workerThread, NSDictionary<NSString *,id> *options) {
- return (BOOL)(![workerThread isExecuting]);
+ [threadLock lockWhenCondition:1];
+ [threadLock unlock];
+ // Give the pthread_exit() a bit of time
+ [NSThread sleepForTimeInterval:.25];
+ // Cancel/stop the thread
+ [workerThread_ stop];
+ // Did we notice the thread died? Did we clean up?
+ XCTAssertFalse([workerThread_ isExecuting]);
+ XCTAssertTrue([workerThread_ isFinished]);
+}
+
+- (void)testStopFromThread {
+ NSConditionLock *threadLock = [[[NSConditionLock alloc] initWithCondition:0]
+ autorelease];
+ [workerThread_ gtm_performWaitingUntilDone:NO block:^{
+ [threadLock lock];
+ [workerThread_ stop]; // Shold not block.
+ [threadLock unlockWithCondition:1];
}];
- [self expectationForPredicate:predicate evaluatedWithObject:workerThread_ handler:NULL];
- [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL];
+ // Block should complete before the stop occurs.
+ [threadLock lockWhenCondition:1];
+ [threadLock unlock];
+ // Still need to give the thread a moment to not be executing
+ sleep(1);
+ XCTAssertFalse([workerThread_ isExecuting]);
XCTAssertTrue([workerThread_ isFinished]);
}