Xcode Test Target - Linker errors, missing headers


#1

Hi folks,

I’m trying to write unit tests in Xcode (7.0.1). I’m aware that Juce has its own unit testing capabilities, but I want to leverage the built-in tooling capabilities of Xcode. This worked prior to my upgrade to Juce 4.2.

As a test, I created an empty Audio Application from Projucer, opened the Xcode project, and added my test target. This creates a file called TestProjectTests.m, which is an Objective-C file. In order to use some Juce classes in my unit tests, I #include "JuceHeader.h", and so I also have to change the file extension to TestProjectTests.mm, which permits mixed Objective-C and C++ content in the same file.

Now I run the test, and I get a build error: /Users/Tom/Documents/Programming/TestProject/JuceLibraryCode/JuceHeader.h:18:10: 'juce_audio_basics/juce_audio_basics.h' file not found.

The next thing I tried is to add the Juce modules to the header search paths for my test target, exactly like they are in the App target: ../../JuceLibraryCode ../../../JUCE/modules. Now, when I run the tests I get a linker failure:
ld: file not found: /Users/Tom/Library/Developer/Xcode/DerivedData/TestProject-aqkriarcaprkccbjfvtdkacnwhif/Build/Products/Debug/TestProject.app/Contents/MacOS/TestProject clang: error: linker command failed with exit code 1 (use -v to see invocation)

I have a feeling I’m missing something simple, but I’m clueless as to what to try next. Thoughts? Thanks!!

Edit: I also tried specifying the “User Search Paths” for the test target. This got me a raft of errors in JuceHeader.h, of the flavor:
/Users/Tom/Documents/Programming/TestProject/JuceLibraryCode/JuceHeader.h:18:10: 'juce_audio_basics/juce_audio_basics.h' file not found with <angled> include; use "quotes" instead

Edit #2: I’ve isolated this down further, reproducible with the following steps:
1: Create an empty Audio Application from Producer
2: Open the Xcode project
3: Add a test target
4: Try to run the tests, get the linker error.


#2

Remove the Debug and Release contents in Test Host.
Project Settings -> Test Target -> Build Settings -> Test Host -> Remove Debug and Release Contents


#3

Wow - right on the money. Thank you! That got me past the linker issue, and on to another one:

"juce::this_will_fail_to_link_if_some_of_your_compile_units_are_built_in_debug_mode::this_will_fail_to_link_if_some_of_your_compile_units_are_built_in_debug_mode()", referenced from: ___cxx_global_var_init in TestProjectTests.o ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation)

Any thoughts on that one?


#4
/*
    As the very long class names here try to explain, the purpose of this code is to cause
    a linker error if not all of your compile units are consistent in the options that they
    enable before including JUCE headers. The reason this is important is that if you have
    two cpp files, and one includes the juce headers with debug enabled, and the other doesn't,
    then each will be generating code with different memory layouts for the classes, and
    you'll get subtle and hard-to-track-down memory corruption bugs!
*/
#if JUCE_DEBUG
 this_will_fail_to_link_if_some_of_your_compile_units_are_built_in_debug_mode
    ::this_will_fail_to_link_if_some_of_your_compile_units_are_built_in_debug_mode() noexcept {}
#else
 this_will_fail_to_link_if_some_of_your_compile_units_are_built_in_release_mode
    ::this_will_fail_to_link_if_some_of_your_compile_units_are_built_in_release_mode() noexcept {}
#endif

}

#5

Thanks Jules. Digging through the source code for it was the first thing I tried when I saw that, was please to see how well-commented it was, followed by searching the forum, followed by:

  1. changing the scheme so both app and test target would build in release. No result.
  2. define JUCE_DEBUG different ways.
  3. define FORCE_NO_DEBUG different ways.
  4. set NDEBUG=1 in Release for my test target

None of these steps had an effect. Clearly I don’t know enough about C++ after having worked with Juce for 2 years. Reading up on what a “compile unit” is didn’t help either. :slight_smile:


#6

Try wrapping TestProjectTests.mm code with this

#ifdef DEBUG
//headers
//Methods
#endif


#7

Thanks, will give that a shot in a few days.


#8

Trying to fix this by defining macros in your headers will probably just make things worse.

The problem only happens if you have different macros defined in different compile units, e.g. by having your own header file somewhere which sets something like _DEBUG, so that this is picked up by the juce headers when included after this, but not when they’re included by the juce cpp files.


#9

Hey Jules, I get what you’re saying, but I’m not sure what you suggest. The fact that this happens in a basically untouched project leads me to believe that there’s something special about the way that the Projucer project is configured which is leading to the issue. Would it help if I attach a reproducer when I get back in a few days?


#10

Sure, if you can show what it is that you did differently then that’ll probably explain it. But I don’t think this is a problem in the library, it’ll be something like you including a mismatched version of a header file or defining a debug macro somewhere.


#11
  1. Create a new Projucer AudioApplication, save and open in Xcode.
  2. Add a Test Target (OS X Unit Testing Bundle)
  3. Run the tests. Fail with linker error.
  4. Change Host Application to “None” (removes Test Host info as described above)
  5. Run the tests. They succeed.
  6. Change file extension on unit test file to “.mm” so C++ can be included with Objective-C
  7. #include “JuceHeader.h” in .mm file.
  8. Run the tests. Fail with ‘juce_audio_basics/juce_audio_basics.h’ file not found
  9. In Build Settings, copy Header Search Paths from Application Target into Test Target. (…/…/JuceLibraryCode …/…/…/JUCE/modules $(inherited))
  10. Run the tests. Fail with juce::this_will_fail_to_link_if_some_of_your_compile_units_are_built_in_debug_mode::this_will_fail_to_link_if_some_of_your_compile_units_are_built_in_debug_mode()

Will attach reproducer.


#12

Reproducer here: https://www.dropbox.com/s/9wt32dng7m25z30/UnitTestIssueReproducer.zip?dl=0


Approaches to running unit tests
#13

JUCE, by default, puts it’s build products in the build folder next to the xcode project. Xcode by default puts them … well somewhere.

To fix this just change the “Per-configuration Build Products Path” of the unit test target to $(PROJECT_DIR)/build/$(CONFIGURATION).

See screenshots below:





#14

Awesome - thank you so much, that worked! However, I had to undo Step 4 from my list of steps (Host Application cannot be set to “None”).

It seems reasonable that Juce should put its build products in the same place as Xcode, if things like this don’t work out of the box. But I’m sure you guys have a good reason for doing things the way you do - for my own knowledge, what would that reason be? Also, how did you know how to fix this? Did you just know it from experience, or do you have some troubleshooting technique I can learn from?


#15

Apologies for resurrecting a rather old thread, but I’m hitting a similar issue to this, and I’d like to understand why the fix @fabian suggested works. What does the directory that the product gets built in have to do with whether or not DEBUG or NBUGUG was defined for the juce compliation units vs the test code.

(If my question doesn’t make sense, please enlighten me - I’m pretty new to C++ still and am still finding my way around preprocessing, compilation, and linking, etc.)

Thanks!