Class: RunLoop::SimControl
- Inherits:
-
Object
- Object
- RunLoop::SimControl
- Defined in:
- lib/run_loop/sim_control.rb
Overview
‘puts` calls need to be replaced with proper logging
All command line tools are run in the context of ‘xcrun`.
One class interact with the iOS Simulators.
Throughout this class’s documentation, there are references to the _current version of Xcode_. The current Xcode version is the one returned by ‘xcrun xcodebuild`. The current Xcode version can be set using `xcode-select` or overridden using the `DEVELOPER_DIR`.
Class Method Summary collapse
-
.terminate_all_sims ⇒ Object
Terminates all simulators.
Instance Method Summary collapse
- #accessibility_enabled?(device) ⇒ Boolean
- #enable_accessibility(device) ⇒ Object
- #enable_software_keyboard(device) ⇒ Object
- #ensure_accessibility(device) ⇒ Object
- #ensure_software_keyboard(device) ⇒ Object
-
#launch_sim(opts = {}) ⇒ Object
If it is not already running, launch the simulator for the current version of Xcode.
-
#pbuddy ⇒ RunLoop::PlistBuddy
Return an instance of PlistBuddy.
-
#quit_sim(opts = {}) ⇒ Object
If it is running, quit the simulator for the current version of Xcode.
-
#relaunch_sim(opts = {}) ⇒ Object
Relaunch the simulator for the current version of Xcode.
-
#reset_sim_content_and_settings(opts = {}) ⇒ Object
Resets the simulator content and settings.
-
#sim_is_running? ⇒ Boolean
Is the simulator for the current version of Xcode running?.
-
#sim_udid?(udid) ⇒ Boolean
Is the arg a valid Xcode >= 6.0 simulator udid?.
- #simulators ⇒ Object
- #software_keyboard_enabled?(device) ⇒ Boolean
-
#xctools ⇒ RunLoop::XCTools
Returns an instance of XCTools.
Class Method Details
.terminate_all_sims ⇒ Object
Sends ‘kill -9` to all Simulator processes. Use sparingly or not at all.
Terminates all simulators.
SimulatorBridge launchd_sim ScriptAgent
There can be only one simulator running at a time. However, during gem testing, situations can arise where multiple simulators are active.
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/run_loop/sim_control.rb', line 128 def self.terminate_all_sims # @todo Throwing SpringBoard crashed UI dialog. # Tried the gentle approach first; it did not work. # SimControl.new.quit_sim({:post_quit_wait => 0.5}) processes = ['iPhone Simulator.app', 'iOS Simulator.app', # Multiple launchd_sim processes have been causing problems. This # is a first pass at investigating what it would mean to kill the # launchd_sim process. 'launchd_sim' # RE: Throwing SpringBoard crashed UI dialog # These are children of launchd_sim. I tried quiting them # to suppress related UI dialogs about crashing processes. Killing # them can throw 'launchd_sim' UI Dialogs #'SimulatorBridge', 'SpringBoard', 'ScriptAgent', 'configd_sim', 'xpcproxy_sim' ] # @todo Maybe should try to send -TERM first and -KILL if TERM fails. # @todo Needs benchmarking. processes.each do |process_name| descripts = `xcrun ps x -o pid,command | grep "#{process_name}" | grep -v grep`.strip.split("\n") descripts.each do |process_desc| pid = process_desc.split(' ').first Open3.popen3("xcrun kill -9 #{pid} && xcrun wait #{pid}") do |_, stdout, stderr, _| if ENV['DEBUG_UNIX_CALLS'] == '1' out = stdout.read.strip err = stderr.read.strip next if out.to_s.empty? and err.to_s.empty? puts "Terminate all simulators: kill process '#{process_name}: #{pid}' => stdout: '#{out}' | stderr: '#{err}'" end end end end end |
Instance Method Details
#accessibility_enabled?(device) ⇒ Boolean
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'lib/run_loop/sim_control.rb', line 336 def accessibility_enabled?(device) plist = device.simulator_accessibility_plist_path return false unless File.exist?(plist) if device.version >= RunLoop::Version.new('8.0') plist_hash = SDK_80_ACCESSIBILITY_PROPERTIES_HASH else plist_hash = SDK_LT_80_ACCESSIBILITY_PROPERTIES_HASH end plist_hash.each do |_, details| key = details[:key] value = details[:value] unless pbuddy.plist_read(key, plist) == "#{value}" return false end end true end |
#enable_accessibility(device) ⇒ Object
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 |
# File 'lib/run_loop/sim_control.rb', line 365 def enable_accessibility(device) debug_logging = RunLoop::Environment.debug? quit_sim plist_path = device.simulator_accessibility_plist_path if device.version >= RunLoop::Version.new('8.0') plist_hash = SDK_80_ACCESSIBILITY_PROPERTIES_HASH else plist_hash = SDK_LT_80_ACCESSIBILITY_PROPERTIES_HASH end unless File.exist? plist_path preferences_dir = File.join(device.simulator_root_dir, 'data/Library/Preferences') FileUtils.mkdir_p(preferences_dir) plist = CFPropertyList::List.new data = {} plist.value = CFPropertyList.guess(data) plist.save(plist_path, CFPropertyList::List::FORMAT_BINARY) end msgs = [] successes = plist_hash.map do |hash_key, settings| success = pbuddy.plist_set(settings[:key], settings[:type], settings[:value], plist_path) unless success if debug_logging if settings[:type] == 'bool' value = settings[:value] ? 'YES' : 'NO' else value = settings[:value] end msgs << "could not set #{hash_key} => '#{settings[:key]}' to #{value}" end end success end if successes.all? true else return false, msgs end end |
#enable_software_keyboard(device) ⇒ Object
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
# File 'lib/run_loop/sim_control.rb', line 438 def enable_software_keyboard(device) debug_logging = RunLoop::Environment.debug? quit_sim plist_path = device.simulator_preferences_plist_path unless File.exist? plist_path preferences_dir = File.join(device.simulator_root_dir, 'data/Library/Preferences') FileUtils.mkdir_p(preferences_dir) plist = CFPropertyList::List.new data = {} plist.value = CFPropertyList.guess(data) plist.save(plist_path, CFPropertyList::List::FORMAT_BINARY) end msgs = [] successes = CORE_SIMULATOR_KEYBOARD_PROPERTIES_HASH.map do |hash_key, settings| success = pbuddy.plist_set(settings[:key], settings[:type], settings[:value], plist_path) unless success if debug_logging if settings[:type] == 'bool' value = settings[:value] ? 'YES' : 'NO' else value = settings[:value] end msgs << "could not set #{hash_key} => '#{settings[:key]}' to #{value}" end end success end if successes.all? true else return false, msgs end end |
#ensure_accessibility(device) ⇒ Object
357 358 359 360 361 362 363 |
# File 'lib/run_loop/sim_control.rb', line 357 def ensure_accessibility(device) if accessibility_enabled?(device) true else enable_accessibility(device) end end |
#ensure_software_keyboard(device) ⇒ Object
430 431 432 433 434 435 436 |
# File 'lib/run_loop/sim_control.rb', line 430 def ensure_software_keyboard(device) if software_keyboard_enabled?(device) true else enable_software_keyboard(device) end end |
#launch_sim(opts = {}) ⇒ Object
Consider migrating apple script call to xctools.
If it is not already running, launch the simulator for the current version of Xcode.
83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/run_loop/sim_control.rb', line 83 def launch_sim(opts={}) unless sim_is_running? default_opts = {:post_launch_wait => RunLoop::Environment.sim_post_launch_wait || 2.0, :hide_after => false} merged_opts = default_opts.merge(opts) `xcrun open -a "#{sim_app_path}"` if merged_opts[:hide_after] `xcrun /usr/bin/osascript -e 'tell application "System Events" to keystroke "h" using command down'` end sleep(merged_opts[:post_launch_wait]) if merged_opts[:post_launch_wait] end end |
#pbuddy ⇒ RunLoop::PlistBuddy
Return an instance of PlistBuddy.
45 46 47 |
# File 'lib/run_loop/sim_control.rb', line 45 def pbuddy @pbuddy ||= RunLoop::PlistBuddy.new end |
#quit_sim(opts = {}) ⇒ Object
Consider migrating apple script call to xctools.
If it is running, quit the simulator for the current version of Xcode.
62 63 64 65 66 67 68 69 |
# File 'lib/run_loop/sim_control.rb', line 62 def quit_sim(opts={}) if sim_is_running? default_opts = {:post_quit_wait => 1.0 } merged_opts = default_opts.merge(opts) `echo 'application "#{sim_name}" quit' | xcrun osascript` sleep(merged_opts[:post_quit_wait]) if merged_opts[:post_quit_wait] end end |
#relaunch_sim(opts = {}) ⇒ Object
Relaunch the simulator for the current version of Xcode. If that simulator is already running, it is quit.
108 109 110 111 112 113 114 115 |
# File 'lib/run_loop/sim_control.rb', line 108 def relaunch_sim(opts={}) default_opts = {:post_quit_wait => 1.0, :post_launch_wait => RunLoop::Environment.sim_post_launch_wait || 2.0, :hide_after => false} merged_opts = default_opts.merge(opts) quit_sim(merged_opts) launch_sim(merged_opts) end |
#reset_sim_content_and_settings(opts = {}) ⇒ Object
Resets the simulator content and settings.
In Xcode < 6, it is analogous to touching the menu item _for every
simulator_, regardless of SDK.
In Xcode 6, the default is the same; the content and settings for every
simulator is erased. However, in Xcode 6 it is possible to pass
a `:sim_udid` as a option to erase an individual simulator.
On Xcode 5, it works by deleting the following directories:
-
~/Library/Application Support/iPhone Simulator/Library
-
~/Library/Application Support/iPhone Simulator/Library/<sdk>
and relaunching the iOS Simulator which will recreate the Library directory and the latest SDK directory.
On Xcode 6, it uses the ‘simctl erase <udid>` command line tool.
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/run_loop/sim_control.rb', line 200 def reset_sim_content_and_settings(opts={}) default_opts = {:post_quit_wait => 1.0, :post_launch_wait => RunLoop::Environment.sim_post_launch_wait || 3.0, :hide_after => false, :sim_udid => nil} merged_opts = default_opts.merge(opts) quit_sim(merged_opts) # WARNING - DO NOT TRY TO DELETE Developer/CoreSimulator/Devices! # Very bad things will happen. Unlike Xcode < 6, the re-launching the # simulator will _not_ recreate the SDK (aka Devices) directories. if xcode_version_gte_6? simctl_reset(merged_opts[:sim_udid]) else sim_lib_path = File.join(sim_app_support_dir, 'Library') FileUtils.rm_rf(sim_lib_path) existing_sim_sdk_or_device_data_dirs.each do |dir| FileUtils.rm_rf(dir) end launch_sim(merged_opts) # This is tricky because we need to wait for the simulator to recreate # the directories. Specifically, we need the Accessibility plist to be # exist so subsequent calabash launches will be able to enable # accessibility. # # The directories take ~3.0 - ~5.0 to create. counter = 0 loop do break if counter == 80 dirs = existing_sim_sdk_or_device_data_dirs if dirs.count == 0 sleep(0.2) else break if dirs.all? { |dir| plist = File.("#{dir}/Library/Preferences/com.apple.Accessibility.plist") File.exist?(plist) } sleep(0.2) end counter = counter + 1 end end end |
#sim_is_running? ⇒ Boolean
Is the simulator for the current version of Xcode running?
51 52 53 |
# File 'lib/run_loop/sim_control.rb', line 51 def sim_is_running? not sim_pid.nil? end |
#sim_udid?(udid) ⇒ Boolean
Is the arg a valid Xcode >= 6.0 simulator udid?
313 314 315 |
# File 'lib/run_loop/sim_control.rb', line 313 def sim_udid?(udid) udid.length == 36 and udid[XCODE_6_SIM_UDID_REGEX,0] != nil end |
#simulators ⇒ Object
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/run_loop/sim_control.rb', line 317 def simulators unless xcode_version_gte_51? raise RuntimeError, 'simctl is only available on Xcode >= 6' end if xcode_version_gte_6? hash = simctl_list :devices sims = [] hash.each_pair do |sdk, list| list.each do |details| sims << RunLoop::Device.new(details[:name], sdk, details[:udid], details[:state]) end end sims else raise NotImplementedError, 'the simulators method is not available yet for Xcode 5.1.1' end end |
#software_keyboard_enabled?(device) ⇒ Boolean
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
# File 'lib/run_loop/sim_control.rb', line 411 def software_keyboard_enabled?(device) unless xcode_version_gte_51? raise RuntimeError, 'Keyboard enabling is only available on Xcode >= 6' end plist = device.simulator_preferences_plist_path return false unless File.exist?(plist) CORE_SIMULATOR_KEYBOARD_PROPERTIES_HASH.each do |_, details| key = details[:key] value = details[:value] unless pbuddy.plist_read(key, plist) == "#{value}" return false end end true end |
#xctools ⇒ RunLoop::XCTools
Returns an instance of XCTools.
19 20 21 |
# File 'lib/run_loop/sim_control.rb', line 19 def xctools @xctools ||= RunLoop::XCTools.new end |