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

ProcessWaiter: improve process detection #581

Merged
merged 4 commits into from
Dec 31, 2016
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
36 changes: 14 additions & 22 deletions lib/run_loop/core_simulator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ class RunLoop::CoreSimulator
# Not yet.
# "com.apple.CoreSimulator.SimVerificationService",

'SimulatorBridge',
'configd_sim',
'CoreSimulatorBridge',
"SimulatorBridge",
"configd_sim",
"CoreSimulatorBridge",

# Xcode 7
'ids_simd'
"ids_simd"
]

# @!visibility private
Expand All @@ -84,39 +84,31 @@ class RunLoop::CoreSimulator
SIMULATOR_QUIT_PROCESSES =
[
# Xcode 7 start throwing this error.
['splashboardd', false],

# Xcode < 5.1
['iPhone Simulator.app', true],

# 7.0 < Xcode <= 6.0
['iOS Simulator.app', true],
["splashboardd", false],

# Xcode >= 7.0
['Simulator.app', true],
["Simulator", true],

# Multiple launchd_sim processes have been causing problems. This
# is a first pass at investigating what it would mean to kill the
# launchd_sim process.
['launchd_sim', false],
# Multiple launchd_sim processes have been causing problems.
# In theory, killing the parent launchd_sim process should kill
# child processes like assetsd, but in practice this does not
# always happen.
["launchd_sim", false],

# Required for DeviceAgent termination; the simulator hangs otherwise.
["xpcproxy", false],

# Causes crash reports on Xcode < 7.0
["apsd", true],

# assetsd instances clobber each other and are not properly
# killed when quiting the simulator.
['assetsd', false],
["assetsd", false],

# iproxy is started by UITest.
['iproxy', false],
["iproxy", false],

# Started by Xamarin Studio, this is the parent process of the
# processes launched by Xamarin's interaction with
# CoreSimulatorBridge.
['csproxy', false],
["csproxy", false],
]

# @!visibility private
Expand Down
23 changes: 19 additions & 4 deletions lib/run_loop/process_waiter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ module RunLoop
# A class for waiting on processes.
class ProcessWaiter

require "run_loop/shell"

include RunLoop::Shell

attr_reader :process_name

def initialize(process_name, options={})
Expand All @@ -13,9 +17,21 @@ def initialize(process_name, options={})
# Collect a list of Integer pids.
# @return [Array<Integer>] An array of integer pids for the `process_name`
def pids
process_info = `ps x -o pid,comm | grep -v grep | grep '#{process_name}'`
process_array = process_info.split("\n")
process_array.map { |process| process.split(' ').first.strip.to_i }
cmd = ["pgrep", "-x", process_name]
hash = run_shell_command(cmd)
out = hash[:out]

if out.nil? || out == ""
[]
else
out.split($-0).map do |pid|
if pid.nil? || pid == ""
nil
else
pid.to_i
end
end.compact
end
end

# Is the `process_name` a running?
Expand Down Expand Up @@ -58,7 +74,6 @@ def wait_for_n(n)
there_are_n
end


# Wait for `process_name` to start.
def wait_for_any
return true if running_process?
Expand Down
126 changes: 64 additions & 62 deletions spec/integration/process_waiter_spec.rb
Original file line number Diff line number Diff line change
@@ -1,108 +1,110 @@
describe RunLoop::ProcessWaiter do

context '#wait_for_any' do
describe 'returns true' do
it 'fast if process is running' do
res = RunLoop::ProcessWaiter.new('ruby').wait_for_any
expect(res).to be == true
end

it 'after waiting for process' do
waiter = RunLoop::ProcessWaiter.new('ruby', {:timeout => 2})
vals = [false, false, false, true]
expect(waiter).to receive(:running_process?).exactly(4).times.and_return(*vals)
expect(waiter.wait_for_any).to be == true
end
end

it 'returns false' do
waiter = RunLoop::ProcessWaiter.new('ruby', {:timeout => 1})
context "#wait_for_any" do
it "returns true if process is running" do
res = RunLoop::ProcessWaiter.new("Finder").wait_for_any
expect(res).to be == true
end

it "returns true after waiting for process to start" do
waiter = RunLoop::ProcessWaiter.new("Finder", {:timeout => 2})
vals = [false, false, false, true]
expect(waiter).to receive(:running_process?).exactly(4).times.and_return(*vals)
expect(waiter.wait_for_any).to be == true
end

it "returns false after waiting for process to start" do
waiter = RunLoop::ProcessWaiter.new("Finder", {:timeout => 0.5})
expect(waiter).to receive(:running_process?).at_least(:twice).and_return(false)
expect(waiter.wait_for_any).to be == false
end

it 'raises an error' do
options = {:timeout => 1, :raise_on_timeout => true }
waiter = RunLoop::ProcessWaiter.new('ruby', options)
it "raises an error when :raise_on_timeout is true" do
options = {:timeout => 0.5, :raise_on_timeout => true }
waiter = RunLoop::ProcessWaiter.new("Finder", options)
expect(waiter).to receive(:running_process?).at_least(:twice).and_return(false)
expect { waiter.wait_for_any }.to raise_error RuntimeError
end

it 'can log how long it waited' do
it "can log how long it waited" do
expect(RunLoop::Environment).to receive(:debug?).at_least(:once).and_return(true)
waiter = RunLoop::ProcessWaiter.new('ruby', {:timeout => 2})
waiter = RunLoop::ProcessWaiter.new("Finder", {:timeout => 2})
vals = [false, false, false, true]
expect(waiter).to receive(:running_process?).exactly(4).times.and_return(*vals)
expect(waiter.wait_for_any).to be == true
end
end

context '#wait_for_none' do
describe 'returns true' do
it 'fast if no process is running' do
res = RunLoop::ProcessWaiter.new('no-such-process').wait_for_none
expect(res).to be == true
end
context "#wait_for_none" do
it "returns true if no process is running" do
waiter = RunLoop::ProcessWaiter.new("no-such-process", {:timeout => 1})
expect(waiter.wait_for_none).to be == true
end

it 'after waiting for process to expire' do
waiter = RunLoop::ProcessWaiter.new('ruby', {:timeout => 1})
vals = [true, true, true, false]
expect(waiter).to receive(:running_process?).exactly(4).times.and_return(*vals)
expect(waiter.wait_for_none).to be == true
end
it "returns true after waiting for process to expire" do
waiter = RunLoop::ProcessWaiter.new("Finder", {:timeout => 1})
vals = [true, true, true, false]
expect(waiter).to receive(:running_process?).exactly(4).times.and_return(*vals)
expect(waiter.wait_for_none).to be == true
end

it 'returns false' do
waiter = RunLoop::ProcessWaiter.new('ruby', {:timeout => 1})
it "returns false if process is still running after :timeout" do
waiter = RunLoop::ProcessWaiter.new("Finder", {:timeout => 0.5})
expect(waiter.wait_for_none).to be == false
end

it 'raises an error' do
options = {:timeout => 1, :raise_on_timeout => true }
waiter = RunLoop::ProcessWaiter.new('ruby', options)
it "raises an error if :raise_on_timeout is true" do
options = {:timeout => 0.5, :raise_on_timeout => true }
waiter = RunLoop::ProcessWaiter.new("Finder", options)
expect { waiter.wait_for_none }.to raise_error RuntimeError
end

it 'can log how long it waited' do
it "logs how long it waited" do
expect(RunLoop::Environment).to receive(:debug?).at_least(:once).and_return(true)
waiter = RunLoop::ProcessWaiter.new('ruby', {:timeout => 2})
waiter = RunLoop::ProcessWaiter.new("Finder", {:timeout => 0.5})
expect(waiter.wait_for_none).to be == false
end
end

context '#wait_for_n' do
describe 'raises an error when' do
it 'n is not an Integer' do
expect { RunLoop::ProcessWaiter.new('ruby').wait_for_n 2.0 }.to raise_error ArgumentError
end
context "#wait_for_n" do
it "raises ArgumentError when n is not an Integer" do
expect do
RunLoop::ProcessWaiter.new("ruby").wait_for_n 2.0
end.to raise_error ArgumentError
end

it 'n is < 0' do
expect { RunLoop::ProcessWaiter.new('ruby').wait_for_n 0 }.to raise_error ArgumentError
expect { RunLoop::ProcessWaiter.new('ruby').wait_for_n -1 }.to raise_error ArgumentError
end
it "raises ArgumentError when n is <= 0" do
expect do
RunLoop::ProcessWaiter.new('ruby').wait_for_n 0
end.to raise_error ArgumentError
expect do
RunLoop::ProcessWaiter.new('ruby').wait_for_n -1
end.to raise_error ArgumentError
end

it 'cannot find n processes and options say :raise' do
options = {:timeout => 1, :raise_on_timeout => true }
waiter = RunLoop::ProcessWaiter.new('ruby', options)
expect { waiter.wait_for_n(1000) }.to raise_error RuntimeError
end
it "raises RuntimeError when n processes are not running and :raise_on_timeout is true" do
options = {:timeout => 0.5, :raise_on_timeout => true }
waiter = RunLoop::ProcessWaiter.new("Finder", options)
expect do
waiter.wait_for_n(1000)
end.to raise_error RuntimeError
end

it 'return true when there are N processes' do
waiter = RunLoop::ProcessWaiter.new('ruby', {:timeout => 1})
it "returns true when there are N processes" do
waiter = RunLoop::ProcessWaiter.new("Finder", {:timeout => 1})
vals = [[], [0], [0, 1], [0, 1, 2], [0, 1, 2, 3]]
expect(waiter).to receive(:pids).exactly(4).times.and_return(*vals)
expect(waiter.wait_for_n(4)).to be == true
end

it 'returns false' do
waiter = RunLoop::ProcessWaiter.new('ruby', {:timeout => 1})
expect(waiter.wait_for_none).to be == false
it "returns false when N processes do not appear" do
waiter = RunLoop::ProcessWaiter.new("Finder", {:timeout => 0.5})
expect(waiter.wait_for_n(2)).to be == false
end

it 'can log how long it waited' do
it "can log how long it waited" do
expect(RunLoop::Environment).to receive(:debug?).at_least(:once).and_return(true)
waiter = RunLoop::ProcessWaiter.new('ruby', {:timeout => 1.0, :interval => 0.5})
waiter = RunLoop::ProcessWaiter.new("Finder", {:timeout => 1.0, :interval => 0.5})
vals = [[], [0], [0, 1]]
expect(waiter).to receive(:pids).exactly(3).times.and_return(*vals)
expect(waiter.wait_for_n(3)).to be == false
Expand Down