lib/run_loop/device.rb in run_loop-2.2.4 vs lib/run_loop/device.rb in run_loop-2.3.0
- old
+ new
@@ -29,11 +29,11 @@
# The maximum amount of time to wait for the simulator
# to stabilize. No errors are raised if this timeout is
# exceeded - if the default 30 seconds has passed, the
# simulator is probably stable enough for subsequent
# operations.
- :timeout => RunLoop::Environment.ci? ? 120 : 30
+ :timeout => RunLoop::Environment.ci? ? 240 : 120
}
attr_reader :name
attr_reader :version
attr_reader :udid
@@ -320,128 +320,58 @@
File.join(simulator_root_dir, "data/Library/Preferences/.GlobalPreferences.plist")
end.call
end
# @!visibility private
- #
- # Waits for three conditions:
- #
- # 1. The SHA sum of the simulator data/ directory to be stable.
- # 2. No more log messages are begin generated.
- # 3. 1 and 2 must hold for 1.5 seconds.
- #
- # When the simulator version is >= iOS 9, two more conditions are added to
- # get past the iOS 9+ boot screen.
- #
- # 4. Wait for com.apple.audio.SystemSoundServer-iOS-Simulator process to
- # start.
- # 5. 1 and 2 must hold for 1.5 seconds.
- #
- # When the simulator version is >= iOS 9 and the device is an iPad another
- # condition is added because simctl fails to correctly install applications;
- # the app and data container exists, but Springboard does not detect them.
- #
- # 6. 1 and 2 must hold for 1.5 seconds.
- #
- # TODO needs update for Xcode 8 + iOS 10 simulators.
+ # In megabytes
+ def simulator_size_on_disk
+ data_path = File.join(simulator_root_dir, 'data')
+ RunLoop::Directory.size(data_path, :mb)
+ end
+
+ # @!visibility private
def simulator_wait_for_stable_state
+ required = simulator_required_child_processes
- # How long to wait between stability checks.
- # Shorter than this gives false positives.
- delay = 0.5
-
- # How many times to wait for stable state.
- max_stable_count = 3
-
- # How long to wait for iOS 9 boot screen.
- boot_screen_wait_options = {
- :max_boot_screen_wait => 10,
- :raise_on_timeout => false
- }
-
- # How much additional time to wait for iOS 9+ iPads.
- #
- # Installing and launching on iPads is problematic.
- # Sometimes the app is installed, but SpringBoard does
- # not recognize that the app is installed even though
- # simctl says that it is.
- additional_ipad_delay = delay * 2
-
- # Adjust for CI environments
- if RunLoop::Environment.ci?
- max_stable_count = 5
- boot_screen_wait_options[:max_boot_screen_wait] = 20
- additional_ipad_delay = delay * 4
- end
-
- # iOS 9 simulators have an additional boot screen.
- is_gte_ios9 = version >= RunLoop::Version.new('9.0')
-
- # Xcode 8 simulators do not need to wait for log file
- is_xcode8 = RunLoop::Xcode.new.version_gte_8?
-
- # iOS > 9 iPad simulators need additional time to stabilize, especially
- # to ensure that `simctl install` notifies SpringBoard that a new app
- # has been installed.
- is_ipad = simulator_is_ipad?
-
timeout = SIM_STABLE_STATE_OPTIONS[:timeout]
now = Time.now
poll_until = now + timeout
RunLoop.log_debug("Waiting for simulator to stabilize with timeout: #{timeout} seconds")
+ footprint = simulator_size_on_disk
- current_dir_sha = simulator_data_directory_sha
- if is_xcode8
- current_log_sha = true
+ if version.major >= 9 && footprint < 18
+ first_launch = true
+ elsif version.major == 8
+ if version.minor >= 3 && footprint < 19
+ first_launch = true
+ else
+ first_launch = footprint < 11
+ end
else
- current_log_sha = simulator_log_file_sha
+ first_launch = false
end
- is_stable = false
- waited_for_boot = false
- waited_for_ipad = false
- stable_count = 0
-
- while Time.now < poll_until do
- latest_dir_sha = simulator_data_directory_sha
- if is_xcode8
- latest_log_sha = true
- else
- latest_log_sha = simulator_log_file_sha
- end
-
- is_stable = [current_dir_sha == latest_dir_sha,
- current_log_sha == latest_log_sha].all?
-
- if is_stable
- stable_count = stable_count + 1
- if stable_count == max_stable_count
- if is_gte_ios9 && !waited_for_boot
- process_name = "com.apple.audio.SystemSoundServer-iOS-Simulator"
- RunLoop::ProcessWaiter.new(process_name, boot_screen_wait_options).wait_for_any
- waited_for_boot = true
- stable_count = 0
- elsif is_gte_ios9 && is_ipad && !waited_for_ipad
- RunLoop.log_debug("Waiting additional time for iOS 9 iPad to stabilize")
- sleep(additional_ipad_delay)
- waited_for_ipad = true
- stable_count = 0
- else
- break
- end
+ while !required.empty? && Time.now < poll_until do
+ sleep(0.5)
+ required = required.map do |process_name|
+ if simulator_process_running?(process_name)
+ nil
+ else
+ process_name
end
- end
-
- current_dir_sha = latest_dir_sha
- current_log_sha = latest_log_sha
- sleep(delay)
+ end.compact
end
- if is_stable
+ if required.empty?
elapsed = Time.now - now
- RunLoop.log_debug("Waited a total of #{elapsed} seconds for simulator to stabilize")
+ RunLoop.log_debug("All required simulator processes have started after #{elapsed}")
+ if first_launch
+ RunLoop.log_debug("Detected a first launch, waiting a little longer - footprint was #{footprint} MB")
+ sleep(RunLoop::Environment.ci? ? 10 : 5)
+ end
+ RunLoop.log_debug("Waited for #{elapsed} seconds for simulator to stabilize")
else
RunLoop.log_debug("Timed out after #{timeout} seconds waiting for simulator to stabilize")
end
end
@@ -539,15 +469,67 @@
end
simulator_languages
end
+ # @!visibility private
+ def simulator_running_app_details
+ pids = simulator_running_app_pids
+ running_apps = {}
+
+ pids.each do |pid|
+ cmd = ["ps", "-o", "comm=", "-p", pid.to_s]
+
+ hash = run_shell_command(cmd)
+ out = hash[:out]
+
+ if out.nil? || out == "" || out.strip.nil?
+ nil
+ else
+ name = out.strip.split("/").last
+
+ cmd = ["ps", "-o", "command=", "-p", pid.to_s]
+ hash = run_shell_command(cmd)
+ out = hash[:out]
+
+ if out.nil? || out == "" || out.strip.nil?
+ nil
+ else
+ tokens = out.split("#{name} ")
+
+ # No arguments
+ if tokens.count == 1
+ args = ""
+ else
+ args = tokens.last.strip
+ end
+
+ running_apps[name] = {
+ args: args,
+ command: out.strip
+ }
+ end
+ end
+ end
+
+ running_apps
+ end
+
+=begin
+ PRIVATE METHODS
+=end
+
private
- attr_reader :pbuddy, :simctl, :xcrun
+ attr_reader :pbuddy, :simctl, :xcrun, :xcode
# @!visibility private
+ def xcode
+ @xcode ||= RunLoop::Xcode.new
+ end
+
+ # @!visibility private
def xcrun
RunLoop::Xcrun.new
end
# @!visibility private
@@ -609,10 +591,80 @@
end
udid
end
# @!visibility private
+ def simulator_required_child_processes
+ @simulator_required_child_processes ||= begin
+ required = ["backboardd", "installd", "SimulatorBridge", "SpringBoard"]
+ if xcode.version_gte_8? && version.major > 8
+ required << "medialibraryd"
+ end
+
+ if simulator_is_ipad? && version.major == 9
+ required << "com.apple.audio.SystemSoundServer-iOS-Simulator"
+ end
+
+ required
+ end
+ end
+
+ # @!visibility private
+ def simulator_launchd_sim_pid
+ waiter = RunLoop::ProcessWaiter.new("launchd_sim")
+ waiter.wait_for_any
+
+ return nil if !waiter.running_process?
+
+ pid = nil
+
+ waiter.pids.each do |launchd_sim_pid|
+ cmd = ["ps", "x", "-o", "pid,command", launchd_sim_pid.to_s]
+ hash = run_shell_command(cmd)
+ out = hash[:out]
+ process_line = out.split($-0)[1]
+ if !process_line || process_line == ""
+ false
+ else
+ pid = process_line.split(" ").first.strip
+ if process_line[/#{udid}/] == nil
+ RunLoop.log_debug("Terminating launchd_sim process with pid #{pid}")
+ RunLoop::ProcessTerminator.new(pid, "KILL", "launchd_sim").kill_process
+ pid = nil
+ end
+ end
+ end
+ pid
+ end
+
+ # @!visibility private
+ def process_parent_is_launchd_sim?(pid)
+ launchd_sim_pid = simulator_launchd_sim_pid
+ return false if !launchd_sim_pid
+
+ cmd = ["ps", "x", "-o", "ppid=", "-p", pid.to_s]
+ hash = run_shell_command(cmd)
+
+ out = hash[:out]
+ if out.nil? || out == ""
+ false
+ else
+ ppid = out.strip
+ ppid == launchd_sim_pid.to_s
+ end
+ end
+
+ # @!visibility private
+ def simulator_process_running?(process_name)
+ waiter = RunLoop::ProcessWaiter.new(process_name)
+ waiter.pids.any? do |pid|
+ process_parent_is_launchd_sim?(pid)
+ #process_parent_is_current_xcode?(pid)
+ end
+ end
+
+ # @!visibility private
def simulator_data_directory_sha
path = File.join(simulator_root_dir, 'data')
begin
# Typically, this returns in < 0.3 seconds.
Timeout.timeout(10, TimeoutError) do
@@ -685,9 +737,29 @@
https://github.com/calabash/calabash-ios/wiki/Testing-on-Physical-Devices
]
end
true
+ end
+
+ # @!visibility private
+ def simulator_running_app_pids
+ simulator_running_user_app_pids +
+ simulator_running_system_app_pids
+ end
+
+ # @!visibility private
+ def simulator_running_user_app_pids
+ path = File.join(udid, "data", "Containers", "Bundle")
+ RunLoop::ProcessWaiter.pgrep_f(path)
+ end
+
+ # @!visibility private
+ def simulator_running_system_app_pids
+ base_dir = xcode.developer_dir
+ sim_apps_dir = "Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/Applications"
+ path = File.expand_path(File.join(base_dir, sim_apps_dir))
+ RunLoop::ProcessWaiter.pgrep_f(path)
end
end
end