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

CoreSimulator can erase a simulator #339

Merged
merged 2 commits into from
Nov 25, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
128 changes: 105 additions & 23 deletions lib/run_loop/core_simulator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,110 @@ def self.quit_simulator
self.simulator_pid = nil
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
def self.wait_for_simulator_state(simulator, target_state)
now = Time.now
timeout = WAIT_FOR_DEVICE_STATE_OPTS[:timeout]
poll_until = now + timeout
delay = WAIT_FOR_DEVICE_STATE_OPTS[:interval]
in_state = false
while Time.now < poll_until
in_state = simulator.update_simulator_state == target_state
break if in_state
sleep delay
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

# @!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] :timout (180) How long tow wait for simctl to
# shutdown and erase the simulator. The timeout is apply separately to
# each command.
#
# @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
def self.erase(simulator, options={})
if simulator.physical_device?
raise ArgumentError,
"#{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.exec(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.

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.exec(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
end

# @!visibility private
def self.simulator_pid
@@simulator_pid
Expand Down Expand Up @@ -287,7 +391,7 @@ def app_is_installed?
def reset_app_sandbox
return true if !app_is_installed?

wait_for_device_state('Shutdown')
RunLoop::CoreSimulator.wait_for_simulator_state(device, "Shutdown")

reset_app_sandbox_internal
end
Expand Down Expand Up @@ -434,28 +538,6 @@ def install_app_with_simctl
installed_app_bundle_dir
end

# @!visibility private
def wait_for_device_state(target_state)
now = Time.now
timeout = WAIT_FOR_DEVICE_STATE_OPTS[:timeout]
poll_until = now + timeout
delay = WAIT_FOR_DEVICE_STATE_OPTS[:interval]
in_state = false
while Time.now < poll_until
in_state = device.update_simulator_state == target_state
break if in_state
sleep delay
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 '#{device.state}' after waiting."
end
in_state
end

# Required for support of iOS 7 CoreSimulators. Can be removed when
# Xcode support is dropped.
def sdk_gte_8?
Expand Down
12 changes: 12 additions & 0 deletions spec/integration/core_simulator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@
sleep 2
end

describe ".erase" do
it "can quit, shutdown, and erase a simulator" do
core_sim.launch_simulator

expect(RunLoop::CoreSimulator.erase(simulator)).to be_truthy
end

it "can shutdown and erase a simulator" do
expect(RunLoop::CoreSimulator.erase(simulator)).to be_truthy
end

end
describe '#launch_simulator' do
it 'can launch the simulator' do
expect(core_sim.launch_simulator).to be_truthy
Expand Down
98 changes: 94 additions & 4 deletions spec/lib/core_simulator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,95 @@
RunLoop::CoreSimulator.terminate_core_simulator_processes
end

describe ".erase" do
let(:xcrun) { RunLoop::Xcrun.new }

let(:options) do
{
:xcrun => xcrun,
:timeout => 100
}
end

let(:device) { RunLoop::Device.new("name", "8.1", "udid") }

let(:erase_args) do
[
["simctl", "erase", "udid"],
{
:log_cmd => true,
:timeout => 100
}
]
end

let(:shutdown_args) do
[
["simctl", "shutdown", "udid"],
{
:log_cmd => true,
:timeout => 100
}
]
end

let(:erase_hash) do
{
:out => "",
:exit_status => 0
}
end

before do
allow(RunLoop::CoreSimulator).to receive(:quit_simulator).and_return true
end

it "raises an error if simulator argument is a physical device" do
# Device#to_s calls physical_device?
expect(device).to receive(:physical_device?).twice.and_return true

expect do
RunLoop::CoreSimulator.erase(device, options)
end.to raise_error(ArgumentError, /is a physical device/)
end

it "calls erase if sim is shutdown" do
expect(device).to receive(:update_simulator_state).and_return "Shutdown"
expect(xcrun).to receive(:exec).with(*erase_args).and_return(erase_hash)

expect(RunLoop::CoreSimulator.erase(device, options)).to be_truthy
end

it "waits for sim to shutdown" do
expect(device).to receive(:update_simulator_state).once.and_return("Unknown")
expect(xcrun).to receive(:exec).with(*shutdown_args).and_return true
expect(RunLoop::CoreSimulator).to receive(:wait_for_simulator_state).and_return true
expect(xcrun).to receive(:exec).with(*erase_args).and_return(erase_hash)

expect(RunLoop::CoreSimulator.erase(device, options)).to be_truthy
end

it "raises error if device cannot be shutdown" do
expect(device).to receive(:update_simulator_state).once.and_return("Unknown")
expect(xcrun).to receive(:exec).with(*shutdown_args).and_return true
expect(RunLoop::CoreSimulator).to receive(:wait_for_simulator_state).and_raise RuntimeError, "Not shutdown"

expect do
RunLoop::CoreSimulator.erase(device, options)
end.to raise_error RuntimeError, /Could not erase simulator because it could not be Shutdown/
end

it "raises error if device cannot be erased" do
expect(device).to receive(:update_simulator_state).and_return "Shutdown"
hash = {:exit_status => 1, :out => "Simulator domain error"}
expect(xcrun).to receive(:exec).with(*erase_args).and_return(hash)

expect do
RunLoop::CoreSimulator.erase(device, options)
end.to raise_error RuntimeError, /Simulator domain error/
end
end

describe 'instance methods' do
before do
allow(RunLoop::CoreSimulator).to receive(:terminate_core_simulator_processes).and_return true
Expand Down Expand Up @@ -573,7 +662,7 @@
end
end

describe '#wait_for_device_state' do
describe '.wait_for_simulator_state' do
it 'times out if state is never reached' do
if Resources.shared.travis_ci?
options = { :timeout => 0.2, :interval => 0.01 }
Expand All @@ -586,7 +675,7 @@
expect(device).to receive(:update_simulator_state).at_least(:once).and_return 'Undesired'

expect do
core_sim.send(:wait_for_device_state, 'Desired')
RunLoop::CoreSimulator.wait_for_simulator_state(device, 'Desired')
end.to raise_error RuntimeError, /Expected/
end

Expand All @@ -597,7 +686,7 @@
values = ['Undesired', 'Undesired', 'Desired']
expect(device).to receive(:update_simulator_state).at_least(:once).and_return(*values)

expect(core_sim.send(:wait_for_device_state, 'Desired')).to be_truthy
expect(RunLoop::CoreSimulator.wait_for_simulator_state(device, "Desired")).to be_truthy
end
end

Expand Down Expand Up @@ -627,7 +716,8 @@ def create_sandbox_dirs(sub_dir_names)

it 'calls reset_app_sandbox_internal otherwise' do
expect(core_sim).to receive(:app_is_installed?).and_return true
expect(core_sim).to receive(:wait_for_device_state).with('Shutdown').and_return true
args = [core_sim.device, "Shutdown"]
expect(RunLoop::CoreSimulator).to receive(:wait_for_simulator_state).with(*args).and_return true
expect(core_sim).to receive(:reset_app_sandbox_internal).and_return true

expect(core_sim.reset_app_sandbox).to be_truthy
Expand Down