We use continuous integration on all of our iOS applications. When the tests run on the server, they are run without the iOS Simulator. This headless environment works well, but there are a few services that the simulator provides. We specifically were running into errors with UIPasteboard and EventKit.
Apple sandboxes your application for security purposes. Among other things, this restricts you from directly interacting with the devices Calendar and clipboard. In order to create or edit events, the program interacts with a calendar access daemon. Apple provides these daemons to allow a program to interact with various services. Unfortunately, these daemons are attached to the simulator and will not be running in our headless environment.
We can start these daemons ourselves before running the tests. There is a catch though; there are a few environment variables that must be set in the daemon’s plist file in order for them to start properly. For those unfamiliar with plist files, they are typically used to store settings. We can then use launchctl to load the daemon plists and start the daemons. Once the daemons are running, our tests can interact with EventKit and UIPasteboard without error.
Some notable changes we need to make to get the daemons to load properly.
- Change the executable path. The daemons expect to be running from the context of the simulator. We need to convince them of that.
- DYLD_ROOT_PATH – The dynamic linker path allows the daemon to interact with shared frameworks. This is similar to the LD_LIBRARY_PATH.
- IPHONE_SIMULATOR_ROOT – Once again, this convinces the daemon that it is running inside the simulator.
- DYLD_NO_FIX_PREBINDING and DYLD_NEW_LOCAL_SHARED_REGIONS – Configuration options for the dynamic linker.
Here is the addition to our Rakefile that we use:
namespace :daemons do
desc "Update launchd files with currently configured SDK"
task :configure do
Dir["tools/simdaemons/*.plist"].each do |plist|
current_executable_path = %x|/usr/libexec/PlistBuddy -c "Print :ProgramArguments:0" #{plist}|
match_data = current_executable_path.match(/^(?:\/Developer\/Platforms\/iPhoneSimulator\.platform\/Developer\/SDKs\/iPhoneSimulator.+\.sdk\/)?(.*)$/)
executable_path = File.join(SDK_DIR, match_data[1])
system(%Q[/usr/libexec/PlistBuddy -c "Delete :EnvironmentVariables" #{plist}])
system_or_exit(%Q[/usr/libexec/PlistBuddy -c "Add :EnvironmentVariables dict" #{plist}])
system_or_exit(%Q[/usr/libexec/PlistBuddy -c "Add :EnvironmentVariables:DYLD_ROOT_PATH string #{SDK_DIR}" #{plist}])
system_or_exit(%Q[/usr/libexec/PlistBuddy -c "Add :EnvironmentVariables:IPHONE_SIMULATOR_ROOT string #{SDK_DIR}" #{plist}])
system_or_exit(%Q[/usr/libexec/PlistBuddy -c "Add :EnvironmentVariables:DYLD_NO_FIX_PREBINDING string YES" #{plist}])
system_or_exit(%Q[/usr/libexec/PlistBuddy -c "Add :EnvironmentVariables:DYLD_NEW_LOCAL_SHARED_REGIONS string YES" #{plist}])
system_or_exit(%Q[/usr/libexec/PlistBuddy -c "Set :ProgramArguments:0 #{executable_path}" #{plist}])
end
end
desc "Unloads, Loads, and Starts Simulator Daemons that we need for our tests"
task :start do
Dir["tools/simdaemons/*.plist"].each do |file|
system_or_exit(%Q[launchctl unload #{file}]);
system_or_exit(%Q[launchctl load #{file}]);
system_or_exit(%Q[launchctl start #{File.basename(file).sub(/\.\w+$/, '')}]);
end
end
desc "Stop Daemons"
task :stop do
Dir["tools/simdaemons/*.plist"].each do |file|
system_or_exit(%Q[launchctl stop #{File.basename(file).sub(/\.\w+$/, '')}]);
end
end
end
- We added a directory to our project and copied over the plists provided by Apple from /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/
iPhoneSimulator4.3.sdk/System/Library/LaunchDaemons - The “configure” task adds the necessary environment variables
- The “start” task loads and starts the daemons
- The “stop” task stops the daemons when we are done with the tests.
This code sits inside our Rakefile and will need some customization to work if you use it with your continuous integration. Hopefully this will allow you to test more aspects of your applications.