Axeman.IN

A Weblog of Programming, Philosophy and Random Thoughts.

Setting Up Jenkins CI for iOS Development

I just started with iOS App development and as one of the first things to do on any (Agile) project we (Few just out of college ThoughtWorkers) started setting up CI. We chose Jenkins as it seemed Free, easy and widely used CI solution. But the job turned out to be more difficult than we ever thought.

What is so damn tough in that !

Just to give a background information, I added Xcode Plugin available in Jenkins and tried running our Unit Tests with sdk as iphonesimulator6.0, I got this error:

1
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Tools/Tools/RunPlatformUnitTests:81: warning: Skipping tests; the iPhoneSimulator platform does not currently support application-hosted tests (TEST_HOST) set

Then I tried executing the command which Jenkins was running on my laptop, and got the same error, which says

“iPhoneSimulator platform does not currently support application-hosted tests”

And the same simulator runs the tests when launched from the Xcode IDE ! So the main problem became how to run Unit Tests from command line and that was very tough. Especially when you are a noob to Xcode !

Solving the problem

I tried following various solutions (few are: ) which told me to hack an internal shell script of Xcode, which seemed pretty dangerous to me, but I did that after backing up the given files. Those solutions worked for quite a few people which I could see from the comments and No of Upvotes on StackOverflow.

But even that didn’t work, probably because most of them involved hacking older version of Xcode scripts and mine is 4.5.2.

Interestingly Apple has made this script more sturdy against the given hacks. The hacks say that replace the line which prints error and exits with a call to a function that runs unit tests. Apple put that line inside a function which is named exactly as the function to run tests and so if somebody just does what he read, he’d end up putting xodebuild in an infinite recursion, One just needs to remove this function and find where it was called and write another line as explained there.

Now after hacking the script when I tried to run this, xcodebuild tried to run the test but couldn’t and just skipped them printing this:

1
2
3
4
5
6
7
8
/Applications/Xcode.app/Contents/Developer/Tools/RunPlatformUnitTests.include:266: note: Started tests for architectures 'i386'
Run unit tests for architecture 'i386' (GC OFF)
/Applications/Xcode.app/Contents/Developer/Tools/RunPlatformUnitTests.include:273: note: Running tests for architecture 'i386' (GC OFF)
2012-12-02 21:19:45.595 JenkinsSetup[53390:11603] Unknown Device Type. Using UIUserInterfaceIdiomPhone based on screen size
Terminating since there is no workspace.
/Applications/Xcode.app/Contents/Developer/Tools/RunPlatformUnitTests.include:334: note: Passed tests for architecture 'i386' (GC OFF)

/Applications/Xcode.app/Contents/Developer/Tools/RunPlatformUnitTests.include:345: note: Completed tests for architectures 'i386'

Thanks to this answer on StackOverFlow that I could run the tests. This solution doesn’t rely on Xcode to run the tests (atleast directly), it rather uses ios-sim to run the tests.

The solution

Although the solution is not originally mine, just to document this is the solution, here it goes:

  • Install ios-sim. If you use Homebrew: $ brew install ios-sim. If you don’t find out how to do that (Google might be helpful)

  • Create a new file somewhere inside the root directory of the project so that it can be checked in with the project. I created it as Scripts/run_tests.rb Containing following code and executable permissions

Scripts/run_tests.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env ruby
if ENV['SL_RUN_UNIT_TESTS'] then
    launcher_path = "ios-sim"
    test_bundle_path= File.join(ENV['BUILT_PRODUCTS_DIR'], "#{ENV['PRODUCT_NAME']}.#{ENV['WRAPPER_EXTENSION']}")

    environment = {
        'DYLD_INSERT_LIBRARIES' => "/../../Library/PrivateFrameworks/IDEBundleInjection.framework/IDEBundleInjection",
        'XCInjectBundle' => test_bundle_path,
        'XCInjectBundleInto' => ENV["TEST_HOST"]
    }

    environment_args = environment.collect { |key, value| "--setenv #{key}=\"#{value}\""}.join(" ")

    app_test_host = File.dirname(ENV["TEST_HOST"])
    passed = system("#{launcher_path} launch \"#{app_test_host}\" #{environment_args} --args -SenTest All #{test_bundle_path}")
    exit(1) if !passed
else
    puts "SL_RUN_UNIT_TESTS not set - Did not run unit tests!"
end

The original script given in the answer in stackoverflow would not exit with a non zero status code when the tests failed, so I added a line just after the system call which checked that.

  • Next the project needed to know that to run the tests, it needs to run a different script when it is asked to run the test. For that: Click on Project Name in the Navigator > Select Your Test Target > Build Phases > Run Script and replace the second line which should say "${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests" with ${PROJECT_DIR}/Scripts/run_tests.rb

  • Now Jenkins needs to know to set SL_RUN_UNIT_TESTS=YES, so set that in Custom xcodebuild arguments option in the Build Step

And You are done :-) All I can say is with an ugly syntax and all this hassle iOS hasn’t made a good impression on me :-(

Comments