// // GTMNSThread+BlocksTest.m // // Copyright 2012 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. // #import #import "GTMSenTestCase.h" #import "GTMNSThread+Blocks.h" static const NSTimeInterval kTestTimeout = 10; static const int kThreadMethodCounter = 5; static const int kThreadMethoduSleep = 10000; @interface GTMNSThread_BlocksTest : GTMTestCase { @private GTMSimpleWorkerThread *workerThread_; } @end @implementation GTMNSThread_BlocksTest - (void)setUp { workerThread_ = [[GTMSimpleWorkerThread alloc] init]; [workerThread_ start]; } - (void)tearDown { [workerThread_ cancel]; [workerThread_ release]; } - (void)testPerformBlockOnCurrentThread { NSThread *currentThread = [NSThread currentThread]; __block NSThread *runThread = nil; // Straight block runs right away (no runloop spin) [currentThread gtm_performBlock:^{ runThread = [NSThread currentThread]; }]; XCTAssertEqualObjects(runThread, currentThread); // Block with waiting runs immediately as well. runThread = nil; [currentThread gtm_performWaitingUntilDone:YES block:^{ runThread = [NSThread currentThread]; }]; XCTAssertEqualObjects(runThread, currentThread); // Block without waiting requires a runloop spin. runThread = nil; XCTestExpectation *expectation = [self expectationWithDescription:@"BlockRan"]; [currentThread gtm_performWaitingUntilDone:NO block:^{ runThread = [NSThread currentThread]; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL]; XCTAssertEqualObjects(runThread, currentThread); } - (void)testPerformBlockInBackground { XCTestExpectation *expectation = [self expectationWithDescription:@"BlockRan"]; __block NSThread *runThread = nil; [NSThread gtm_performBlockInBackground:^{ runThread = [NSThread currentThread]; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL]; XCTAssertNotNil(runThread); XCTAssertNotEqualObjects(runThread, [NSThread currentThread]); } - (void)testWorkerThreadBasics { // Unstarted worker isn't running. GTMSimpleWorkerThread *worker = [[GTMSimpleWorkerThread alloc] init]; XCTAssertFalse([worker isExecuting]); XCTAssertFalse([worker isFinished]); // Unstarted worker can be cancelled without error. [worker cancel]; XCTAssertFalse([worker isExecuting]); XCTAssertFalse([worker isFinished]); // And can be cancelled again [worker cancel]; XCTAssertFalse([worker isExecuting]); XCTAssertFalse([worker isFinished]); [worker release]; // A thread we start can be cancelled with correct state. 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 *opts) { return (BOOL)(![workerThread isExecuting]); }]; [self expectationForPredicate:predicate evaluatedWithObject:worker handler:NULL]; [worker cancel]; [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL]; XCTAssertFalse([worker isExecuting]); XCTAssertTrue([worker isCancelled]); XCTAssertTrue([worker isFinished]); [worker release]; } - (void)testPerformBlockOnWorkerThread { __block NSThread *runThread = nil; // Runs on the other thread XCTestExpectation *expectation = [self expectationWithDescription:@"BlockRan"]; [workerThread_ gtm_performBlock:^{ runThread = [NSThread currentThread]; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL]; XCTAssertNotNil(runThread); XCTAssertEqualObjects(runThread, workerThread_); // Other thread no wait. runThread = nil; expectation = [self expectationWithDescription:@"BlockRan2"]; [workerThread_ gtm_performWaitingUntilDone:NO block:^{ runThread = [NSThread currentThread]; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL]; XCTAssertNotNil(runThread); XCTAssertEqualObjects(runThread, workerThread_); // Waiting requires no runloop spin runThread = nil; [workerThread_ gtm_performWaitingUntilDone:YES block:^{ runThread = [NSThread currentThread]; }]; XCTAssertNotNil(runThread); XCTAssertEqualObjects(runThread, workerThread_); } - (void)testExitingBlock { [workerThread_ gtm_performWaitingUntilDone:NO block:^{ pthread_exit(NULL); }]; NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id workerThread, NSDictionary *opts) { return (BOOL)(![workerThread isExecuting]); }]; [self expectationForPredicate:predicate evaluatedWithObject:workerThread_ handler:NULL]; [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL]; XCTAssertTrue([workerThread_ isFinished]); } - (void)testCancelFromThread { [workerThread_ gtm_performWaitingUntilDone:NO block:^{ [workerThread_ cancel]; }]; NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id workerThread, NSDictionary *opts) { return (BOOL)(![workerThread isExecuting]); }]; [self expectationForPredicate:predicate evaluatedWithObject:workerThread_ handler:NULL]; [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL]; XCTAssertTrue([workerThread_ isFinished]); } - (void)testNestedCancelFromThread { [workerThread_ gtm_performWaitingUntilDone:NO block:^{ [workerThread_ gtm_performWaitingUntilDone:NO block:^{ [workerThread_ gtm_performWaitingUntilDone:NO block:^{ [workerThread_ gtm_performWaitingUntilDone:NO block:^{ [workerThread_ cancel]; }]; }]; }]; }]; NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id workerThread, NSDictionary *opts) { return (BOOL)(![workerThread isExecuting]); }]; [self expectationForPredicate:predicate evaluatedWithObject:workerThread_ handler:NULL]; [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL]; XCTAssertTrue([workerThread_ isFinished]); } - (void)testCancelFromOtherThread { // Cancel will kill the thread at same point. // It may or may not complete all the blocks. // There is no guarantee made (unlike stop). for (int i = 0; i < kThreadMethodCounter; i++) { [workerThread_ gtm_performWaitingUntilDone:NO block:^{ usleep(kThreadMethoduSleep); }]; } [workerThread_ cancel]; NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id workerThread, NSDictionary *opts) { return (BOOL)(![workerThread isExecuting]); }]; [self expectationForPredicate:predicate evaluatedWithObject:workerThread_ handler:NULL]; [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL]; XCTAssertTrue([workerThread_ isFinished]); } - (void)testStopFromThread { // Show that stop forces all blocks to be executed. __block int counter = 0; for (int i = 0; i < kThreadMethodCounter; i++) { [workerThread_ gtm_performWaitingUntilDone:NO block:^{ usleep(kThreadMethoduSleep); ++counter; }]; } [workerThread_ gtm_performWaitingUntilDone:NO block:^{ [workerThread_ stop]; }]; NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id workerThread, NSDictionary *opts) { return (BOOL)(![workerThread isExecuting]); }]; [self expectationForPredicate:predicate evaluatedWithObject:workerThread_ handler:NULL]; [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL]; XCTAssertTrue([workerThread_ isFinished]); XCTAssertEqual(counter, kThreadMethodCounter); } - (void)testNestedStopFromThread { __block int counter = 0; for (int i = 0; i < kThreadMethodCounter; i++) { [workerThread_ gtm_performWaitingUntilDone:NO block:^{ usleep(kThreadMethoduSleep); ++counter; }]; } [workerThread_ gtm_performWaitingUntilDone:NO block:^{ [workerThread_ gtm_performWaitingUntilDone:NO block:^{ [workerThread_ gtm_performWaitingUntilDone:NO block:^{ [workerThread_ gtm_performWaitingUntilDone:NO block:^{ [workerThread_ stop]; }]; }]; }]; }]; NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id workerThread, NSDictionary *opts) { return (BOOL)(![workerThread isExecuting]); }]; [self expectationForPredicate:predicate evaluatedWithObject:workerThread_ handler:NULL]; [self waitForExpectationsWithTimeout:kTestTimeout handler:NULL]; XCTAssertTrue([workerThread_ isFinished]); XCTAssertEqual(counter, kThreadMethodCounter); } - (void)testStopFromOtherThread { __block int counter = 0; for (int i = 0; i < kThreadMethodCounter; i++) { [workerThread_ gtm_performWaitingUntilDone:NO block:^{ usleep(kThreadMethoduSleep); ++counter; }]; } [workerThread_ stop]; XCTAssertTrue([workerThread_ isFinished]); XCTAssertEqual(counter, kThreadMethodCounter); } @end