I know how to resolve EXC_BAD_ACCESS issues, but I'm not sure how to unit test for it. Is there a way to capture EXC_BAD_ACCESS in code instead of simply crashing?
Here's why I ask: I have written a library that heavily uses blocks, like this:
- (void)doSomething:(void (^)())myBlock;
In my implementation of doSomething: I'm going to eventually run the block, like this:
myBlock();
If a caller passes nil for the block, then it will crash with EXC_BAD_ACCESS, so the solution is to check that the block exists, like this:
if (myBlock) {
myBlock();
}
This nil check is pretty easy to forget, so I'd like a way to write a unit test that fails when the crash occurs. I suppose a crash could be considered a test failure, but I think it would be nicer for others trying to run the tests to see a nice failure message rather than a crash. Any ideas?
I think you'll need to run the test in a subprocess; then you can let the subprocess crash, check for that crash, and fail the test neatly if it occurs.
Working from Peter Hosey's singleton test code.
- (void) runTestInSubprocess:(SEL)testCmd {
pid_t pid = fork();
// The return value of fork is 0 in the child process, and it is
// the id of the child process in the parent process.
if (pid == 0) {
// Child process: run test
// isInSubprocess is an ivar of your test case class
isInSubprocess = YES;
[self performSelector:testCmd];
exit(0);
} else {
// Parent process: wait for child process to end, check
// its status
int status;
waitpid(pid, &status, /*options*/ 0);
// This was a crash; fail the test
STAssertFalse(WIFSIGNALED(status), #"Test %# crashed due to signal %d", NSStringFromSelector(testCmd), WTERMSIG(status));
}
}
Each test will then run itself in a subprocess like so:
- (void) testSomething {
if (!isInSubprocess) {
// Hand off this test's selector to be run in a subprocess
[self runTestInSubprocess:_cmd];
return;
}
// Put actual test code here
STAssertEquals(1, 1, #"Something wrong with the universe.");
}
You may need to tweak this; I haven't tested it.
I would suggest using one of the assertion macros found in the Assertions and Logging Programming Guide
So you could do something like:
NSAssert(myBlock != nil, #"myBlock must not be nil")
This enforces the preconditions that must be met before the method continues executing. It also allows the app to crash and will give you a reason why other than EXEC_BAD_ACCESS.
Related
The part of a method that I am trying to test is as follows:
- (void)configureTableFooterView {
dispatch_async(dispatch_get_main_queue(), ^{
self.tableView.tableFooterView = nil;
if ([self.parser.resultSet isLastPage]) {
return;
}
});
}
I have written the unit test as follows:
- (void)testTableFooterViewConfigurationAfterLastPageLoaded {
id mockTableView = OCMClassMock([GMGFlatTableView class]);
OCMExpect([mockTableView setTableFooterView:[OCMArg isNil]]);
id resultSet = OCMClassMock([GMGResultSetInfo class]);
OCMStub([resultSet isLastPage]).andReturn(YES);
OCMStub([self.mockParser resultSet]).andReturn(resultSet);
id partialMockSUT = OCMPartialMock(self.sut);
OCMStub([partialMockSUT tableView]).andReturn(mockTableView);
[self.sut configureTableFooterView];
OCMVerifyAllWithDelay(mockTableView, 2.0);
//OCMVerifyAllWithDelay(partialMockSUT, 2.0);
}
I have another test in the same class which is testing the same things from with in the dispatch_async call on the main thread. The test expectations and verification setup in that test match this one. While that test passes, this one gets stuck in an infinite loop at the delayed verification step.
Interestingly, if I only run this 1 test, it passes with out any problems. Its only when this test is run with other tests that I see the problem.
UPDATE:
In unit test, execute the block passed in queue with dispatch_asyc
This is a much more relevant post. However, this fails almost in the exact same way as the original test method:
- (void)testTableFooterViewConfigurationAfterLastPageLoaded {
id mockTableView = OCMClassMock([GMGFlatTableView class]);
OCMExpect([mockTableView setTableFooterView:[OCMArg isNil]]);
id resultSet = OCMClassMock([GMGResultSetInfo class]);
OCMStub([resultSet isLastPage]).andReturn(YES);
OCMStub([self.mockParser resultSet]).andReturn(resultSet);
id partialMockSUT = OCMPartialMock(self.sut);
OCMStub([partialMockSUT tableView]).andReturn(mockTableView);
[self.sut configureTableFooterView];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
OCMVerifyAll(mockTableView);
}
The line with NSRunLoop crashes with EXC_BAD_ACCESS when run as suite but runs fine alone!
You can make class wrapper around dispatch_async, and pass it as dependency. Also you can make fake wrapper, and pass it in tests. If you interested in, I can provide much more detailed explanation.
In a project I am working I have implemented the HTTP Manager Reachability example.
When I run the actual app, it goes inside the block and from there to the switch:
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
In addition, when I call ...reachabilityManager] isReachable method returns true as expected.
The problem occurs when I try to unit test a class method I wrote that uses ...reachabilityManager] isReachable as a precondition - it returns false and what is weird that during a debug I have noticed that it doesn't go inside the above block, it skips it.
Of course, in the actual app it goes inside the block.
I have even tried to mock the class that implements the HTTP Manager Reachability example using OCMock in the unit test but it gave me the same result:
// NetworkClass implements the example
NetworkClass *networkClass = [[NetworkClass alloc] init];
id mockedNetworkClass = OCMPartialMock(networkClass);
// startNetworkMonitoring method implements the whole example above
[mockedNetworkClass startNetworkMonitoring];
// Giving enough time for AFNetworking to finish
[NSThread sleepForTimeInterval:60.0f];
EDIT1:
Looks like semaphore/XCTestExpectation won't help, the problem is in AFNetworkReachabilityManager::startMonitoring:
The only way that we could get the callback we want is inside startMonitoring method at dispatch_async(dispatch_get_main_queue(), ^{
callback(status);
But it runs outside the unit test even if we use semaphore/XCTestExpectation as mentioned.
Still looking for soultions..
EDIT2:
I was trying to follow the objc.io for Testing Asynchronous Code but it seems to be missing some code and some of the explanations are lacking of integration details.
I'd imagine that sleeping the thread is causing issues.
Try using the expectations API documented in Writing Tests of Asynchronous Operations.
Something along the lines of this should get you started (note this is more of a demonstration of the expectations API rather than a complete working test case):
- (void)testReachability {
XCTestExpectation *expectation = [self expectationWithDescription:#"Wait for reachability"];
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
...
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) {
// timed out waiting for reachability
}];
}
I wrote a test to experiment with RestKit using blocks. I thought that the RKRequest.onDidLoadResponse would need to contain a dispatch_async() call to get the response on the main UI thread, though to my surprise this turned out to cause a runtime exception, so I took it out and I could update the interface quite fine.
My code in question is as follows:
- (IBAction)loadSeasons:(UIButton *)sender {
[[RKClient sharedClient] get:#"/api/v1/seasons" usingBlock:^(RKRequest* req) {
req.onDidLoadResponse = ^(RKResponse* res) {
NSLog(#"Request Performed: %#", res.bodyAsString);
self.textResponse.text = res.bodyAsString;
//dispatch_async(dispatch_get_main_queue(), ^{
// NSLog(#"In main queue: %#", res.bodyAsString);
//});
};
}];
}
If I uncomment the dispatch_async block I get the exception. So how do I know when I need to use this and when I should not need to? Should I test for something that tells me if the code being executed is on the main thread or not?
I am new to Objective-C and it's early days for me so I do apologise for what may be a newbie question.
UPDATE: When uncommenting the code above the following happens:
The console outputs the text (lldb) in blue, no other information is provided.
A break on libobjc.A.dylibobjc_msgSend:is shown in the editor on0x1c2009b: movl 8(%edx), %ediwith the message in green:Thread 1: EXC_BAD_ACCESS (code=1, address=0x40000008)`.
To answer your question you can check your current thread by calling [NSThread isMainThread]. However, you DONT need to do this for your issue and in fact its perfectly legal to dispatch an execution block to the main thread if you are already running in the main thread.
The crash is because of the way that variables are stored when executing a block. When the first block is completed the stack memory used by the variable can be overwritten, so by the time your async NSLog statement runs in the second block, your variable res has been destroyed, so its pointing to garbage when its used in your log statement, triggering a crash.
For completeness, research __block variable declarations. To make res referenceable in your second dispatch block I believe you'd have to declare it as a __block variable to move it to heap by declaring this in your first block: __block RKResponse* response = res; and then using response in your dispatched log statement.
I am receiving EXC_BAD_ACCESS signal in my iOS application when doing a recursion that involves objective-c blocks. Here is the simplified code:
- (void)problematicMethod:(FriendInfo*)friendInfo onComplete:(void(^)(NSString*))onComplete1 {
[self doSomethingWithFriend:friendInfo onComplete:^(Response* response) {
switch (response.status) {
case IS_OK:
onComplete1(message);
break;
case ISNT_OK:
// Recursively calls the method until a different response is received
[self problematicMethod:friendInfo onComplete:onComplete1];
break;
default:
break;
}
}];
}
So basically, the problematicMethod, in this simplified version, calls doSomethingWithFriend:onComplete:. When that method finishes (onComplete), and if everything was ok, the original onComplete1 block gets called, and this works fine.
But if something went wrong, problematicMethod needs to be called again (the recursion part), and when this happens for the first time, I immediately get EXC_BAD_ACCESS signal.
Any kind of help would be greatly appreciated.
How are you creating your block? Remember that you have to move it from stack to heap.
Example:
void(^onCompleteBlock)(NSString*) = [[^(NSString* param) {
//...block code
}] copy] autorelease];
[self problematicMethod:friendInfo onCompleteBlock];
If response.status value is ISNT_OK you never finish calling recursively the function.
I was having some trouble unit testing some grand central dispatch code with the built in Xcode unit testing framework, SenTestingKit. I managed to boil my problem done to this. I have a unit test that builds a block and tries to execute it on the main thread. However, the block is never actually executed, so the test hangs because it's a synchronous dispatch.
- (void)testSample {
dispatch_sync(dispatch_get_main_queue(), ^(void) {
NSLog(#"on main thread!");
});
STFail(#"FAIL!");
}
What is it about the testing environment that causes this to hang?
dispatch_sync runs a block on a given queue and waits for it to complete. In this case, the queue is the main dispatch queue. The main queue runs all its operations on the main thread, in FIFO (first-in-first-out) order. That means that whenever you call dispatch_sync, your new block will be put at the end of the line, and won't run until everything else before it in the queue is done.
The problem here is that the block you just enqueued is at the end of the line waiting to run on the main thread—while the testSample method is currently running on the main thread. The block at the end of the queue can't get access to the main thread until the current method (itself) finishes using the main thread. However dispatch_sync means Submits a block object for execution on a dispatch queue and waits until that block completes.
The problem in your code is that no matter whether you use dispatch_sync or dispatch_async , STFail() will always be called, causing your test to fail.
More importantly, as BJ Homer's explained, if you need to run something synchronously in the main queue, you must make sure you are not in the main queue or a dead-lock will happen. If you are in the main queue you can simply run the block as a regular function.
Hope this helps:
- (void)testSample {
__block BOOL didRunBlock = NO;
void (^yourBlock)(void) = ^(void) {
NSLog(#"on main queue!");
// Probably you want to do more checks here...
didRunBlock = YES;
};
// 2012/12/05 Note: dispatch_get_current_queue() function has been
// deprecated starting in iOS6 and OSX10.8. Docs clearly state they
// should be used only for debugging/testing. Luckily this is our case :)
dispatch_queue_t currentQueue = dispatch_get_current_queue();
dispatch_queue_t mainQueue = dispatch_get_main_queue();
if (currentQueue == mainQueue) {
blockInTheMainThread();
} else {
dispatch_sync(mainQueue, yourBlock);
}
STAssertEquals(YES, didRunBlock, #"FAIL!");
}
If you are on the main queue and synchronously wait for the main queue to be available you will indeed wait a long time. You should test to make sure you are not already on the main thread.
Will you ever get out of house if you must wait for yourself to get out house first? You guessed right! No! :]
Basically if:
You are on FooQueue. (doesn't have to be main_queue)
You call the method using sync ie in a serial way and want to execute on FooQueue.
It will never happen for same reason that you will never get out of house!
It won't ever get dispatched because it's waiting for itself to get off the queue!
To follow up, since
dispatch_get_current_queue()
is now deprecated, you can use
[NSThread isMainThread]
to see if you are on the main thread.
So, using the other answer above, you could do:
- (void)testSample
{
BOOL __block didRunBlock = NO;
void (^yourBlock)(void) = ^(void) {
NSLog(#"on main queue!");
didRunBlock = YES;
};
if ([NSThread isMainThread])
yourBlock();
else
dispatch_sync(dispatch_get_main_queue(), yourBlock);
STAssertEquals(YES, didRunBlock, #"FAIL!");
}