run_loop 1.2.6 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/run_loop.rb +7 -0
- data/lib/run_loop/app.rb +67 -0
- data/lib/run_loop/core.rb +17 -128
- data/lib/run_loop/device.rb +15 -3
- data/lib/run_loop/dylib_injector.rb +125 -0
- data/lib/run_loop/environment.rb +15 -0
- data/lib/run_loop/host_cache.rb +2 -1
- data/lib/run_loop/instruments.rb +59 -65
- data/lib/run_loop/lldb.rb +55 -0
- data/lib/run_loop/plist_buddy.rb +18 -2
- data/lib/run_loop/process_terminator.rb +140 -0
- data/lib/run_loop/process_waiter.rb +85 -0
- data/lib/run_loop/sim_control.rb +4 -4
- data/lib/run_loop/simctl/bridge.rb +242 -0
- data/lib/run_loop/version.rb +1 -1
- data/lib/run_loop/xctools.rb +16 -0
- data/scripts/calabash_script_uia.js +3667 -3590
- metadata +48 -24
- data/bin/run-loop +0 -9
- data/lib/run_loop/cli.rb +0 -21
@@ -0,0 +1,15 @@
|
|
1
|
+
module RunLoop
|
2
|
+
class Environment
|
3
|
+
|
4
|
+
# Returns the user's Unix uid.
|
5
|
+
# @return [Integer] The user's Unix uid as an integer.
|
6
|
+
def self.uid
|
7
|
+
`id -u`.strip.to_i
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns true if debugging is enabled.
|
11
|
+
def self.debug?
|
12
|
+
ENV['DEBUG'] == '1'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/run_loop/host_cache.rb
CHANGED
@@ -23,7 +23,8 @@ module RunLoop
|
|
23
23
|
# The directory where the cache is stored.
|
24
24
|
# @return [String] Expanded path to the default cache directory.
|
25
25
|
def self.default_directory
|
26
|
-
|
26
|
+
uid = RunLoop::Environment.uid
|
27
|
+
File.expand_path("/tmp/com.xamarin.calabash.run-loop/host-cache/#{uid}")
|
27
28
|
end
|
28
29
|
|
29
30
|
# The default cache.
|
data/lib/run_loop/instruments.rb
CHANGED
@@ -3,8 +3,6 @@ module RunLoop
|
|
3
3
|
# A class for interacting with the instruments command-line tool
|
4
4
|
#
|
5
5
|
# @note All instruments commands are run in the context of `xcrun`.
|
6
|
-
#
|
7
|
-
# @todo Detect Instruments.app is running and pop an alert.
|
8
6
|
class Instruments
|
9
7
|
|
10
8
|
# Returns an Array of instruments process ids.
|
@@ -38,31 +36,11 @@ module RunLoop
|
|
38
36
|
# what version of Xcode is active.
|
39
37
|
def kill_instruments(xcode_tools = RunLoop::XCTools.new)
|
40
38
|
kill_signal = kill_signal xcode_tools
|
41
|
-
# It is difficult to test using a block.
|
42
39
|
instruments_pids.each do |pid|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
Process.kill(kill_signal, pid.to_i)
|
48
|
-
Process.wait(pid, Process::WNOHANG)
|
49
|
-
rescue Exception => e
|
50
|
-
if ENV['DEBUG'] == '1' or ENV['DEBUG_UNIX'] == '1'
|
51
|
-
puts "Could not kill and wait for process '#{pid.to_i}' - ignoring exception '#{e}'"
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Process.wait or `wait` here is pointless. The pid may or may not be
|
56
|
-
# a child of this Process.
|
57
|
-
begin
|
58
|
-
if ENV['DEBUG'] == '1' or ENV['DEBUG_UNIX_CALLS'] == '1'
|
59
|
-
puts "Waiting for instruments '#{pid}' to terminate"
|
60
|
-
end
|
61
|
-
wait_for_process_to_terminate(pid, {:timeout => 2.0})
|
62
|
-
rescue Exception => e
|
63
|
-
if ENV['DEBUG'] == '1' or ENV['DEBUG_UNIX_CALLS'] == '1'
|
64
|
-
puts "Ignoring #{e.message}"
|
65
|
-
end
|
40
|
+
terminator = RunLoop::ProcessTerminator.new(pid, kill_signal, 'instruments')
|
41
|
+
unless terminator.kill_process
|
42
|
+
terminator = RunLoop::ProcessTerminator.new(pid, 'KILL', 'instruments')
|
43
|
+
terminator.kill_process
|
66
44
|
end
|
67
45
|
end
|
68
46
|
end
|
@@ -80,8 +58,59 @@ module RunLoop
|
|
80
58
|
end
|
81
59
|
end
|
82
60
|
|
61
|
+
# Spawn a new instruments process in the context of `xcrun` and detach.
|
62
|
+
#
|
63
|
+
# @param [String] automation_template The template instruments will use when
|
64
|
+
# launching the application.
|
65
|
+
# @param [Hash] options The launch options.
|
66
|
+
# @param [String] log_file The file to log to.
|
67
|
+
# @return [Integer] Returns the process id of the instruments process.
|
68
|
+
# @todo Do I need to enumerate the launch options in the docs?
|
69
|
+
# @todo Should this raise errors?
|
70
|
+
# @todo Is this jruby compatible?
|
71
|
+
def spawn(automation_template, options, log_file)
|
72
|
+
splat_args = spawn_arguments(automation_template, options)
|
73
|
+
if ENV['DEBUG'] == '1'
|
74
|
+
puts "#{Time.now} xcrun #{splat_args.join(' ')} >& #{log_file}"
|
75
|
+
$stdout.flush
|
76
|
+
end
|
77
|
+
pid = Process.spawn('xcrun', *splat_args, {:out => log_file, :err => log_file})
|
78
|
+
Process.detach(pid)
|
79
|
+
pid.to_i
|
80
|
+
end
|
81
|
+
|
83
82
|
private
|
84
83
|
|
84
|
+
# @!visibility private
|
85
|
+
# Parses the run-loop options hash into an array of arguments that can be
|
86
|
+
# passed to `Process.spawn` to launch instruments.
|
87
|
+
def spawn_arguments(automation_template, options)
|
88
|
+
array = ['instruments']
|
89
|
+
array << '-w'
|
90
|
+
array << options[:udid]
|
91
|
+
|
92
|
+
trace = options[:results_dir_trace]
|
93
|
+
if trace
|
94
|
+
array << '-D'
|
95
|
+
array << trace
|
96
|
+
end
|
97
|
+
|
98
|
+
array << '-t'
|
99
|
+
array << automation_template
|
100
|
+
|
101
|
+
array << options[:bundle_dir_or_bundle_id]
|
102
|
+
|
103
|
+
{
|
104
|
+
'UIARESULTSPATH' => options[:results_dir],
|
105
|
+
'UIASCRIPT' => options[:script]
|
106
|
+
}.each do |key, value|
|
107
|
+
array << '-e'
|
108
|
+
array << key
|
109
|
+
array << value
|
110
|
+
end
|
111
|
+
array + options.fetch(:args, [])
|
112
|
+
end
|
113
|
+
|
85
114
|
# @!visibility private
|
86
115
|
#
|
87
116
|
# ```
|
@@ -96,7 +125,7 @@ module RunLoop
|
|
96
125
|
# $ ps x -o pid,command | grep -v grep | grep instruments
|
97
126
|
# 98082 /Xcode/6.0.1/Xcode.app/Contents/Developer/usr/bin/instruments -w < args >
|
98
127
|
# ```
|
99
|
-
|
128
|
+
INSTRUMENTS_FIND_PIDS_CMD = 'ps x -o pid,command | grep -v grep | grep instruments'
|
100
129
|
|
101
130
|
# @!visibility private
|
102
131
|
#
|
@@ -106,7 +135,7 @@ module RunLoop
|
|
106
135
|
# processes.
|
107
136
|
# @return [String] A ps-style list of process details. The details returned
|
108
137
|
# are controlled by the `ps_cmd`.
|
109
|
-
def ps_for_instruments(ps_cmd=
|
138
|
+
def ps_for_instruments(ps_cmd=INSTRUMENTS_FIND_PIDS_CMD)
|
110
139
|
`#{ps_cmd}`.strip
|
111
140
|
end
|
112
141
|
|
@@ -117,8 +146,7 @@ module RunLoop
|
|
117
146
|
# @return [Boolean] True if the details describe an instruments process.
|
118
147
|
def is_instruments_process?(ps_details)
|
119
148
|
return false if ps_details.nil?
|
120
|
-
|
121
|
-
ps_details[/sh -c xcrun instruments/, 0]) != nil
|
149
|
+
ps_details[/\/usr\/bin\/instruments/, 0] != nil
|
122
150
|
end
|
123
151
|
|
124
152
|
# @!visibility private
|
@@ -129,7 +157,7 @@ module RunLoop
|
|
129
157
|
# processes.
|
130
158
|
# @return [Array<Integer>] An array of integer pids for instruments
|
131
159
|
# processes. Returns an empty list if no instruments process are found.
|
132
|
-
def pids_from_ps_output(ps_cmd=
|
160
|
+
def pids_from_ps_output(ps_cmd=INSTRUMENTS_FIND_PIDS_CMD)
|
133
161
|
ps_output = ps_for_instruments(ps_cmd)
|
134
162
|
lines = ps_output.lines("\n").map { |line| line.strip }
|
135
163
|
lines.map do |line|
|
@@ -166,39 +194,5 @@ module RunLoop
|
|
166
194
|
def kill_signal(xcode_tools = RunLoop::XCTools.new)
|
167
195
|
xcode_tools.xcode_version_gte_6? ? 'QUIT' : 'TERM'
|
168
196
|
end
|
169
|
-
|
170
|
-
# @!visibility private
|
171
|
-
# Wait for Unix process with id `pid` to terminate.
|
172
|
-
#
|
173
|
-
# @param [Integer] pid The id of the process we are waiting on.
|
174
|
-
# @param [Hash] options Values to control the behavior of this method.
|
175
|
-
# @option options [Float] :timeout (2.0) How long to wait for the process to
|
176
|
-
# terminate.
|
177
|
-
# @option options [Float] :interval (0.1) The polling interval.
|
178
|
-
# @option options [Boolean] :raise_on_no_terminate (false) Should an error
|
179
|
-
# be raised if process does not terminate.
|
180
|
-
# @raise [RuntimeError] If process does not terminate and
|
181
|
-
# options[:raise_on_no_terminate] is truthy.
|
182
|
-
def wait_for_process_to_terminate(pid, options={})
|
183
|
-
default_opts = {:timeout => 2.0,
|
184
|
-
:interval => 0.1,
|
185
|
-
:raise_on_no_terminate => false}
|
186
|
-
merged_opts = default_opts.merge(options)
|
187
|
-
|
188
|
-
cmd = "ps #{pid} -o pid | grep #{pid}"
|
189
|
-
poll_until = Time.now + merged_opts[:timeout]
|
190
|
-
delay = merged_opts[:interval]
|
191
|
-
has_terminated = false
|
192
|
-
while Time.now < poll_until
|
193
|
-
has_terminated = `#{cmd}`.strip == ''
|
194
|
-
break if has_terminated
|
195
|
-
sleep delay
|
196
|
-
end
|
197
|
-
|
198
|
-
if merged_opts[:raise_on_no_terminate] and not has_terminated
|
199
|
-
details = `ps -p #{pid} -o pid,comm | grep #{pid}`.strip
|
200
|
-
raise RuntimeError, "Waited #{merged_opts[:timeout]} s for process '#{details}' to terminate"
|
201
|
-
end
|
202
|
-
end
|
203
197
|
end
|
204
198
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module RunLoop
|
2
|
+
|
3
|
+
# A class for interacting with the lldb command-line tool
|
4
|
+
class LLDB
|
5
|
+
|
6
|
+
# Returns a list of lldb pids.
|
7
|
+
# @return [Array<Integer>] An array of integer pids.
|
8
|
+
def self.lldb_pids
|
9
|
+
ps_output = `#{LLDB_FIND_PIDS_CMD}`.strip
|
10
|
+
lines = ps_output.lines("\n").map { |line| line.strip }
|
11
|
+
lldb_processes = lines.select { |line| self.is_lldb_process?(line) }
|
12
|
+
lldb_processes.map do |ps_description|
|
13
|
+
tokens = ps_description.strip.split(' ').map { |token| token.strip }
|
14
|
+
pid = tokens.fetch(0, nil)
|
15
|
+
if pid.nil?
|
16
|
+
nil
|
17
|
+
else
|
18
|
+
pid.to_i
|
19
|
+
end
|
20
|
+
end.compact.sort
|
21
|
+
end
|
22
|
+
|
23
|
+
# @!visibility private
|
24
|
+
# Is the process described an lldb process?
|
25
|
+
#
|
26
|
+
# @param [String] ps_details Details about a process as returned by `ps`
|
27
|
+
# @return [Boolean] True if the details describe an lldb process.
|
28
|
+
def self.is_lldb_process?(ps_details)
|
29
|
+
return false if ps_details.nil?
|
30
|
+
ps_details[/Contents\/Developer\/usr\/bin\/lldb/, 0] != nil
|
31
|
+
end
|
32
|
+
|
33
|
+
# Attempts to gracefully kill all running lldb processes.
|
34
|
+
def self.kill_lldb_processes
|
35
|
+
self.lldb_pids.each do |pid|
|
36
|
+
unless self.kill_with_signal(pid, 'TERM')
|
37
|
+
unless self.kill_with_signal(pid, 'QUIT')
|
38
|
+
self.kill_with_signal(pid, 'KILL')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# @!visibility private
|
47
|
+
LLDB_FIND_PIDS_CMD = 'ps x -o pid,command | grep -v grep | grep lldb'
|
48
|
+
|
49
|
+
# @!visibility private
|
50
|
+
def self.kill_with_signal(pid, signal)
|
51
|
+
RunLoop::ProcessTerminator.new(pid, signal, 'lldb').kill_process
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/lib/run_loop/plist_buddy.rb
CHANGED
@@ -68,6 +68,23 @@ module RunLoop
|
|
68
68
|
res == ''
|
69
69
|
end
|
70
70
|
|
71
|
+
# Creates an new empty plist at `path`.
|
72
|
+
#
|
73
|
+
# Is not responsible for creating directories or ensuring write permissions.
|
74
|
+
#
|
75
|
+
# @param [String] path Where to create the new plist.
|
76
|
+
def create_plist(path)
|
77
|
+
File.open(path, 'w') do |file|
|
78
|
+
file.puts "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
79
|
+
file.puts "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
|
80
|
+
file.puts "<plist version=\"1.0\">"
|
81
|
+
file.puts '<dict>'
|
82
|
+
file.puts '</dict>'
|
83
|
+
file.puts '</plist>'
|
84
|
+
end
|
85
|
+
path
|
86
|
+
end
|
87
|
+
|
71
88
|
private
|
72
89
|
|
73
90
|
# returns the path to the PlistBuddy executable
|
@@ -168,6 +185,5 @@ module RunLoop
|
|
168
185
|
|
169
186
|
"#{plist_buddy} -c #{cmd_part} \"#{file}\""
|
170
187
|
end
|
171
|
-
|
172
188
|
end
|
173
|
-
end
|
189
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module RunLoop
|
2
|
+
|
3
|
+
# A class for terminating processes and waiting for them to die.
|
4
|
+
class ProcessTerminator
|
5
|
+
|
6
|
+
# @!attribute [r] pid
|
7
|
+
# The process id of the process.
|
8
|
+
# @return [Integer] The pid.
|
9
|
+
attr_reader :pid
|
10
|
+
|
11
|
+
# @!attribute [r] kill_signal
|
12
|
+
# The kill signal to send to the process. Can be a Unix signal name or an
|
13
|
+
# Integer.
|
14
|
+
# @return [Integer, String] The kill signal.
|
15
|
+
attr_reader :kill_signal
|
16
|
+
|
17
|
+
# @!attribute [r] display_name
|
18
|
+
# The process name to use log messages and exceptions. Not used to find
|
19
|
+
# or otherwise interact with the process.
|
20
|
+
# @return [String] The display name.
|
21
|
+
attr_reader :display_name
|
22
|
+
|
23
|
+
# @!attribute [r] options
|
24
|
+
# Options to control the behavior of `kill_process`.
|
25
|
+
# @return [Hash] A hash of options.
|
26
|
+
attr_reader :options
|
27
|
+
|
28
|
+
# Create a new process terminator.
|
29
|
+
#
|
30
|
+
# @param[String,Integer] pid The process pid.
|
31
|
+
# @param[String, Integer] kill_signal The kill signal to send to the process.
|
32
|
+
# @param[String] display_name The name of the process to kill. Used only
|
33
|
+
# in log messages and exceptions.
|
34
|
+
# @option options [Float] :timeout (2.0) How long to wait for the process to
|
35
|
+
# terminate.
|
36
|
+
# @option options [Float] :interval (0.1) The polling interval.
|
37
|
+
# @option options [Boolean] :raise_on_no_terminate (false) Should an error
|
38
|
+
# be raised if process does not terminate.
|
39
|
+
def initialize(pid, kill_signal, display_name, options={})
|
40
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
41
|
+
@pid = pid.to_i
|
42
|
+
@kill_signal = kill_signal
|
43
|
+
@display_name = display_name
|
44
|
+
end
|
45
|
+
|
46
|
+
# Try to kill the process identified by `pid`.
|
47
|
+
#
|
48
|
+
# After sending `kill_signal` to `pid`, wait for the process to terminate.
|
49
|
+
#
|
50
|
+
# @return [Boolean] Returns true if the process was terminated or is no
|
51
|
+
# longer alive.
|
52
|
+
# @raise [SignalException] Raised on an unhandled `Process.kill` exception.
|
53
|
+
# Errno:ESRCH and Errno:EPERM are _handled_ exceptions; all others will
|
54
|
+
# be raised.
|
55
|
+
def kill_process
|
56
|
+
return true unless process_alive?
|
57
|
+
|
58
|
+
debug_logging = RunLoop::Environment.debug?
|
59
|
+
begin
|
60
|
+
if debug_logging
|
61
|
+
puts "Sending '#{kill_signal}' to #{display_name} process '#{pid}'"
|
62
|
+
end
|
63
|
+
Process.kill(kill_signal, pid.to_i)
|
64
|
+
# Don't wait.
|
65
|
+
# We might not own this process and a WNOHANG would be a nop.
|
66
|
+
# Process.wait(pid, Process::WNOHANG)
|
67
|
+
rescue Errno::ESRCH
|
68
|
+
if debug_logging
|
69
|
+
puts "Process with pid '#{pid}' does not exist; nothing to do."
|
70
|
+
end
|
71
|
+
# Return early; there is no need to wait if the process does not exist.
|
72
|
+
return true
|
73
|
+
rescue Errno::EPERM
|
74
|
+
if debug_logging
|
75
|
+
puts "Cannot kill process '#{pid}' with '#{kill_signal}'; not a child of this process"
|
76
|
+
end
|
77
|
+
rescue SignalException => e
|
78
|
+
raise e.message
|
79
|
+
end
|
80
|
+
|
81
|
+
if debug_logging
|
82
|
+
puts "Waiting for #{display_name} '#{pid}' to terminate"
|
83
|
+
end
|
84
|
+
wait_for_process_to_terminate
|
85
|
+
end
|
86
|
+
|
87
|
+
# Is the process `pid` alive?
|
88
|
+
# @return [Boolean] Returns true if the process is still alive.
|
89
|
+
def process_alive?
|
90
|
+
begin
|
91
|
+
Process.kill(0, pid.to_i)
|
92
|
+
true
|
93
|
+
rescue Errno::ESRCH
|
94
|
+
false
|
95
|
+
rescue Errno::EPERM
|
96
|
+
true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# @!visibility private
|
103
|
+
# The default options for waiting on a process to terminate.
|
104
|
+
DEFAULT_OPTIONS =
|
105
|
+
{
|
106
|
+
:timeout => 2.0,
|
107
|
+
:interval => 0.1,
|
108
|
+
:raise_on_no_terminate => false
|
109
|
+
}
|
110
|
+
|
111
|
+
# @!visibility private
|
112
|
+
# The details of the process reported by `ps`.
|
113
|
+
def ps_details
|
114
|
+
`xcrun ps -p #{pid} -o pid,comm | grep #{pid}`.strip
|
115
|
+
end
|
116
|
+
|
117
|
+
# @!visibility private
|
118
|
+
# Wait for the process to terminate by polling.
|
119
|
+
def wait_for_process_to_terminate
|
120
|
+
now = Time.now
|
121
|
+
poll_until = now + options[:timeout]
|
122
|
+
delay = options[:interval]
|
123
|
+
has_terminated = false
|
124
|
+
while Time.now < poll_until
|
125
|
+
has_terminated = !process_alive?
|
126
|
+
break if has_terminated
|
127
|
+
sleep delay
|
128
|
+
end
|
129
|
+
|
130
|
+
if RunLoop::Environment.debug?
|
131
|
+
puts "Waited for #{Time.now - now} seconds for #{display_name} with '#{pid}' to terminate"
|
132
|
+
end
|
133
|
+
|
134
|
+
if @options[:raise_on_no_terminate] and !has_terminated
|
135
|
+
raise "Waited #{options[:timeout]} seconds for #{display_name} (#{ps_details}) to terminate"
|
136
|
+
end
|
137
|
+
has_terminated
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module RunLoop
|
2
|
+
|
3
|
+
# A class for waiting on processes.
|
4
|
+
class ProcessWaiter
|
5
|
+
|
6
|
+
attr_reader :process_name
|
7
|
+
|
8
|
+
def initialize(process_name, options={})
|
9
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
10
|
+
@process_name = process_name
|
11
|
+
end
|
12
|
+
|
13
|
+
# Collect a list of Integer pids.
|
14
|
+
# @return [Array<Integer>] An array of integer pids for the `process_name`
|
15
|
+
def pids
|
16
|
+
process_info = `ps x -o pid,comm | grep -v grep | grep #{process_name}`
|
17
|
+
process_array = process_info.split("\n")
|
18
|
+
process_array.map { |process| process.split(' ').first.strip.to_i }
|
19
|
+
end
|
20
|
+
|
21
|
+
# Is the `process_name` a running?
|
22
|
+
def running_process?
|
23
|
+
!pids.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Wait for `process_name` to start.
|
27
|
+
def wait_for_any
|
28
|
+
return true if running_process?
|
29
|
+
|
30
|
+
now = Time.now
|
31
|
+
poll_until = now + @options[:timeout]
|
32
|
+
delay = @options[:interval]
|
33
|
+
is_alive = false
|
34
|
+
while Time.now < poll_until
|
35
|
+
is_alive = running_process?
|
36
|
+
break if is_alive
|
37
|
+
sleep delay
|
38
|
+
end
|
39
|
+
|
40
|
+
if RunLoop::Environment.debug?
|
41
|
+
puts "Waited for #{Time.now - now} seconds for '#{process_name}' to start."
|
42
|
+
end
|
43
|
+
|
44
|
+
if @options[:raise_on_timeout] and !is_alive
|
45
|
+
raise "Waited #{@options[:timeout]} seconds for '#{process_name}' to start."
|
46
|
+
end
|
47
|
+
is_alive
|
48
|
+
end
|
49
|
+
|
50
|
+
# Wait for all `process_name` to finish.
|
51
|
+
def wait_for_none
|
52
|
+
return true if !running_process?
|
53
|
+
|
54
|
+
now = Time.now
|
55
|
+
poll_until = now + @options[:timeout]
|
56
|
+
delay = @options[:interval]
|
57
|
+
has_terminated = false
|
58
|
+
while Time.now < poll_until
|
59
|
+
has_terminated = !self.running_process?
|
60
|
+
break if has_terminated
|
61
|
+
sleep delay
|
62
|
+
end
|
63
|
+
|
64
|
+
if RunLoop::Environment.debug?
|
65
|
+
puts "Waited for #{Time.now - now} seconds for '#{process_name}' to die."
|
66
|
+
end
|
67
|
+
|
68
|
+
if @options[:raise_on_timeout] and !has_terminated
|
69
|
+
raise "Waited #{@options[:timeout]} seconds for '#{process_name}' to die."
|
70
|
+
end
|
71
|
+
has_terminated
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# @!visibility private
|
77
|
+
DEFAULT_OPTIONS =
|
78
|
+
{
|
79
|
+
:timeout => 10.0,
|
80
|
+
:interval => 0.1,
|
81
|
+
:raise_on_timeout => false
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|