poltergeistFork 0.0.1
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 +7 -0
- data/LICENSE +22 -0
- data/README.md +425 -0
- data/lib/capybara/poltergeist/browser.rb +426 -0
- data/lib/capybara/poltergeist/client.rb +151 -0
- data/lib/capybara/poltergeist/client/agent.coffee +423 -0
- data/lib/capybara/poltergeist/client/browser.coffee +497 -0
- data/lib/capybara/poltergeist/client/cmd.coffee +17 -0
- data/lib/capybara/poltergeist/client/compiled/agent.js +587 -0
- data/lib/capybara/poltergeist/client/compiled/browser.js +687 -0
- data/lib/capybara/poltergeist/client/compiled/cmd.js +31 -0
- data/lib/capybara/poltergeist/client/compiled/connection.js +25 -0
- data/lib/capybara/poltergeist/client/compiled/main.js +228 -0
- data/lib/capybara/poltergeist/client/compiled/node.js +88 -0
- data/lib/capybara/poltergeist/client/compiled/web_page.js +539 -0
- data/lib/capybara/poltergeist/client/connection.coffee +11 -0
- data/lib/capybara/poltergeist/client/main.coffee +99 -0
- data/lib/capybara/poltergeist/client/node.coffee +70 -0
- data/lib/capybara/poltergeist/client/pre/agent.js +587 -0
- data/lib/capybara/poltergeist/client/pre/browser.js +688 -0
- data/lib/capybara/poltergeist/client/pre/cmd.js +31 -0
- data/lib/capybara/poltergeist/client/pre/connection.js +25 -0
- data/lib/capybara/poltergeist/client/pre/main.js +228 -0
- data/lib/capybara/poltergeist/client/pre/node.js +88 -0
- data/lib/capybara/poltergeist/client/pre/web_page.js +540 -0
- data/lib/capybara/poltergeist/client/web_page.coffee +372 -0
- data/lib/capybara/poltergeist/command.rb +17 -0
- data/lib/capybara/poltergeist/cookie.rb +35 -0
- data/lib/capybara/poltergeist/driver.rb +394 -0
- data/lib/capybara/poltergeist/errors.rb +183 -0
- data/lib/capybara/poltergeist/inspector.rb +46 -0
- data/lib/capybara/poltergeist/json.rb +25 -0
- data/lib/capybara/poltergeist/network_traffic.rb +7 -0
- data/lib/capybara/poltergeist/network_traffic/error.rb +19 -0
- data/lib/capybara/poltergeist/network_traffic/request.rb +27 -0
- data/lib/capybara/poltergeist/network_traffic/response.rb +40 -0
- data/lib/capybara/poltergeist/node.rb +177 -0
- data/lib/capybara/poltergeist/server.rb +36 -0
- data/lib/capybara/poltergeist/utility.rb +9 -0
- data/lib/capybara/poltergeist/version.rb +5 -0
- data/lib/capybara/poltergeist/web_socket_server.rb +107 -0
- data/lib/capybara/poltergeistFork.rb +27 -0
- metadata +268 -0
@@ -0,0 +1,426 @@
|
|
1
|
+
require "capybara/poltergeist/errors"
|
2
|
+
require "capybara/poltergeist/command"
|
3
|
+
require 'multi_json'
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
module Capybara::Poltergeist
|
7
|
+
class Browser
|
8
|
+
ERROR_MAPPINGS = {
|
9
|
+
'Poltergeist.JavascriptError' => JavascriptError,
|
10
|
+
'Poltergeist.FrameNotFound' => FrameNotFound,
|
11
|
+
'Poltergeist.InvalidSelector' => InvalidSelector,
|
12
|
+
'Poltergeist.StatusFailError' => StatusFailError,
|
13
|
+
'Poltergeist.NoSuchWindowError' => NoSuchWindowError
|
14
|
+
}
|
15
|
+
|
16
|
+
attr_reader :server, :client, :logger
|
17
|
+
|
18
|
+
def initialize(server, client, logger = nil)
|
19
|
+
@server = server
|
20
|
+
@client = client
|
21
|
+
@logger = logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def restart
|
25
|
+
server.restart
|
26
|
+
client.restart
|
27
|
+
|
28
|
+
self.debug = @debug if defined?(@debug)
|
29
|
+
self.js_errors = @js_errors if defined?(@js_errors)
|
30
|
+
self.extensions = @extensions if @extensions
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit(url)
|
34
|
+
command 'visit', url
|
35
|
+
end
|
36
|
+
|
37
|
+
def current_url
|
38
|
+
command 'current_url'
|
39
|
+
end
|
40
|
+
|
41
|
+
def status_code
|
42
|
+
command 'status_code'
|
43
|
+
end
|
44
|
+
|
45
|
+
def body
|
46
|
+
command 'body'
|
47
|
+
end
|
48
|
+
|
49
|
+
def source
|
50
|
+
command 'source'
|
51
|
+
end
|
52
|
+
|
53
|
+
def title
|
54
|
+
command 'title'
|
55
|
+
end
|
56
|
+
|
57
|
+
def parents(page_id, id)
|
58
|
+
command 'parents', page_id, id
|
59
|
+
end
|
60
|
+
|
61
|
+
def find(method, selector)
|
62
|
+
result = command('find', method, selector)
|
63
|
+
result['ids'].map { |id| [result['page_id'], id] }
|
64
|
+
end
|
65
|
+
|
66
|
+
def find_within(page_id, id, method, selector)
|
67
|
+
command 'find_within', page_id, id, method, selector
|
68
|
+
end
|
69
|
+
|
70
|
+
def all_text(page_id, id)
|
71
|
+
command 'all_text', page_id, id
|
72
|
+
end
|
73
|
+
|
74
|
+
def visible_text(page_id, id)
|
75
|
+
command 'visible_text', page_id, id
|
76
|
+
end
|
77
|
+
|
78
|
+
def delete_text(page_id, id)
|
79
|
+
command 'delete_text', page_id, id
|
80
|
+
end
|
81
|
+
|
82
|
+
def property(page_id, id, name)
|
83
|
+
command 'property', page_id, id, name.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
def attributes(page_id, id)
|
87
|
+
command 'attributes', page_id, id
|
88
|
+
end
|
89
|
+
|
90
|
+
def attribute(page_id, id, name)
|
91
|
+
command 'attribute', page_id, id, name.to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
def value(page_id, id)
|
95
|
+
command 'value', page_id, id
|
96
|
+
end
|
97
|
+
|
98
|
+
def set(page_id, id, value)
|
99
|
+
command 'set', page_id, id, value
|
100
|
+
end
|
101
|
+
|
102
|
+
def select_file(page_id, id, value)
|
103
|
+
command 'select_file', page_id, id, value
|
104
|
+
end
|
105
|
+
|
106
|
+
def tag_name(page_id, id)
|
107
|
+
command('tag_name', page_id, id).downcase
|
108
|
+
end
|
109
|
+
|
110
|
+
def visible?(page_id, id)
|
111
|
+
command 'visible', page_id, id
|
112
|
+
end
|
113
|
+
|
114
|
+
def disabled?(page_id, id)
|
115
|
+
command 'disabled', page_id, id
|
116
|
+
end
|
117
|
+
|
118
|
+
def click_coordinates(x, y)
|
119
|
+
command 'click_coordinates', x, y
|
120
|
+
end
|
121
|
+
|
122
|
+
def evaluate(script)
|
123
|
+
command 'evaluate', script
|
124
|
+
end
|
125
|
+
|
126
|
+
def execute(script)
|
127
|
+
command 'execute', script
|
128
|
+
end
|
129
|
+
|
130
|
+
def within_frame(handle, &block)
|
131
|
+
if handle.is_a?(Capybara::Node::Base)
|
132
|
+
command 'push_frame', [handle.native.page_id, handle.native.id]
|
133
|
+
else
|
134
|
+
command 'push_frame', handle
|
135
|
+
end
|
136
|
+
|
137
|
+
yield
|
138
|
+
ensure
|
139
|
+
command 'pop_frame'
|
140
|
+
end
|
141
|
+
|
142
|
+
def window_handle
|
143
|
+
command 'window_handle'
|
144
|
+
end
|
145
|
+
|
146
|
+
def window_handles
|
147
|
+
command 'window_handles'
|
148
|
+
end
|
149
|
+
|
150
|
+
def switch_to_window(handle)
|
151
|
+
command 'switch_to_window', handle
|
152
|
+
end
|
153
|
+
|
154
|
+
def open_new_window
|
155
|
+
command 'open_new_window'
|
156
|
+
end
|
157
|
+
|
158
|
+
def close_window(handle)
|
159
|
+
command 'close_window', handle
|
160
|
+
end
|
161
|
+
|
162
|
+
def find_window_handle(locator)
|
163
|
+
return locator if window_handles.include? locator
|
164
|
+
|
165
|
+
handle = command 'window_handle', locator
|
166
|
+
raise noSuchWindowError unless handle
|
167
|
+
return handle
|
168
|
+
end
|
169
|
+
|
170
|
+
def within_window(locator, &block)
|
171
|
+
original = window_handle
|
172
|
+
handle = find_window_handle(locator)
|
173
|
+
switch_to_window(handle)
|
174
|
+
yield
|
175
|
+
ensure
|
176
|
+
switch_to_window(original)
|
177
|
+
end
|
178
|
+
|
179
|
+
def click(page_id, id)
|
180
|
+
command 'click', page_id, id
|
181
|
+
end
|
182
|
+
|
183
|
+
def right_click(page_id, id)
|
184
|
+
command 'right_click', page_id, id
|
185
|
+
end
|
186
|
+
|
187
|
+
def double_click(page_id, id)
|
188
|
+
command 'double_click', page_id, id
|
189
|
+
end
|
190
|
+
|
191
|
+
def hover(page_id, id)
|
192
|
+
command 'hover', page_id, id
|
193
|
+
end
|
194
|
+
|
195
|
+
def drag(page_id, id, other_id)
|
196
|
+
command 'drag', page_id, id, other_id
|
197
|
+
end
|
198
|
+
|
199
|
+
def drag_by(page_id, id, x, y)
|
200
|
+
command 'drag_by', page_id, id, x, y
|
201
|
+
end
|
202
|
+
|
203
|
+
def select(page_id, id, value)
|
204
|
+
command 'select', page_id, id, value
|
205
|
+
end
|
206
|
+
|
207
|
+
def trigger(page_id, id, event)
|
208
|
+
command 'trigger', page_id, id, event.to_s
|
209
|
+
end
|
210
|
+
|
211
|
+
def reset
|
212
|
+
command 'reset'
|
213
|
+
end
|
214
|
+
|
215
|
+
def scroll_to(left, top)
|
216
|
+
command 'scroll_to', left, top
|
217
|
+
end
|
218
|
+
|
219
|
+
def render(path, options = {})
|
220
|
+
check_render_options!(options)
|
221
|
+
command 'render', path.to_s, !!options[:full], options[:selector]
|
222
|
+
end
|
223
|
+
|
224
|
+
def render_base64(format, options = {})
|
225
|
+
check_render_options!(options)
|
226
|
+
command 'render_base64', format.to_s, !!options[:full], options[:selector]
|
227
|
+
end
|
228
|
+
|
229
|
+
def set_zoom_factor(zoom_factor)
|
230
|
+
command 'set_zoom_factor', zoom_factor
|
231
|
+
end
|
232
|
+
|
233
|
+
def set_screen_size(s_width,s_height)
|
234
|
+
command 'set_screen_size', s_width, s_height
|
235
|
+
end
|
236
|
+
|
237
|
+
def set_paper_size(size)
|
238
|
+
command 'set_paper_size', size
|
239
|
+
end
|
240
|
+
|
241
|
+
def resize(width, height)
|
242
|
+
command 'resize', width, height
|
243
|
+
end
|
244
|
+
|
245
|
+
def send_keys(page_id, id, keys)
|
246
|
+
command 'send_keys', page_id, id, normalize_keys(keys)
|
247
|
+
end
|
248
|
+
|
249
|
+
def path(page_id, id)
|
250
|
+
command 'path', page_id, id
|
251
|
+
end
|
252
|
+
|
253
|
+
def network_traffic
|
254
|
+
command('network_traffic').values.map do |event|
|
255
|
+
NetworkTraffic::Request.new(
|
256
|
+
event['request'],
|
257
|
+
event['responseParts'].map { |response| NetworkTraffic::Response.new(response) },
|
258
|
+
event['error'] ? NetworkTraffic::Error.new(event['error']) : nil
|
259
|
+
)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def clear_network_traffic
|
264
|
+
command('clear_network_traffic')
|
265
|
+
end
|
266
|
+
|
267
|
+
def equals(page_id, id, other_id)
|
268
|
+
command('equals', page_id, id, other_id)
|
269
|
+
end
|
270
|
+
|
271
|
+
def get_headers
|
272
|
+
command 'get_headers'
|
273
|
+
end
|
274
|
+
|
275
|
+
def set_headers(headers)
|
276
|
+
command 'set_headers', headers
|
277
|
+
end
|
278
|
+
|
279
|
+
def add_headers(headers)
|
280
|
+
command 'add_headers', headers
|
281
|
+
end
|
282
|
+
|
283
|
+
def add_header(header, permanent)
|
284
|
+
command 'add_header', header, permanent
|
285
|
+
end
|
286
|
+
|
287
|
+
def response_headers
|
288
|
+
command 'response_headers'
|
289
|
+
end
|
290
|
+
|
291
|
+
def cookies
|
292
|
+
Hash[command('cookies').map { |cookie| [cookie['name'], Cookie.new(cookie)] }]
|
293
|
+
end
|
294
|
+
|
295
|
+
def set_cookie(cookie)
|
296
|
+
if cookie[:expires]
|
297
|
+
cookie[:expires] = cookie[:expires].to_i * 1000
|
298
|
+
end
|
299
|
+
|
300
|
+
command 'set_cookie', cookie
|
301
|
+
end
|
302
|
+
|
303
|
+
def remove_cookie(name)
|
304
|
+
command 'remove_cookie', name
|
305
|
+
end
|
306
|
+
|
307
|
+
def clear_cookies
|
308
|
+
command 'clear_cookies'
|
309
|
+
end
|
310
|
+
|
311
|
+
def cookies_enabled=(flag)
|
312
|
+
command 'cookies_enabled', !!flag
|
313
|
+
end
|
314
|
+
|
315
|
+
def set_http_auth(user, password)
|
316
|
+
command 'set_http_auth', user, password
|
317
|
+
end
|
318
|
+
|
319
|
+
def js_errors=(val)
|
320
|
+
@js_errors = val
|
321
|
+
command 'set_js_errors', !!val
|
322
|
+
end
|
323
|
+
|
324
|
+
def extensions=(names)
|
325
|
+
@extensions = names
|
326
|
+
Array(names).each do |name|
|
327
|
+
command 'add_extension', name
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def url_blacklist=(blacklist)
|
332
|
+
command 'set_url_blacklist', *blacklist
|
333
|
+
end
|
334
|
+
|
335
|
+
def debug=(val)
|
336
|
+
@debug = val
|
337
|
+
command 'set_debug', !!val
|
338
|
+
end
|
339
|
+
|
340
|
+
def command(name, *args)
|
341
|
+
cmd = Command.new(name, *args)
|
342
|
+
log cmd.message
|
343
|
+
|
344
|
+
response = server.send(cmd)
|
345
|
+
log response
|
346
|
+
|
347
|
+
json = JSON.load(response)
|
348
|
+
|
349
|
+
if json['error']
|
350
|
+
klass = ERROR_MAPPINGS[json['error']['name']] || BrowserError
|
351
|
+
raise klass.new(json['error'])
|
352
|
+
else
|
353
|
+
json['response']
|
354
|
+
end
|
355
|
+
rescue DeadClient
|
356
|
+
restart
|
357
|
+
raise
|
358
|
+
end
|
359
|
+
|
360
|
+
def go_back
|
361
|
+
command 'go_back'
|
362
|
+
end
|
363
|
+
|
364
|
+
def go_forward
|
365
|
+
command 'go_forward'
|
366
|
+
end
|
367
|
+
|
368
|
+
def accept_confirm
|
369
|
+
command 'set_confirm_process', true
|
370
|
+
end
|
371
|
+
|
372
|
+
def dismiss_confirm
|
373
|
+
command 'set_confirm_process', false
|
374
|
+
end
|
375
|
+
|
376
|
+
#
|
377
|
+
# press "OK" with text (response) or default value
|
378
|
+
#
|
379
|
+
def accept_prompt(response)
|
380
|
+
command 'set_prompt_response', response || false
|
381
|
+
end
|
382
|
+
|
383
|
+
#
|
384
|
+
# press "Cancel"
|
385
|
+
#
|
386
|
+
def dismiss_prompt
|
387
|
+
command 'set_prompt_response', nil
|
388
|
+
end
|
389
|
+
|
390
|
+
def modal_message
|
391
|
+
command 'modal_message'
|
392
|
+
end
|
393
|
+
|
394
|
+
private
|
395
|
+
|
396
|
+
def log(message)
|
397
|
+
logger.puts message if logger
|
398
|
+
end
|
399
|
+
|
400
|
+
def check_render_options!(options)
|
401
|
+
if !!options[:full] && options.has_key?(:selector)
|
402
|
+
warn "Ignoring :selector in #render since :full => true was given at #{caller.first}"
|
403
|
+
options.delete(:selector)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
def normalize_keys(keys)
|
408
|
+
keys.map do |key|
|
409
|
+
case key
|
410
|
+
when Array
|
411
|
+
# [:Shift, "s"] => { modifier: "shift", key: "S" }
|
412
|
+
# [:Ctrl, :Left] => { modifier: "ctrl", key: :Left }
|
413
|
+
# [:Ctrl, :Shift, :Left] => { modifier: "ctrl,shift", key: :Left }
|
414
|
+
letter = key.pop
|
415
|
+
symbol = key.map { |k| k.to_s.downcase }.join(',')
|
416
|
+
|
417
|
+
{ modifier: symbol.to_s.downcase, key: letter.capitalize }
|
418
|
+
when Symbol
|
419
|
+
{ key: key.capitalize } # Return a known sequence for PhantomJS
|
420
|
+
when String
|
421
|
+
key # Plain string, nothing to do
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require "timeout"
|
2
|
+
require "capybara/poltergeist/utility"
|
3
|
+
require 'cliver'
|
4
|
+
|
5
|
+
module Capybara::Poltergeist
|
6
|
+
class Client
|
7
|
+
PHANTOMJS_SCRIPT = File.expand_path('../client/compiled/main.js', __FILE__)
|
8
|
+
PHANTOMJS_VERSION = ['>= 1.8.1', '< 3.0']
|
9
|
+
PHANTOMJS_NAME = 'phantomjs'
|
10
|
+
|
11
|
+
KILL_TIMEOUT = 2 # seconds
|
12
|
+
|
13
|
+
def self.start(*args)
|
14
|
+
client = new(*args)
|
15
|
+
client.start
|
16
|
+
client
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns a proc, that when called will attempt to kill the given process.
|
20
|
+
# This is because implementing ObjectSpace.define_finalizer is tricky.
|
21
|
+
# Hat-Tip to @mperham for describing in detail:
|
22
|
+
# http://www.mikeperham.com/2010/02/24/the-trouble-with-ruby-finalizers/
|
23
|
+
def self.process_killer(pid)
|
24
|
+
proc do
|
25
|
+
begin
|
26
|
+
Process.kill('KILL', pid)
|
27
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :pid, :server, :path, :window_size, :phantomjs_options
|
33
|
+
|
34
|
+
def initialize(server, options = {})
|
35
|
+
@server = server
|
36
|
+
@path = Cliver::detect!((options[:path] || PHANTOMJS_NAME),
|
37
|
+
*PHANTOMJS_VERSION)
|
38
|
+
|
39
|
+
@window_size = options[:window_size] || [1024, 768]
|
40
|
+
@phantomjs_options = options[:phantomjs_options] || []
|
41
|
+
@phantomjs_logger = options[:phantomjs_logger] || $stdout
|
42
|
+
|
43
|
+
pid = Process.pid
|
44
|
+
at_exit do
|
45
|
+
# do the work in a separate thread, to avoid stomping on $!,
|
46
|
+
# since other libraries depend on it directly.
|
47
|
+
Thread.new do
|
48
|
+
stop if Process.pid == pid
|
49
|
+
end.join
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def start
|
54
|
+
@read_io, @write_io = IO.pipe
|
55
|
+
@out_thread = Thread.new {
|
56
|
+
while !@read_io.eof? && data = @read_io.readpartial(1024)
|
57
|
+
@phantomjs_logger.write(data)
|
58
|
+
end
|
59
|
+
}
|
60
|
+
|
61
|
+
process_options = {}
|
62
|
+
process_options[:pgroup] = true unless Capybara::Poltergeist.windows?
|
63
|
+
|
64
|
+
redirect_stdout do
|
65
|
+
@pid = Process.spawn(*command.map(&:to_s), process_options)
|
66
|
+
ObjectSpace.define_finalizer(self, self.class.process_killer(@pid))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def stop
|
71
|
+
if pid
|
72
|
+
kill_phantomjs
|
73
|
+
@out_thread.kill
|
74
|
+
close_io
|
75
|
+
ObjectSpace.undefine_finalizer(self)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def restart
|
80
|
+
stop
|
81
|
+
start
|
82
|
+
end
|
83
|
+
|
84
|
+
def command
|
85
|
+
parts = [path]
|
86
|
+
parts.concat phantomjs_options
|
87
|
+
parts << PHANTOMJS_SCRIPT
|
88
|
+
parts << server.port
|
89
|
+
parts.concat window_size
|
90
|
+
parts
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# This abomination is because JRuby doesn't support the :out option of
|
96
|
+
# Process.spawn. To be honest it works pretty bad with pipes too, because
|
97
|
+
# we ought close writing end in parent process immediately but JRuby will
|
98
|
+
# lose all the output from child. Process.popen can be used here and seems
|
99
|
+
# it works with JRuby but I've experienced strange mistakes on Rubinius.
|
100
|
+
def redirect_stdout
|
101
|
+
prev = STDOUT.dup
|
102
|
+
$stdout = @write_io
|
103
|
+
STDOUT.reopen(@write_io)
|
104
|
+
yield
|
105
|
+
ensure
|
106
|
+
STDOUT.reopen(prev)
|
107
|
+
$stdout = STDOUT
|
108
|
+
prev.close
|
109
|
+
end
|
110
|
+
|
111
|
+
def kill_phantomjs
|
112
|
+
begin
|
113
|
+
if Capybara::Poltergeist.windows?
|
114
|
+
Process.kill('KILL', pid)
|
115
|
+
else
|
116
|
+
Process.kill('TERM', pid)
|
117
|
+
begin
|
118
|
+
Timeout.timeout(KILL_TIMEOUT) { Process.wait(pid) }
|
119
|
+
rescue Timeout::Error
|
120
|
+
Process.kill('KILL', pid)
|
121
|
+
Process.wait(pid)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
125
|
+
# Zed's dead, baby
|
126
|
+
end
|
127
|
+
@pid = nil
|
128
|
+
end
|
129
|
+
|
130
|
+
# We grab all the output from PhantomJS like console.log in another thread
|
131
|
+
# and when PhantomJS crashes we try to restart it. In order to do it we stop
|
132
|
+
# server and client and on JRuby see this error `IOError: Stream closed`.
|
133
|
+
# It happens because JRuby tries to close pipe and it is blocked on `eof?`
|
134
|
+
# or `readpartial` call. The error is raised in the related thread and it's
|
135
|
+
# not actually main thread but the thread that listens to the output. That's
|
136
|
+
# why if you put some debug code after `rescue IOError` it won't be shown.
|
137
|
+
# In fact the main thread will continue working after the error even if we
|
138
|
+
# don't use `rescue`. The first attempt to fix it was a try not to block on
|
139
|
+
# IO, but looks like similar issue appers after JRuby upgrade. Perhaps the
|
140
|
+
# only way to fix it is catching the exception what this method overall does.
|
141
|
+
def close_io
|
142
|
+
[@write_io, @read_io].each do |io|
|
143
|
+
begin
|
144
|
+
io.close unless io.closed?
|
145
|
+
rescue IOError
|
146
|
+
raise unless RUBY_ENGINE == 'jruby'
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|