lib/run_loop/device.rb in run_loop-2.1.1 vs lib/run_loop/device.rb in run_loop-2.1.2
- old
+ new
@@ -1,8 +1,9 @@
module RunLoop
class Device
+ require 'securerandom'
include RunLoop::Regex
# Starting in Xcode 7, iOS 9 simulators have a new "booting" state.
#
# The simulator must completely boot before run-loop tries to do things
@@ -131,11 +132,11 @@
# Please don't call this method. It is for internal use only. The behavior
# may change at any time! You have been warned.
#
# @param [Hash] options The launch options passed to RunLoop::Core
# @param [RunLoop::Xcode] xcode An Xcode instance
- # @param [RunLoop::Simctl] simctl A SimControl instance
+ # @param [RunLoop::Simctl] simctl A Simctl instance
# @param [RunLoop::Instruments] instruments An Instruments instance
#
# @raise [ArgumentError] If "device" is detected as the device target and
# there is no matching device.
# @raise [ArgumentError] If DEVICE_TARGET or options specify an identifier
@@ -317,133 +318,114 @@
File.join(simulator_root_dir, "data/Library/Preferences/.GlobalPreferences.plist")
end.call
end
# @!visibility private
- # Is this the first launch of this Simulator?
#
- # TODO Needs unit and integration tests.
- def simulator_first_launch?
- megabytes = simulator_data_dir_size
-
- if version >= RunLoop::Version.new('9.0')
- megabytes < 20
- elsif version >= RunLoop::Version.new('8.0')
- megabytes < 12
- else
- megabytes < 8
- end
- end
-
- # @!visibility private
- # The size of the simulator data/ directory.
- #
- # TODO needs unit tests.
- def simulator_data_dir_size
- path = File.join(simulator_root_dir, 'data')
- RunLoop::Directory.size(path, :mb)
- 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 seconds.
+ # 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 _and_ it is the first launch of
- # the simulator after a reset or a new simulator install, a fourth condition
- # is added:
+ # When the simulator version is >= iOS 9, two more conditions are added to
+ # get past the iOS 9+ boot screen.
#
- # 4. The first three conditions must be met a second time.
+ # 4. Wait for com.apple.audio.SystemSoundServer-iOS-Simulator process to
+ # start.
+ # 5. 1 and 2 must hold for 1.5 seconds.
#
- # and the quiet time is increased to 2.0.
+ # 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.
def simulator_wait_for_stable_state
- require 'securerandom'
# How long to wait between stability checks.
+ # Shorter than this gives false positives.
delay = 0.5
- first_launch = false
+ # How many times to wait for stable state.
+ max_stable_count = 3
- # At launch there is a brief moment when the SHA and
- # the log file are are stable. Then a bunch of activity
- # occurs. This is the quiet time.
+ # 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.
#
- # Starting in iOS 9, simulators display at _booting_ screen
- # at first launch. At first launch, these simulators need
- # a much longer quiet time.
- if version >= RunLoop::Version.new('9.0')
- first_launch = simulator_data_dir_size < 20
- quiet_time = 2.0
- else
- quiet_time = 1.0
+ # 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
- now = Time.now
+ # iOS 9 simulators have an additional boot screen.
+ is_gte_ios9 = version >= RunLoop::Version.new('9.0')
+
+ # iOS 9 iPad simulators need additional time to stabilize.
+ is_ipad = simulator_is_ipad?
+
timeout = SIM_STABLE_STATE_OPTIONS[:timeout]
+ now = Time.now
poll_until = now + timeout
- quiet = now + quiet_time
+ RunLoop.log_debug("Waiting for simulator to stabilize with timeout: #{timeout} seconds")
+
+ current_dir_sha = simulator_data_directory_sha
+ current_log_sha = simulator_log_file_sha
is_stable = false
+ waited_for_boot = false
+ waited_for_ipad = false
+ stable_count = 0
- path = File.join(simulator_root_dir, 'data')
- current_sha = nil
- sha_fn = lambda do |data_dir|
- begin
- # Typically, this returns in < 0.3 seconds.
- Timeout.timeout(10, TimeoutError) do
- # Errors are ignorable and users are confused by the messages.
- options = { :handle_errors_by => :ignoring }
- RunLoop::Directory.directory_digest(data_dir, options)
- end
- rescue => _
- SecureRandom.uuid
- end
- end
-
- RunLoop.log_debug("Waiting for simulator to stabilize with timeout: #{timeout}")
- if first_launch
- RunLoop.log_debug("Detected the first launch of an iOS >= 9.0 Simulator")
- end
-
- current_line = nil
-
while Time.now < poll_until do
- latest_sha = sha_fn.call(path)
- latest_line = last_line_from_simulator_log_file
+ latest_dir_sha = simulator_data_directory_sha
+ latest_log_sha = simulator_log_file_sha
- is_stable = current_sha == latest_sha && current_line == latest_line
+ is_stable = [current_dir_sha == latest_dir_sha,
+ current_log_sha == latest_log_sha].all?
if is_stable
- if Time.now > quiet
- if first_launch
- RunLoop.log_debug('First launch detected - allowing additional time to stabilize')
- first_launch = false
- sleep 1.2
- quiet = Time.now + quiet_time
+ 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
- else
- quiet = Time.now + quiet_time
end
end
- current_sha = latest_sha
- current_line = latest_line
- sleep delay
+ current_dir_sha = latest_dir_sha
+ current_log_sha = latest_log_sha
+ sleep(delay)
end
if is_stable
elapsed = Time.now - now
- stabilized = elapsed - quiet_time
- RunLoop.log_debug("Simulator stable after #{stabilized} seconds")
RunLoop.log_debug("Waited a total of #{elapsed} seconds for simulator to stabilize")
else
- RunLoop.log_debug("Timed out: simulator not stable after #{timeout} seconds")
+ RunLoop.log_debug("Timed out after #{timeout} seconds waiting for simulator to stabilize")
end
end
# @!visibility private
#
@@ -529,37 +511,10 @@
end
private
# @!visibility private
- # TODO write a unit test.
- def last_line_from_simulator_log_file
- file = simulator_log_file_path
-
- return nil if !File.exist?(file)
-
- debug = RunLoop::Environment.debug?
-
- begin
- io = File.open(file, 'r')
- io.seek(-100, IO::SEEK_END)
-
- line = io.readline
- rescue StandardError => e
- RunLoop.log_error("Caught #{e} while reading simulator log file") if debug
- ensure
- io.close if io && !io.closed?
- end
-
- if line
- line.chomp
- else
- line
- end
- end
-
- # @!visibility private
def xcrun
RunLoop::Xcrun.new
end
# @!visibility private
@@ -646,9 +601,53 @@
RunLoop.log_debug("Running `udidetect` raised: #{e}")
ensure
`killall udidetect &> /dev/null`
end
udid
+ 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
+ # Errors are ignorable and users are confused by the messages.
+ options = { :handle_errors_by => :ignoring }
+ RunLoop::Directory.directory_digest(path, options)
+ end
+ rescue => _
+ SecureRandom.uuid
+ end
+ end
+
+ # @!visibility private
+ def simulator_log_file_sha
+ file = simulator_log_file_path
+
+ return nil if !File.exist?(file)
+
+ sha = OpenSSL::Digest::SHA256.new
+
+ begin
+ sha << File.read(file)
+ rescue => _
+ sha = SecureRandom.uuid
+ end
+
+ sha
+ end
+
+ # @!visibility private
+ # Value of <UDID>/.device.plist 'deviceType' key.
+ def simulator_device_type
+ plist = File.join(simulator_device_plist)
+ pbuddy.plist_read("deviceType", plist)
+ end
+
+ # @!visibility private
+ def simulator_is_ipad?
+ simulator_device_type[/iPad/, 0]
end
# @!visibility private
def self.ensure_physical_device_connected(identifier, options)
if identifier.nil?