Unit testing: duplicate classes vs undefined symbols
I'm struggling to get unit tests working properly so I created a sample project. My project involves a bunch of pod dependencies, but I've reduced the list to the following simplified Podfile:
platform :ios, '6.1'
pod 'AFNetworking'
pod 'BlocksKit'
pod 'OpenCV'
target :SampleAppTests do
pod 'Kiwi/XCTest'
end
I can run the app and tests in Xcode without issue. The app runs fine with AppCode, but when running the tests I get a bunch of warnings about multiple implementations of classes like this (I've only shown 1 out of about 40 warnings):
...objc[91516]: Class CvAbstractCamera is implemented in both /Users/michael/Library/Application Support/iPhone Simulator/7.0/Applications/761652F3-842D-4D75-A25B-85187745F28A/SampleApp.app/SampleApp and /Users/michael/Library/Caches/appCode20/DerivedData/SampleApp-25cfb026/Build/Products/Debug-iphonesimulator/SampleAppTests.xctest/SampleAppTests. One of the two will be used. Which one is undefined.
...
I'm not sure why this happens only with AppCode. So I tried another idea which is to make the dependencies for the SampleAppTests to be 'exclusive' so that the test target wouldn't contain duplicates classes:
platform :ios, '6.1'
pod 'AFNetworking'
pod 'BlocksKit'
pod 'OpenCV'
target :SampleAppTests, :exclusive => true do
pod 'Kiwi/XCTest'
end
Of course, now the test target fails to link because of undefined symbols (the samle test simply allocates a cv::Mat on the stack just to exercise the dependency on the opencv pod):
Undefined symbols for architecture i386:
"cv::Mat::deallocate()", referenced from:
cv::Mat::release() in SampleAppTests.o
"cv::fastFree(void*)", referenced from:
cv::Mat::~Mat() in SampleAppTests.o
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)
So I'm stuck between a rock and a hard place...I either put up with those warnings or I get linker errors. Hopefully I'm just missing something obvious that someone can point out to me. Any help would be greatly appreciated.
Thanks,
Michael
[EDIT: Forgot to mention that I'm using AppCode 2.5 EAP 131.284]
Please sign in to leave a comment.
The only offhand explanation I can think for the difference between running the tests in Xcode vs AppCode is something I've seen a couple of times, which is newly-altered build settings in Xcode for some reason not being immediately picked up by AppCode. I've seen this infrequently, not reproducibly, so I haven't reported it (also not sure whether it's been a consequence of Xcode not writing or AppCode not reading the xcodeproj).
If your test target's bundle_loader and test_host build settings are pointing at the main executable, you shouldn't need (or indeed have) the main target linked into the test target. I'd suggest double-checking those (Xcode 5 has nicely simplified this with the 'Target' dropdown on the 'Genera' tab of the test target build setting). Then clean build and then close both IDEs, then start up in AppCode and try again.
Apologies if any of that is too elementary. If it is, as you've mentioned this is a sample project, perhaps you could post it here for folk to have a closer look?
When you run tests with "Bundle loader" specified, the simulator launches your application first and then injects the bundle that contains your tests. Since cocoapods dependencies are linked statically, you end up having the same symbols loaded twice. Since they are the same (and not just having identical names), this should not be a problem, the only inconvenience is those warnings.
If you look at ~/Library/Logs/iOS Simulator/[version]/system.log, you will see that the warnings appear when you are running the tests from Xcode too. It looks like Xcode simply suppresses them in the UI, and we might do the same in future releases. Here is an issue to follow: http://youtrack.jetbrains.com/issue/OC-8576
@Vyacheslav: but doesn't the use of ' :exclusive => true ' for the test target take care of this? This stops the main project's classes from being linked into the static library, but its symbols are still available to the tests because of the test bundle injection (I don't entirely understand how the injection works, though).
At least this is how things work for me (and I don't get the duplicate symbol errors in the system.log).
For Objective-C it works exactly as you are describing, but not for C++. Opencv is written in C++, so it needs to be linked with the test target. Objective-C dependencies may be left out from it, though.
Ah, I see. Thank you.
(An aside: anyone know of a good write-up of how ObjC bundle injection works?0