Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

All simctl commands are performed through Simctl instance #507

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
011fa80
Shell: add .run_shell_command class method
jmoody Aug 2, 2016
22e3344
Simctl: new instances ensure valid CoreSim service is loaded
jmoody Aug 2, 2016
b651194
Style: prefer double quotes over single quotes
jmoody Aug 2, 2016
f1477b7
Simctl: implement shutdown methods
jmoody Aug 2, 2016
b976cb2
Simctl: implement #erase simulator
jmoody Aug 2, 2016
ec3109d
CoreSim: implement erase with Simctl#erase
jmoody Aug 2, 2016
14134f0
Simctl: implement launch app
jmoody Aug 2, 2016
c5da509
CoreSim: app launch is done with Simctl instance
jmoody Aug 3, 2016
12b2d5c
Simct: implement uninstall app
jmoody Aug 3, 2016
97680b2
CoreSim: calls uninstall through simctl
jmoody Aug 3, 2016
c4e66be
Simctl: implement install app
jmoody Aug 3, 2016
43142ed
CoreSim: implement install with Simctl instance
jmoody Aug 3, 2016
ef6a16a
Simctl: can return simulator state as a string
jmoody Aug 3, 2016
f78857a
Device: updates simulator state with Simctl#simulator_state_as_string
jmoody Aug 3, 2016
2e34632
SimControl: add guards for incompatible CoreSimulator service
jmoody Aug 3, 2016
5a99b59
Spec: remove ununsed simctl methods from Resources
jmoody Aug 3, 2016
0ec02e4
Simctl: combine simctl life cycle methods into one example
jmoody Aug 3, 2016
d30ad40
Simctl: execute => shell_out_with_xcrun
jmoody Aug 3, 2016
0c9a80a
Adds wait options for core simulator spec to pass
jescriba Aug 4, 2016
d4f03ad
Simctl: handle 'Shutdown' state if changes between check and call
jmoody Aug 4, 2016
6d3ec90
Spec: add sim plist check to core sim #erase specs
jmoody Aug 4, 2016
66b5854
Spec: remove example for deprecated Core.simulator_target? method
jmoody Aug 4, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .irbrc
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,15 @@ def create_simulator(n, options={})
runtime = merged_options[:runtime]

n.times do
system('xcrun', 'simctl', 'create', name, type, runtime)
system('xcrun', "simctl", 'create', name, type, runtime)
end
end

def delete_simulator(name)
simctl.simulators.each do |simulator|
if simulator.name == name
puts "Deleting #{simulator}"
system('xcrun', 'simctl', 'delete', simulator.udid)
system('xcrun', "simctl", 'delete', simulator.udid)
end
end
true
Expand Down
4 changes: 2 additions & 2 deletions lib/run_loop/cli/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def version
desc 'instruments', "Interact with Xcode's command-line instruments"
subcommand 'instruments', RunLoop::CLI::Instruments

desc 'simctl', "Interact with Xcode's command-line simctl"
subcommand 'simctl', RunLoop::CLI::Simctl
desc "simctl", "Interact with Xcode's command-line simctl"
subcommand "simctl", RunLoop::CLI::Simctl

desc "locale", "Tools for interacting with locales"
subcommand "locale", RunLoop::CLI::Locale
Expand Down
162 changes: 56 additions & 106 deletions lib/run_loop/core_simulator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class RunLoop::CoreSimulator
:install_app_timeout => RunLoop::Environment.ci? ? 120 : 30,
:uninstall_app_timeout => RunLoop::Environment.ci? ? 120 : 30,
:launch_app_timeout => RunLoop::Environment.ci? ? 120 : 30,
:wait_for_state_timeout => RunLoop::Environment.ci? ? 120 : 30
:wait_for_state_timeout => RunLoop::Environment.ci? ? 120 : 30,
:app_launch_retries => RunLoop::Environment.ci? ? 5 : 3
}

# @!visibility private
Expand Down Expand Up @@ -178,9 +179,8 @@ def self.wait_for_simulator_state(simulator, target_state)
#
# @param [RunLoop::Device] simulator The simulator to erase
# @param [Hash] options Control the behavior of the method.
# @option options [Numeric] :timeout (180) How long tow wait for simctl to
# shutdown and erase the simulator. The timeout is apply separately to
# each command.
# @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
Expand All @@ -191,61 +191,13 @@ def self.erase(simulator, options={})
"#{simulator} is a physical device. This method is only for Simulators"
end

default_options = {
:timeout => 60*3
}

merged_options = default_options.merge(options)

self.quit_simulator

xcrun = merged_options[:xcrun] || RunLoop::Xcrun.new
timeout = merged_options[:timeout]
xcrun_opts = {
:log_cmd => true,
:timeout => timeout
}

if simulator.update_simulator_state != "Shutdown"
args = ["simctl", "shutdown", simulator.udid]
xcrun.run_command_in_context(args, xcrun_opts)
begin
self.wait_for_simulator_state(simulator, "Shutdown")
rescue RuntimeError => _
raise RuntimeError, %Q{
Could not erase simulator because it could not be Shutdown.
merged_options = DEFAULT_OPTIONS.merge(options)
simctl = merged_options[:simctl] || RunLoop::Simctl.new
timeout = merged_options[:timeout] || merged_options[:wait_for_state_timeout]

This usually means your CoreSimulator processes need to be restarted.

You can restart the CoreSimulator processes with this command:

$ bundle exec run-loop simctl manage-processes

}

end
end

args = ["simctl", "erase", simulator.udid]
hash = xcrun.run_command_in_context(args, xcrun_opts)

if hash[:exit_status] != 0
raise RuntimeError, %Q{
Could not erase simulator because simctl returned this error:

#{hash[:out]}

This usually means your CoreSimulator processes need to be restarted.

You can restart the CoreSimulator processes with this command:

$ bundle exec run-loop simctl manage-processes

}

end

hash
simctl.erase(simulator,
timeout,
WAIT_FOR_SIMULATOR_STATE_INTERVAL)
end

# @!visibility private
Expand Down Expand Up @@ -351,6 +303,11 @@ def xcrun
@xcrun ||= RunLoop::Xcrun.new
end

# @!visibility private
def simctl
@simctl ||= RunLoop::Simctl.new
end

# Launch the simulator indicated by device.
def launch_simulator

Expand Down Expand Up @@ -404,29 +361,19 @@ def launch
# relaunch it.
launch_simulator

tries = RunLoop::Environment.ci? ? 5 : 3
last_error = nil
tries = app_launch_retries

RunLoop.log_debug("Trying #{tries} times to launch #{app.bundle_identifier} on #{device}")

tries.times do |try|
# Terminates CoreSimulatorService on failures.
hash = attempt_to_launch_app_with_simctl

exit_status = hash[:exit_status]
if exit_status != 0
# Last argument is how long to sleep after an error.
last_error = handle_failed_app_launch(hash, try, tries, 0.5)
else
last_error = nil
break
end
end
last_error = try_to_launch_app_n_times(tries)

if last_error
raise RuntimeError, %Q[Could not launch #{app.bundle_identifier} on #{device}
raise RuntimeError, %Q[
Could not launch #{app.bundle_identifier} on #{device} after trying #{tries} times:

#{last_error}
#{last_error}:

#{last_error.message}

]
end
Expand Down Expand Up @@ -487,10 +434,8 @@ def uninstall_app_and_sandbox

launch_simulator

args = ['simctl', 'uninstall', device.udid, app.bundle_identifier]

timeout = DEFAULT_OPTIONS[:uninstall_app_timeout]
xcrun.run_command_in_context(args, log_cmd: true, timeout: timeout)
simctl.uninstall(device, app, timeout)

device.simulator_wait_for_stable_state
true
Expand Down Expand Up @@ -610,52 +555,57 @@ def running_simulator_pid
def install_app_with_simctl
launch_simulator

args = ['simctl', 'install', device.udid, app.path]
timeout = DEFAULT_OPTIONS[:install_app_timeout]
xcrun.run_command_in_context(args, log_cmd: true, timeout: timeout)
simctl.install(device, app, timeout)

device.simulator_wait_for_stable_state
installed_app_bundle_dir
end

# @!visibility private
def launch_app_with_simctl
args = ['simctl', 'launch', device.udid, app.bundle_identifier]
timeout = DEFAULT_OPTIONS[:launch_app_timeout]
xcrun.run_command_in_context(args, log_cmd: true, timeout: timeout)
simctl.launch(device, app, timeout)
end

# @!visibility private
def handle_failed_app_launch(hash, try, tries, wait_time)
out = hash[:out]
RunLoop.log_debug("Failed to launch app on try #{try + 1} of #{tries}.")
out.split($-0).each do |line|
RunLoop.log_debug(" #{line}")
#
# Returns nil if launch_app_with_simctl succeeds and the error if it fails.
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
# If we timed out on the launch, the CoreSimulator processes are quit
# (see above). If at all possible, we want to avoid terminating
# CoreSimulatorService, because it takes a long time to launch.
sleep(wait_time) if wait_time > 0
end

out
# @!visibility private
def app_launch_retries
DEFAULT_OPTIONS[:app_launch_retries]
end

# @!visibility private
def attempt_to_launch_app_with_simctl
begin
hash = launch_app_with_simctl
rescue RunLoop::Xcrun::TimeoutError => e
hash = {
:exit_status => 1,
:out => e.message
}
# Simulator is probably in a bad state. Terminates the
# CoreSimulatorService. Restarting this service is expensive!
RunLoop::CoreSimulator.terminate_core_simulator_processes
Kernel.sleep(0.5)
launch_simulator
#
# Returns nil if launch_app_with_simctl succeeds and the error if it fails.
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
hash

last_error
end

# Required for support of iOS 7 CoreSimulators. Can be removed when
Expand Down
44 changes: 5 additions & 39 deletions lib/run_loop/device.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ class Device
attr_reader :simulator_accessibility_plist_path
attr_reader :simulator_preferences_plist_path
attr_reader :simulator_log_file_path
attr_reader :pbuddy

# Create a new device.
#
Expand Down Expand Up @@ -271,7 +270,7 @@ def update_simulator_state
raise RuntimeError, 'This method is available only for simulators'
end

@state = fetch_simulator_state
@state = simctl.simulator_state_as_string(self)
end

# @!visibility private
Expand Down Expand Up @@ -528,6 +527,8 @@ def simulator_set_language(lang_code)

private

attr_reader :pbuddy, :simctl, :xcrun

# @!visibility private
def xcrun
RunLoop::Xcrun.new
Expand All @@ -539,43 +540,8 @@ def pbuddy
end

# @!visibility private
def detect_state_from_line(line)

if line[/unavailable/, 0]
RunLoop.log_debug("Simulator state is unavailable: #{line}")
return 'Unavailable'
end

state = line[/(Booted|Shutdown|Shutting Down)/,0]

if state.nil?
RunLoop.log_debug("Simulator state is unknown: #{line}")
'Unknown'
else
state
end
end

# @!visibility private
def fetch_simulator_state
if physical_device?
raise RuntimeError, 'This method is available only for simulators'
end

args = ['simctl', 'list', 'devices']
hash = xcrun.run_command_in_context(args)
out = hash[:out]

matched_line = out.split("\n").find do |line|
line.include?(udid)
end

if matched_line.nil?
raise RuntimeError,
"Expected a simulator with udid '#{udid}', but found none"
end

detect_state_from_line(matched_line)
def simctl
@simctl ||= RunLoop::Simctl.new
end

# @!visibility private
Expand Down
10 changes: 10 additions & 0 deletions lib/run_loop/shell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ class Error < RuntimeError; end
# Raised when shell command times out.
class TimeoutError < RuntimeError; end

def self.run_shell_command(args, options={})
shell = Class.new do
include RunLoop::Shell
def to_s; "#<Anonymous Shell>"; end
def inspect; to_s; end
end.new

shell.run_shell_command(args, options)
end

def run_shell_command(args, options={})

merged_options = DEFAULT_OPTIONS.merge(options)
Expand Down
8 changes: 6 additions & 2 deletions lib/run_loop/sim_control.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,9 @@ def simctl_list(what)
# base sdk version.
# @see #simctl_list
def simctl_list_devices
args = ['simctl', 'list', 'devices']
# Ensure correct CoreSimulator service is installed.
RunLoop::Simctl.new
args = ["simctl", 'list', 'devices']
hash = xcrun.run_command_in_context(args)

current_sdk = nil
Expand Down Expand Up @@ -1219,7 +1221,9 @@ def simctl_list_devices
#
# @see #simctl_list
def simctl_list_runtimes
args = ['simctl', 'list', 'runtimes']
# Ensure correct CoreSimulator service is installed.
RunLoop::Simctl.new
args = ["simctl", 'list', 'runtimes']
hash = xcrun.run_command_in_context(args)

# Ex.
Expand Down
Loading