class RunLoop::CoreSimulator
A class to manage interactions with CoreSimulators.
Constants
- CORE_SIMULATOR_DEVICE_DIR
@!visibility private
- DEFAULT_OPTIONS
These options control various aspects of an app’s life cycle on the iOS Simulator.
You can override these values if they do not work in your environment.
For cucumber users, the best place to override would be in your features/support/env.rb.
For example:
- MANAGED_PROCESSES
@!visibility private
- METADATA_PLIST
@!visibility private
- PREFERENCES_PLIST
@!visibility private
- SIMULATOR_QUIT_PROCESSES
@!visibility private Pattern:
- ‘< process name >’, < send term first >
- WAIT_FOR_SIMULATOR_STATE_INTERVAL
@!visibility private This should not be overridden
Attributes
@!visibility private
@!visibility private
@!visibility private
@!visibility private
@!visibility private
@!visibility private
Public Class Methods
@!visibility private
@param [RunLoop::Device, String] device a simulator UDID, instruments-ready
name, or a RunLoop::Device
@param [String] bundle_identifier the app to check for
@raise [ArgumentError] if no device can be found matching the UDID or
instruments-ready name
@raise [ArgumentError] if device is not a simulator @raise [ArgumentError] if language_code is invalid
@return [Boolean] true if the app with the identifier is installed
# File lib/run_loop/core_simulator.rb, line 364 def self.app_installed?(device, bundle_identifier, options={}) merged_options = {:xcode => RunLoop::Xcode.new}.merge(options) if device.is_a?(RunLoop::Device) simulator = device else simulator = RunLoop::Device.device_with_identifier(device, merged_options) end if simulator.physical_device? raise ArgumentError, "The device must be a simulator" end start = Time.now installed = self.send(:user_app_installed?, device, bundle_identifier) || self.send(:system_app_installed?, bundle_identifier, merged_options[:xcode]) RunLoop.log_debug("Took #{Time.now - start} seconds to check if app was installed") installed end
@!visibility private
Connect the hardware keyboard so users can use the host machine keyboard to type text during testing.
# File lib/run_loop/core_simulator.rb, line 266 def self.ensure_hardware_keyboard_connected(pbuddy) plist = self.simulator_preferences_plist(pbuddy) pbuddy.plist_set("ConnectHardwareKeyboard", "bool", true, plist) end
@!visibility private Erase a simulator. This is the same as touching the Simulator “Reset Content & Settings” menu item.
@param [RunLoop::Device] simulator The simulator to erase @param [Hash] options Control the behavior of the method. @option options [Numeric] :timeout How long to wait for simctl to
shutdown the simulator. This is necessary for the erase to succeed.
@raise RuntimeError If the simulator cannot be shutdown @raise RuntimeError If the simulator cannot be erased @raise ArgumentError If the simulator is a physical device
# File lib/run_loop/core_simulator.rb, line 283 def self.erase(simulator, options={}) if simulator.physical_device? raise ArgumentError, "#{simulator} is a physical device. This method is only for Simulators" end merged_options = DEFAULT_OPTIONS.merge(options) simctl = merged_options[:simctl] || RunLoop::Simctl.new timeout = merged_options[:timeout] || merged_options[:wait_for_state_timeout] simctl.erase(simulator, timeout, WAIT_FOR_SIMULATOR_STATE_INTERVAL) end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 257 def self.hardware_keyboard_connected?(pbuddy) plist = self.simulator_preferences_plist(pbuddy) pbuddy.plist_read("ConnectHardwareKeyboard", plist) end
@param [RunLoop::Device] device The device. @param [RunLoop::App] app The application. @param [Hash] options Controls the behavior of this class. @option options :quit_sim_on_init (true) If true, quit any running @option options :xcode An instance of Xcode to use
simulators in the initialize method.
# File lib/run_loop/core_simulator.rb, line 392 def initialize(device, app, options={}) @app = app @device = device @xcode = options[:xcode] # stdio.pipe - can cause problems finding the SHA of a simulator rm_instruments_pipe end
@!visibility private Quit any Simulator.app or iOS Simulator.app
# File lib/run_loop/core_simulator.rb, line 207 def self.quit_simulator RunLoop::DeviceAgent::Xcodebuild.terminate_simulator_tests SIMULATOR_QUIT_PROCESSES.each do |process_details| process_name = process_details[0] send_term_first = process_details[1] self.term_or_kill(process_name, send_term_first) end end
@!visibility private
@param [RunLoop::Device, String] device a simulator UDID, instruments-ready
name, or a RunLoop::Device
@param [String] lang_code a language code
@raise [ArgumentError] if no device can be found matching the UDID or
instruments-ready name
@raise [ArgumentError] if device is not a simulator @raise [ArgumentError] if language_code is invalid
# File lib/run_loop/core_simulator.rb, line 336 def self.set_language(device, lang_code) if device.is_a?(RunLoop::Device) simulator = device else simulator = RunLoop::Device.device_with_identifier(device) end if simulator.physical_device? raise ArgumentError, "The language cannot be set on physical devices" end self.quit_simulator RunLoop.log_debug("Setting preferred language to '#{lang_code}'") simulator.simulator_set_language(lang_code) end
@!visibility private
@param [RunLoop::Device, String] device a simulator UDID, instruments-ready
name, or a RunLoop::Device.
@param [String] locale_code a locale code
@raise [ArgumentError] if no device can be found matching the UDID or
instruments-ready name
@raise [ArgumentError] if device is not a simulator @raise [ArgumentError] if locale_code is invalid
# File lib/run_loop/core_simulator.rb, line 309 def self.set_locale(device, locale_code) if device.is_a?(RunLoop::Device) simulator = device else simulator = RunLoop::Device.device_with_identifier(device) end if simulator.physical_device? raise ArgumentError, "The locale cannot be set on physical devices" end self.quit_simulator RunLoop.log_debug("Setting locale to '#{locale_code}'") simulator.simulator_set_locale(locale_code) end
@!visibility private
Per-user CoreSimulator
preferences located in ~/Library/Preferences
# File lib/run_loop/core_simulator.rb, line 248 def self.simulator_preferences_plist(pbuddy) if !File.exist?(PREFERENCES_PLIST) pbuddy.create_plist(PREFERENCES_PLIST) end PREFERENCES_PLIST end
@!visibility private
Terminate CoreSimulator
related processes. This processes can accumulate as testing proceeds and can cause instability.
# File lib/run_loop/core_simulator.rb, line 151 def self.terminate_core_simulator_processes start = Time.now self.quit_simulator MANAGED_PROCESSES.each do |process_name| send_term_first = false self.term_or_kill(process_name, send_term_first) end ps_name_fn = lambda do |pid| args = ["ps", "-o", "comm=", "-p", pid.to_s] out = RunLoop::Shell.run_shell_command(args)[:out] if out && out.strip != "" out.strip else "UNKNOWN PROCESS: #{pid}" end end term_options = { :timeout => 0.1 } kill_options = { :timeout => 0.0 } RunLoop::ProcessWaiter.pgrep_f("launchd_sim").each do |pid| process_name = ps_name_fn.call(pid) RunLoop::ProcessTerminator.new(pid, 'KILL', process_name, kill_options).kill_process end RunLoop::ProcessWaiter.pgrep_f("iPhoneSimulator").each do |pid| process_name = ps_name_fn.call(pid) RunLoop::ProcessTerminator.new(pid, 'KILL', process_name, kill_options).kill_process end RunLoop::ProcessWaiter.pgrep_f("CoreSimulator").each do |pid| args = ["ps", "-o", "uid=", pid.to_s] uid = RunLoop::Shell.run_shell_command(args)[:out].strip process_name = File.basename(ps_name_fn.call(pid)) if uid != "0" term = RunLoop::ProcessTerminator.new(pid, 'TERM', process_name, term_options) killed = term.kill_process if !killed term = RunLoop::ProcessTerminator.new(pid, 'KILL', process_name, kill_options) term.kill_process end end end elapsed = Time.now - start RunLoop.log_debug("Took #{elapsed} to terminate CoreSimulator Services") end
@!visibility private
Some operations, like erase, require that the simulator be ‘Shutdown’.
@param [RunLoop::Device] simulator the sim to wait for @param [String] target_state the state to wait for
# File lib/run_loop/core_simulator.rb, line 224 def self.wait_for_simulator_state(simulator, target_state) now = Time.now timeout = DEFAULT_OPTIONS[:wait_for_state_timeout] poll_until = now + timeout delay = WAIT_FOR_SIMULATOR_STATE_INTERVAL in_state = false while Time.now < poll_until in_state = simulator.update_simulator_state == target_state break if in_state sleep delay if delay != 0 end elapsed = Time.now - now RunLoop.log_debug("Waited for #{elapsed} seconds for device to have state: '#{target_state}'.") unless in_state raise "Expected '#{target_state} but found '#{simulator.state}' after waiting." end in_state end
Private Class Methods
@!visibility private
# File lib/run_loop/core_simulator.rb, line 1197 def self.system_app_installed?(bundle_identifier, xcode) apps_dir = self.send(:system_applications_dir, xcode) serv_dir = self.send(:system_services_dir, xcode) return false if !File.exist?(apps_dir) || !File.exist?(serv_dir) if xcode.version_gte_90? black_list = [ "AirMusic.app", "AirPodcasts.app", "AppStore.app", "Calculator.app", "CheckerBoard.app", "CTCarrierSpaceAuth.app", "Diagnostics.app", "DiagnosticsService.app", "FaceTime.app", "Feedback Assistant iOS.app", "FindMyFriends.app", "iBooks.app", "Magnifier.app", "MobileMail.app", "MobileNotes.app", "Music.app", "Podcasts.app", "PreBoard.app", "SoftwareUpdateUIService.app", "StoreDemoViewService.app", "TV.app", "Videos.app" ] else black_list = ["Fitness.app", "Photo Booth.app", "ScreenSharingViewService.app"] end [Dir.glob("#{apps_dir}/*.app"), Dir.glob("#{serv_dir}/*.app")].flatten.detect do |app_dir| basename = File.basename(app_dir) if black_list.include?(basename) false else begin RunLoop::App.new(app_dir).bundle_identifier == bundle_identifier rescue ArgumentError => _ bundle_name = File.basename(app_dir) RunLoop.log_debug("Could not create an App from simulator system app: #{bundle_name}") nil end end end end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 1165 def self.system_applications_dir(xcode=RunLoop::Xcode.new) if xcode.version_gte_90? apps_dir = File.join(xcode.core_simulator_dir, "Profiles", "Runtimes", "iOS.simruntime", "Contents", "Resources", "RuntimeRoot", "Applications") else apps_dir = File.join(xcode.developer_dir, "Platforms", "iPhoneSimulator.platform", "Developer", "SDKs", "iPhoneSimulator.sdk", "Applications") end File.expand_path(apps_dir) end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 1181 def self.system_services_dir(xcode=RunLoop::Xcode.new) if xcode.version_gte_90? apps_dir = File.join(xcode.core_simulator_dir, "Profiles", "Runtimes", "iOS.simruntime", "Contents", "Resources", "RuntimeRoot", "System", "Library", "CoreServices") else apps_dir = File.join(xcode.developer_dir, "Platforms", "iPhoneSimulator.platform", "Developer", "SDKs", "iPhoneSimulator.sdk", "System", "Library", "CoreServices") end File.expand_path(apps_dir) end
Send ‘TERM’ then ‘KILL’ to allow processes to quit cleanly.
# File lib/run_loop/core_simulator.rb, line 593 def self.term_or_kill(process_name, send_term_first) term_options = { :timeout => 0.5 } kill_options = { :timeout => 0.0 } RunLoop::ProcessWaiter.new(process_name).pids.each do |pid| # We could try to determine if terminating the process will be successful # by asking for the parent pid and the user id. This adds another call # to `ps` and does not save any time. It is easier to simply let the # ProcessTerminator fail. The downside is that a failure will appear # in the debug log. # # macOS is looking more like iOS. Process names like 'mobileassetd' are # found in both operating systems. args = ["ps", "-o", "uid=", pid.to_s] uid = RunLoop::Shell.run_shell_command(args)[:out].strip if uid != "0" killed = false if send_term_first term = RunLoop::ProcessTerminator.new(pid, 'TERM', process_name, term_options) killed = term.kill_process end if !killed term = RunLoop::ProcessTerminator.new(pid, 'KILL', process_name, kill_options) term.kill_process end end end end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 1234 def self.user_app_installed?(device, bundle_identifier) core_sim = self.new(device, bundle_identifier) sim_apps_dir = core_sim.send(:device_applications_dir) Dir.glob("#{sim_apps_dir}/**/*.app").find do |path| RunLoop::App.new(path).bundle_identifier == bundle_identifier end end
Public Instance Methods
Is this app installed?
# File lib/run_loop/core_simulator.rb, line 543 def app_is_installed? if installed_app_bundle_dir || simctl.app_container(device, app.bundle_identifier) true else false end end
Install the app.
-
If the app is not installed, it is installed.
-
Does nothing, if the app is the same as the one that is installed.
-
Installs the app if it is different from the installed app.
The app sandbox is not touched.
# File lib/run_loop/core_simulator.rb, line 529 def install installed_app_bundle = installed_app_bundle_dir # App is not installed. Use simctl interface to install. if !installed_app_bundle installed_app_bundle = install_app_with_simctl else ensure_app_same end installed_app_bundle end
Launch the app on the simulator.
-
If the app is not installed, it is installed.
-
If the app is different from the app that is installed, it is installed.
# File lib/run_loop/core_simulator.rb, line 482 def launch install # If the app is the same, install will not launch the simulator. # In order to launch the app, the simulator needs to be running. # launch_simulator ensures that the sim is launched and will not # relaunch it. launch_simulator tries = app_launch_retries RunLoop.log_debug("Trying #{tries} times to launch #{app.bundle_identifier} on #{device}") last_error = try_to_launch_app_n_times(tries) if last_error raise RuntimeError, %Q[ Could not launch #{app.bundle_identifier} on #{device} after trying #{tries} times: #{last_error}: #{last_error.message} ] end wait_for_app_launch end
Launch the simulator indicated by device.
# File lib/run_loop/core_simulator.rb, line 433 def launch_simulator(options={}) merged_options = { :wait_for_stable => true }.merge(options) if !simulator_requires_relaunch? RunLoop.log_debug("Simulator is running and does not require a relaunch.") return end RunLoop::CoreSimulator.quit_simulator RunLoop::CoreSimulator.ensure_hardware_keyboard_connected(pbuddy) sim_keyboard.ensure_soft_keyboard_will_show sim_keyboard.ensure_keyboard_tutorial_disabled args = ['open', '-g', '-a', sim_app_path, '--args', '-CurrentDeviceUDID', device.udid, "-ConnectHardwareKeyboard", "0", "-DeviceBootTimeout", "120", # Yes, this is the argument even though it is not spelled correctly "-DetatchOnAppQuit", "0", "-DetachOnWindowClose", "0", "LAUNCHED_BY_RUN_LOOP"] RunLoop.log_debug("Launching #{device} with:") RunLoop.log_unix_cmd("xcrun #{args.join(' ')}") start_time = Time.now pid = Process.spawn('xcrun', *args) Process.detach(pid) options = { :timeout => 5, :raise_on_timeout => true } RunLoop::ProcessWaiter.new(sim_name, options).wait_for_any if merged_options[:wait_for_stable] device.simulator_wait_for_stable_state end elapsed = Time.now - start_time RunLoop.log_debug("Took #{elapsed} seconds to launch the simulator") true end
Resets the app sandbox.
Does nothing if the app is not installed.
# File lib/run_loop/core_simulator.rb, line 555 def reset_app_sandbox return true if !app_is_installed? RunLoop::CoreSimulator.quit_simulator RunLoop::CoreSimulator.wait_for_simulator_state(device, "Shutdown") reset_app_sandbox_internal end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 422 def simctl @simctl ||= RunLoop::Simctl.new end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 427 def simulator_requires_relaunch? [simulator_state_requires_relaunch?, running_apps_require_relaunch?].any? end
Uninstalls the app and clears the sandbox.
# File lib/run_loop/core_simulator.rb, line 565 def uninstall_app_and_sandbox return true if !app_is_installed? uninstall_app_with_simctl true end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 512 def wait_for_app_launch options = { :timeout => 10, :raise_on_timeout => true } RunLoop::ProcessWaiter.new(app.executable_name, options).wait_for_any device.simulator_wait_for_stable_state true end
Private Instance Methods
The Documents directory in the sandbox.
# File lib/run_loop/core_simulator.rb, line 969 def app_documents_dir base_dir = app_sandbox_dir if base_dir.nil? nil else File.join(base_dir, 'Documents') end end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 774 def app_launch_retries DEFAULT_OPTIONS[:app_launch_retries] end
The Library directory in the sandbox.
# File lib/run_loop/core_simulator.rb, line 949 def app_library_dir base_dir = app_sandbox_dir if base_dir.nil? nil else File.join(base_dir, 'Library') end end
The Library/Preferences directory in the sandbox.
# File lib/run_loop/core_simulator.rb, line 959 def app_library_preferences_dir base_dir = app_library_dir if base_dir.nil? nil else File.join(base_dir, 'Preferences') end end
The sandbox directory for the app.
~/Library/Developer/CoreSimulator/Devices/<UDID>/Containers/Data/Application
Contains Library, Documents, and tmp directories.
# File lib/run_loop/core_simulator.rb, line 925 def app_sandbox_dir app_install_dir = installed_app_bundle_dir return nil if app_install_dir.nil? if sdk_gte_8? app_sandbox_dir_sdk_gte_8 else app_install_dir end end
# File lib/run_loop/core_simulator.rb, line 935 def app_sandbox_dir_sdk_gte_8 containers_data_dir = File.join(device_data_dir, 'Containers', 'Data', 'Application') apps = Dir.glob("#{containers_data_dir}/**/#{METADATA_PLIST}") match = apps.find do |metadata_plist| pbuddy.plist_read('MCMMetadataIdentifier', metadata_plist) == app.bundle_identifier end if match File.dirname(match) else nil end end
The tmp directory in the sandbox.
# File lib/run_loop/core_simulator.rb, line 979 def app_tmp_dir base_dir = app_sandbox_dir if base_dir.nil? nil else File.join(base_dir, 'tmp') end end
Required after when installing and uninstalling.
# File lib/run_loop/core_simulator.rb, line 994 def clear_device_launch_csstore glob = File.join(device_caches_dir, "com.apple.LaunchServices-*.csstore") Dir.glob(glob) do | ccstore | FileUtils.rm_f ccstore end end
Detect an incomplete app installation.
# File lib/run_loop/core_simulator.rb, line 1050 def complete_app_install?(app_bundle_dir) base_dir = File.dirname(app_bundle_dir) plist = File.join(base_dir, METADATA_PLIST) File.exist?(plist) end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 841 def device_agent_launched_by_xcode?(running_apps) process_info = running_apps["XCTRunner"] || running_apps["DeviceAgent-Runner"] return false if !process_info process_info[:args][/CBX_LAUNCHED_BY_XCODE/] end
The applications directory for the device.
~/Library/Developer/CoreSimulator/Devices/<UDID>/Containers/Bundle/Application
# File lib/run_loop/core_simulator.rb, line 910 def device_applications_dir @device_app_dir ||= lambda do if sdk_gte_8? File.join(device_data_dir, 'Containers', 'Bundle', 'Application') else File.join(device_data_dir, 'Applications') end end.call end
A cache of installed apps on the device.
# File lib/run_loop/core_simulator.rb, line 989 def device_caches_dir @device_caches_dir ||= File.join(device_data_dir, 'Library', 'Caches') end
The data directory for the the device.
~/Library/Developer/CoreSimulator/Devices/<UDID>/data
# File lib/run_loop/core_simulator.rb, line 903 def device_data_dir @device_data_dir ||= File.join(CORE_SIMULATOR_DEVICE_DIR, device.udid, 'data') end
-
Does nothing if the app is not installed.
-
Does nothing if the app the same as the app that is installed
-
Installs app if it is different from the installed app
# File lib/run_loop/core_simulator.rb, line 1072 def ensure_app_same installed_app_bundle = installed_app_bundle_dir if !installed_app_bundle RunLoop.log_debug("App: #{app} is not installed") return true end installed_sha = installed_app_sha1 app_sha = app.sha1 if installed_sha == app_sha RunLoop.log_debug("Installed app is the same as #{app}") return true end RunLoop.log_debug("The app you are testing is not the same as the app that is installed.") RunLoop.log_debug(" Installed app SHA: #{installed_sha}") RunLoop.log_debug(" App to launch SHA: #{app_sha}") RunLoop.log_debug("Will install #{app}") uninstall_app_with_simctl install_app_with_simctl true end
Cleans up bad installations of an app. For unknown reasons, an app bundle can exist, but be unrecognized by CoreSimulator
. If we detect a case like this, we need to clean up the installation.
# File lib/run_loop/core_simulator.rb, line 1035 def ensure_complete_app_installation(app_bundle_dir) return nil if app_bundle_dir.nil? return app_bundle_dir if complete_app_install?(app_bundle_dir) # Remove the directory that contains the app bundle base_dir = File.dirname(app_bundle_dir) FileUtils.rm_rf(base_dir) # Clean up Containers/Data/Application remove_stale_data_containers nil end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 723 def install_app_with_simctl launch_simulator app_size = RunLoop::Directory.size(app.path, :mb) sim_size = device.simulator_size_on_disk target_size = sim_size + app_size timeout = DEFAULT_OPTIONS[:install_app_timeout] simctl.install(device, app, timeout) current_size = device.simulator_size_on_disk start = Time.now while current_size <= target_size && Time.now < start + 5 sleep(0.5) current_size = device.simulator_size_on_disk end elapsed = Time.now - start if current_size > target_size RunLoop.log_debug("Waited for #{elapsed} seconds for app to install") else RunLoop.log_debug("Timed out after #{elapsed} seconds for app to install") RunLoop.log_debug("Expected sim size #{current_size} >= #{target_size}") end installed_app_bundle_dir end
Returns the path to the installed app bundle directory (.app).
If this method returns nil, the app is not installed.
# File lib/run_loop/core_simulator.rb, line 1019 def installed_app_bundle_dir sim_app_dir = device_applications_dir return nil if !File.exist?(sim_app_dir) app_bundle_dir = Dir.glob("#{sim_app_dir}/**/*.app").find do |path| RunLoop::App.new(path).bundle_identifier == app.bundle_identifier end app_bundle_dir = ensure_complete_app_installation(app_bundle_dir) app_bundle_dir end
The sha1 of the installed app.
# File lib/run_loop/core_simulator.rb, line 1002 def installed_app_sha1 installed_bundle = installed_app_bundle_dir if installed_bundle RunLoop::Directory.directory_digest(installed_bundle) else nil end end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 752 def launch_app_with_simctl timeout = DEFAULT_OPTIONS[:launch_app_timeout] simctl.launch(device, app, timeout) end
Remove stale data directories that might have appeared as a result of an incomplete app installation. See ensure_complete_app_installation
# File lib/run_loop/core_simulator.rb, line 1059 def remove_stale_data_containers containers_data_dir = File.join(device_data_dir, "Containers", "Data", "Application") apps = Dir.glob("#{containers_data_dir}/**/#{METADATA_PLIST}") apps.each do |metadata_plist| if pbuddy.plist_read("MCMMetadataIdentifier", metadata_plist) == app.bundle_identifier FileUtils.rm_rf(File.dirname(metadata_plist)) end end end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 1154 def reset_app_sandbox_internal reset_app_sandbox_internal_shared if sdk_gte_8? reset_app_sandbox_internal_sdk_gte_8 else reset_app_sandbox_internal_sdk_lt_8 end end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 1107 def reset_app_sandbox_internal_sdk_gte_8 lib_dir = app_library_dir RunLoop::Directory.recursive_glob_for_entries(lib_dir).each do |entry| if entry.include?('Preferences') # nop else if File.exist?(entry) FileUtils.rm_rf(entry) end end end prefs_dir = app_library_preferences_dir protected = ['com.apple.UIAutomation.plist', 'com.apple.UIAutomationPlugIn.plist'] RunLoop::Directory.recursive_glob_for_entries(prefs_dir).each do |entry| unless protected.include?(File.basename(entry)) if File.exist?(entry) FileUtils.rm_rf entry end end end end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 1132 def reset_app_sandbox_internal_sdk_lt_8 prefs_dir = app_library_preferences_dir RunLoop::Directory.recursive_glob_for_entries(prefs_dir).each do |entry| if entry.end_with?('.GlobalPreferences.plist') || entry.end_with?('com.apple.PeoplePicker.plist') # nop else if File.exist?(entry) FileUtils.rm_rf entry end end end # app preferences lives in device Library/Preferences device_prefs_dir = File.join(app_sandbox_dir, 'Library', 'Preferences') app_prefs_plist = File.join(device_prefs_dir, "#{app.bundle_identifier}.plist") if File.exist?(app_prefs_plist) FileUtils.rm_rf(app_prefs_plist) end end
@!visibility private
This stdio.pipe file causes problems when checking the size and taking the checksum of the core simulator directory.
# File lib/run_loop/core_simulator.rb, line 582 def rm_instruments_pipe device_tmp_dir = File.join(device_data_dir, 'tmp') Dir.glob("#{device_tmp_dir}/instruments_*/stdio.pipe") do |file| if File.exist?(file) RunLoop.log_debug("Deleting #{file}") FileUtils.rm_rf(file) end end end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 849 def running_apps_require_relaunch? running_apps = device.simulator_running_app_details if running_apps.empty? RunLoop.log_debug("Simulator relaunch not required: no running apps") return false end # DeviceAgent is running, but it was launched by Xcode. if device_agent_launched_by_xcode?(running_apps) RunLoop.log_debug("Simulator relaunch required: XCTRunner is controlled by Xcode") return true end # Why? # No app was passed to initializer. if app.nil? RunLoop.log_debug("Simulator relaunch required: no app was passed to CoreSimulator.new") return true end # AUT is running, but it was not launched by DeviceAgent. app_name = app.executable_name if running_apps[app_name] launch_arg = RunLoop::DeviceAgent::Client::AUT_LAUNCHED_BY_RUN_LOOP_ARG if !running_apps[app_name][:args][/#{launch_arg}/] RunLoop.log_debug("Simulator relaunch required: AUT is running, but not launched by run-loop") return true end end # This is the UITest behavior. UITest does not inspect the simulator for # system apps that are running - it only checks for running user apps. # # I don't think this condition is necessary, so we'll skip it for now, but # capture there is a difference between UITest and run-loop. # # There is some other application running on the simulator. # running_apps.delete("XCTRunner") # running_apps.delete(app_name) # # if running_apps.empty? # RunLoop.log_debug("Simulator relaunch not required: only XCTRunner and AUT are running") # false # else # RunLoop.log_debug("Simulator relaunch required: other applications are running") # true # end false end
@!visibility private
@return [Hash] details about the running simulator.
# File lib/run_loop/core_simulator.rb, line 648 def running_simulator_details process_name = sim_app_path args = ["ps", "x", "-o", "pid=,command="] hash = run_shell_command(args) exit_status = hash[:exit_status] if exit_status != 0 raise RuntimeError, %Q{Could not find the process details of #{sim_name} with: #{args.join(" ")} Command exited with status: #{exit_status} '#{hash[:out]}' } end if hash[:out].nil? || hash[:out] == "" raise RuntimeError, %Q{Could not find the process details of #{sim_name} with: #{args.join(" ")} Command had no output. } end lines = hash[:out].split($-0) match = lines.detect do |line| line[/#{process_name}/] end return {} if match.nil? hash = {} pid = match.split(" ").first.strip.to_i hash[:pid] = pid hash[:launched_by_run_loop] = match[/LAUNCHED_BY_RUN_LOOP/] hash end
Is the app that is install the same as the one we have in hand?
# File lib/run_loop/core_simulator.rb, line 1012 def same_sha1_as_installed? app.sha1 == installed_app_sha1 end
Required for support of iOS 7 CoreSimulators. Can be removed when Xcode support is dropped.
# File lib/run_loop/core_simulator.rb, line 799 def sdk_gte_8? device.version >= RunLoop::Version.new('8.0') end
@!visibility private Returns the path to the current simulator.
@return [String] The path to the simulator app for the current version of
Xcode.
# File lib/run_loop/core_simulator.rb, line 639 def sim_app_path @sim_app_path ||= begin "#{xcode.developer_dir}/Applications/#{sim_name}.app" end end
Returns the current simulator name.
@return [String] A String suitable for searching for a pid, quitting, or
launching the current simulator.
# File lib/run_loop/core_simulator.rb, line 630 def sim_name @sim_name ||= "Simulator" end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 804 def simulator_state_requires_relaunch? running_sim_details = running_simulator_details # Simulator is not running. if !running_sim_details[:pid] RunLoop.log_debug("Simulator relaunch required: simulator is not running.") return true end # Simulator is running, but run-loop did not launch it. if !running_sim_details[:launched_by_run_loop] RunLoop.log_debug("Simulator relaunch required: simulator was not launched by run_loop") return true end if !RunLoop::CoreSimulator.hardware_keyboard_connected?(pbuddy) RunLoop.log_debug("Simulator relaunch required: hardware keyboard not connected.") return true end if !sim_keyboard.soft_keyboard_will_show? RunLoop.log_debug("Simulator relaunch required: software keyboard is minimized") return true end # Simulator is running, run_loop launched it, but it is not Booted. device.update_simulator_state if device.state == "Booted" RunLoop.log_debug("Simulator relaunch not required: simulator has state 'Booted'") false else RunLoop.log_debug("Simulator relaunch required: simulator does not have state 'Booted'") true end end
@!visibility private
Returns nil if launch_app_with_simctl
succeeds and the error if it fails.
# File lib/run_loop/core_simulator.rb, line 760 def try_to_launch_app begin launch_app_with_simctl nil rescue RuntimeError, RunLoop::Xcrun::TimeoutError => error # Simulator is probably in a bad state. Restart the service. RunLoop::CoreSimulator.terminate_core_simulator_processes Kernel.sleep(0.5) launch_simulator error end end
@!visibility private
Returns nil if launch_app_with_simctl
succeeds and the error if it fails.
# File lib/run_loop/core_simulator.rb, line 781 def try_to_launch_app_n_times(tries) last_error = nil tries.times do |try| # Terminates CoreSimulatorService on failures and launches the simulator again. # Returns nil if app launched. # Returns rescued Runtime or Timeout errors. last_error = try_to_launch_app break if last_error.nil? RunLoop.log_debug("Failed to launch app on try #{try + 1} of #{tries}.") end last_error end
@!visibility private
# File lib/run_loop/core_simulator.rb, line 696 def uninstall_app_with_simctl launch_simulator app_size = RunLoop::Directory.size(app.path, :mb) sim_size = device.simulator_size_on_disk target_size = sim_size - app_size + 5 timeout = DEFAULT_OPTIONS[:install_app_timeout] simctl.uninstall(device, app, timeout) current_size = device.simulator_size_on_disk start = Time.now while current_size > target_size && Time.now < start + 5 sleep(0.5) current_size = device.simulator_size_on_disk end elapsed = Time.now - start if current_size <= target_size RunLoop.log_debug("Waited for #{elapsed} seconds for app to uninstall") else RunLoop.log_debug("Timed out after #{elapsed} seconds for app to uninstall") RunLoop.log_debug("Expected sim size #{current_size} < #{target_size}") end end