scriptty 0.5.0-java
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.
- data/.gitattributes +1 -0
- data/.gitignore +3 -0
- data/COPYING +674 -0
- data/COPYING.LESSER +165 -0
- data/README.rdoc +31 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/bin/scriptty-capture +5 -0
- data/bin/scriptty-dump-screens +4 -0
- data/bin/scriptty-replay +5 -0
- data/bin/scriptty-term-test +4 -0
- data/bin/scriptty-transcript-parse +4 -0
- data/examples/captures/xterm-overlong-line-prompt.bin +9 -0
- data/examples/captures/xterm-vim-session.bin +262 -0
- data/examples/demo-capture.rb +19 -0
- data/examples/telnet-nego.rb +55 -0
- data/lib/scriptty/apps/capture_app/console.rb +104 -0
- data/lib/scriptty/apps/capture_app/password_prompt.rb +65 -0
- data/lib/scriptty/apps/capture_app.rb +213 -0
- data/lib/scriptty/apps/dump_screens_app.rb +166 -0
- data/lib/scriptty/apps/replay_app.rb +229 -0
- data/lib/scriptty/apps/term_test_app.rb +124 -0
- data/lib/scriptty/apps/transcript_parse_app.rb +143 -0
- data/lib/scriptty/cursor.rb +39 -0
- data/lib/scriptty/exception.rb +38 -0
- data/lib/scriptty/expect.rb +392 -0
- data/lib/scriptty/multiline_buffer.rb +192 -0
- data/lib/scriptty/net/event_loop.rb +610 -0
- data/lib/scriptty/screen_pattern/generator.rb +398 -0
- data/lib/scriptty/screen_pattern/parser.rb +558 -0
- data/lib/scriptty/screen_pattern.rb +104 -0
- data/lib/scriptty/term/dg410/dg410-client-escapes.txt +37 -0
- data/lib/scriptty/term/dg410/dg410-escapes.txt +82 -0
- data/lib/scriptty/term/dg410/parser.rb +162 -0
- data/lib/scriptty/term/dg410.rb +489 -0
- data/lib/scriptty/term/xterm/xterm-escapes.txt +73 -0
- data/lib/scriptty/term/xterm.rb +661 -0
- data/lib/scriptty/term.rb +40 -0
- data/lib/scriptty/util/fsm/definition_parser.rb +111 -0
- data/lib/scriptty/util/fsm/scriptty_fsm_definition.treetop +189 -0
- data/lib/scriptty/util/fsm.rb +177 -0
- data/lib/scriptty/util/transcript/reader.rb +96 -0
- data/lib/scriptty/util/transcript/writer.rb +111 -0
- data/test/apps/capture_app_test.rb +123 -0
- data/test/apps/transcript_parse_app_test.rb +118 -0
- data/test/cursor_test.rb +51 -0
- data/test/fsm_definition_parser_test.rb +220 -0
- data/test/fsm_test.rb +322 -0
- data/test/multiline_buffer_test.rb +275 -0
- data/test/net/event_loop_test.rb +402 -0
- data/test/screen_pattern/generator_test.rb +408 -0
- data/test/screen_pattern/parser_test/explicit_cursor_pattern.txt +14 -0
- data/test/screen_pattern/parser_test/explicit_fields.txt +22 -0
- data/test/screen_pattern/parser_test/multiple_patterns.txt +42 -0
- data/test/screen_pattern/parser_test/simple_pattern.txt +14 -0
- data/test/screen_pattern/parser_test/truncated_heredoc.txt +12 -0
- data/test/screen_pattern/parser_test/utf16bebom_pattern.bin +0 -0
- data/test/screen_pattern/parser_test/utf16lebom_pattern.bin +0 -0
- data/test/screen_pattern/parser_test/utf8_pattern.bin +14 -0
- data/test/screen_pattern/parser_test/utf8_unix_pattern.bin +14 -0
- data/test/screen_pattern/parser_test/utf8bom_pattern.bin +14 -0
- data/test/screen_pattern/parser_test.rb +266 -0
- data/test/term/dg410/parser_test.rb +139 -0
- data/test/term/xterm_test.rb +327 -0
- data/test/test_helper.rb +3 -0
- data/test/util/transcript/reader_test.rb +131 -0
- data/test/util/transcript/writer_test.rb +126 -0
- data/test.watchr +29 -0
- metadata +175 -0
@@ -0,0 +1,402 @@
|
|
1
|
+
# = Tests for ScripTTY::Net::EventLoop
|
2
|
+
# Copyright (C) 2010 Infonium Inc.
|
3
|
+
#
|
4
|
+
# This file is part of ScripTTY.
|
5
|
+
#
|
6
|
+
# ScripTTY is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Lesser General Public License as published
|
8
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# ScripTTY is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU Lesser General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Lesser General Public License
|
17
|
+
# along with ScripTTY. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
require File.dirname(__FILE__) + "/../test_helper.rb"
|
20
|
+
|
21
|
+
class EventLoopTest < Test::Unit::TestCase
|
22
|
+
|
23
|
+
if !defined?(Java)
|
24
|
+
|
25
|
+
# This test gets executed when JRuby is not detected
|
26
|
+
def test_dummy_no_jruby
|
27
|
+
raise LoadError.new("Cannot test ScripTTY::Net::EventLoop: Not running under JRuby")
|
28
|
+
end
|
29
|
+
|
30
|
+
else # defined?(Java)
|
31
|
+
|
32
|
+
# XXX - These tests are ugly, using a mix of different styles. Clean them up (only if you know what you are doing!)
|
33
|
+
|
34
|
+
require 'scriptty/net/event_loop'
|
35
|
+
require 'thread'
|
36
|
+
require 'stringio'
|
37
|
+
|
38
|
+
CONNECTION_REFUSE_ADDR = ['localhost', 2] # address on which connections will be refused
|
39
|
+
|
40
|
+
def setup
|
41
|
+
raise "EventLoopTest disabled" # FIXME
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_callback_error_handling_on_connect_error
|
45
|
+
result = []
|
46
|
+
evloop = ScripTTY::Net::EventLoop.new
|
47
|
+
evloop.connect(CONNECTION_REFUSE_ADDR) { |c|
|
48
|
+
c.on_callback_error { result << :callback_error }
|
49
|
+
c.on_connect_error { |e| raise "FOO" }
|
50
|
+
}
|
51
|
+
evloop.main
|
52
|
+
assert_equal [:callback_error], result
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_connect_error_handling
|
56
|
+
result = []
|
57
|
+
connect_error = nil
|
58
|
+
evloop = ScripTTY::Net::EventLoop.new
|
59
|
+
evloop.connect(CONNECTION_REFUSE_ADDR) { |c|
|
60
|
+
c.on_connect { result << :connect }
|
61
|
+
c.on_connect_error { |e| result << :connect_error; connect_error = e }
|
62
|
+
c.on_receive_bytes { |bytes| result << [:receive_bytes, bytes] }
|
63
|
+
c.on_close { result << :closed }
|
64
|
+
}
|
65
|
+
evloop.main
|
66
|
+
assert_equal [:connect_error], result
|
67
|
+
assert_match /^java.net.ConnectException: Connection refused/, connect_error.message
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_channel_gets_deregistered_after_close
|
71
|
+
selector_keys = nil
|
72
|
+
evloop = ScripTTY::Net::EventLoop.new
|
73
|
+
evloop.connect(CONNECTION_REFUSE_ADDR) { |c|
|
74
|
+
c.on_connect_error { |e|
|
75
|
+
# On the next call to select(), the SelectionKey should be gone.
|
76
|
+
evloop.timer(0) {
|
77
|
+
selector_keys = evloop.instance_eval("@selector").keys.to_a
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
evloop.main
|
82
|
+
assert_equal [], selector_keys, "Channels should be deregistered with the Selector when a channel is closed"
|
83
|
+
end
|
84
|
+
|
85
|
+
# An listening socket should be closed when the event loop finishes
|
86
|
+
def test_listening_socket_gets_closed_on_exit
|
87
|
+
# Create an event loop, bind a socket, then exit the event loop.
|
88
|
+
evloop = ScripTTY::Net::EventLoop.new
|
89
|
+
bind_addr = evloop.listen(['localhost', 0]) { |server| server.local_address }
|
90
|
+
evloop.timer(0) { evloop.exit }
|
91
|
+
evloop.main
|
92
|
+
|
93
|
+
# Create another event loop, and attempt to connect to the socket.
|
94
|
+
connected = false
|
95
|
+
error = false
|
96
|
+
evloop = ScripTTY::Net::EventLoop.new
|
97
|
+
evloop.connect(bind_addr) { |c|
|
98
|
+
c.on_connect { |conn| evloop.exit }
|
99
|
+
c.on_connect_error { |e|
|
100
|
+
assert_match /^java\.net\.ConnectException: Connection refused/, e.message
|
101
|
+
error = true
|
102
|
+
}
|
103
|
+
}
|
104
|
+
evloop.main
|
105
|
+
assert error, "sockets should be closed when event loop exits"
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_simple_echo_server
|
109
|
+
server_log = []
|
110
|
+
client_log = []
|
111
|
+
timeout_log = []
|
112
|
+
evloop = ScripTTY::Net::EventLoop.new
|
113
|
+
bytes_received = ""
|
114
|
+
echo_addr = evloop.on_accept(['localhost', 0]) { |conn|
|
115
|
+
conn.on_close { server_log << :echo_closed }
|
116
|
+
conn.on_receive_bytes { |bytes| bytes_received += bytes; conn.write(bytes) }
|
117
|
+
}.local_address
|
118
|
+
bytes_to_send = "Hello, world!".split("")
|
119
|
+
evloop.on_connect(echo_addr) { |conn|
|
120
|
+
client_log << :client_open
|
121
|
+
conn.on_close {
|
122
|
+
client_log << :client_close
|
123
|
+
evloop.exit
|
124
|
+
}
|
125
|
+
write_next = Proc.new {
|
126
|
+
if bytes_to_send.empty?
|
127
|
+
client_log << :client_close_graceful
|
128
|
+
conn.close
|
129
|
+
else
|
130
|
+
client_log << :client_write unless client_log.last == :client_write
|
131
|
+
conn.write(bytes_to_send.shift, &write_next)
|
132
|
+
end
|
133
|
+
}
|
134
|
+
write_next.call
|
135
|
+
}
|
136
|
+
evloop.timer(5, :daemon => true) { timeout_log << :TIMEOUT; evloop.exit } # Set 5-second hard timeout for this test
|
137
|
+
evloop.main
|
138
|
+
|
139
|
+
assert_equal "Hello, world!", bytes_received
|
140
|
+
|
141
|
+
expected_logs = {
|
142
|
+
:client => [:client_open, :client_write, :client_close_graceful, :client_close],
|
143
|
+
:server => [:echo_closed],
|
144
|
+
:timeout => []
|
145
|
+
}
|
146
|
+
|
147
|
+
actual_logs = {
|
148
|
+
:client => client_log,
|
149
|
+
:server => server_log,
|
150
|
+
:timeout => timeout_log,
|
151
|
+
}
|
152
|
+
|
153
|
+
assert_equal expected_logs, actual_logs
|
154
|
+
end
|
155
|
+
|
156
|
+
# Start two sockets and make them talk to each other
|
157
|
+
def test_chatter
|
158
|
+
evloop = ScripTTY::Net::EventLoop.new
|
159
|
+
expected_logs = {}
|
160
|
+
|
161
|
+
alice_done = bob_done = false # XXX - We should do graceful TCP shutdown here instead.
|
162
|
+
|
163
|
+
# Alice
|
164
|
+
expected_logs[:alice] = [ "accepted", "said hello", "received", "said goodbye", "closed" ]
|
165
|
+
alice_log = []
|
166
|
+
alice_addr = evloop.on_accept(["localhost", 0]) { |conn|
|
167
|
+
alice_log << "accepted"
|
168
|
+
conn.write("Hello, my name is Alice. What is your name?\n") { alice_log << "said hello" }
|
169
|
+
buffer = ""
|
170
|
+
conn.on_receive_bytes { |bytes|
|
171
|
+
alice_log << "received" unless alice_log.last == "received"
|
172
|
+
buffer += bytes
|
173
|
+
if buffer =~ /\A(My name is ([^.]*)\.)$/
|
174
|
+
name = $2
|
175
|
+
buffer = "" # this would be buggy if we wanted to do more than this
|
176
|
+
conn.write("Goodbye, #{name}!\n") { alice_log << "said goodbye"; conn.close }
|
177
|
+
end
|
178
|
+
}
|
179
|
+
conn.on_close { alice_log << "closed"; alice_done = true; evloop.exit if bob_done }
|
180
|
+
}.local_address
|
181
|
+
|
182
|
+
# Bob
|
183
|
+
expected_logs[:bob] = [ "connected", "received", "said name", "received", "closed" ]
|
184
|
+
bob_log = []
|
185
|
+
evloop.on_connect(alice_addr) { |conn|
|
186
|
+
bob_log << "connected"
|
187
|
+
buffer = ""
|
188
|
+
conn.on_receive_bytes { |bytes|
|
189
|
+
bob_log << "received" unless bob_log.last == "received"
|
190
|
+
buffer += bytes
|
191
|
+
if buffer =~ /What is your name\?/
|
192
|
+
buffer = "" # this would be buggy if we wanted to do more than this
|
193
|
+
conn.write("My name is Bob.\n") { bob_log << "said name" }
|
194
|
+
end
|
195
|
+
}
|
196
|
+
conn.on_close { bob_log << "closed"; bob_done = true; evloop.exit if alice_done }
|
197
|
+
}
|
198
|
+
|
199
|
+
# Set 5-second hard timeout for this test
|
200
|
+
timeout = false
|
201
|
+
expected_logs[:timeout] = false
|
202
|
+
evloop.timer(5, :daemon => true) { timeout = true; evloop.exit }
|
203
|
+
|
204
|
+
# Execute
|
205
|
+
evloop.main
|
206
|
+
|
207
|
+
# Assertions
|
208
|
+
assert_equal(expected_logs, {:timeout => timeout, :alice => alice_log, :bob => bob_log}, "logs not what was expected")
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_run_empty
|
212
|
+
net = ScripTTY::Net::EventLoop.new
|
213
|
+
net.main
|
214
|
+
end
|
215
|
+
|
216
|
+
# Test ConnectionWrapper#local_address and ConnectionWrapper#remote_address
|
217
|
+
def test_local_and_remote_address
|
218
|
+
evloop = ScripTTY::Net::EventLoop.new
|
219
|
+
|
220
|
+
client_log = StringIO.new
|
221
|
+
server_log = StringIO.new
|
222
|
+
timeout_log = StringIO.new
|
223
|
+
#client_log = server_log = timeout_log = $stdout # for debugging
|
224
|
+
|
225
|
+
server_local_addr = nil
|
226
|
+
server_remote_addr = nil
|
227
|
+
server = evloop.listen(['localhost', 0])
|
228
|
+
server_addr = server.local_address
|
229
|
+
server.on_accept { |conn|
|
230
|
+
server_log.puts "server_accept"
|
231
|
+
conn.on_close { server_log.puts "server_conn_close" }
|
232
|
+
server_local_addr = conn.local_address
|
233
|
+
server_remote_addr = conn.remote_address
|
234
|
+
conn.close
|
235
|
+
server.close
|
236
|
+
}
|
237
|
+
server.on_close { server_log.puts "server_close" }
|
238
|
+
|
239
|
+
client_local_addr = nil
|
240
|
+
client_remote_addr = nil
|
241
|
+
client = evloop.connect(server_addr)
|
242
|
+
client.on_connect { |conn|
|
243
|
+
client_log.puts "client_connect"
|
244
|
+
client_local_addr = conn.local_address
|
245
|
+
client_remote_addr = conn.remote_address
|
246
|
+
conn.close
|
247
|
+
}
|
248
|
+
client.on_close { client_log.puts "client_close" }
|
249
|
+
|
250
|
+
# Set 5-second hard timeout for this test
|
251
|
+
evloop.timer(5, :daemon => true) { timeout_log.puts "TIMEOUT"; evloop.exit }
|
252
|
+
|
253
|
+
evloop.main
|
254
|
+
|
255
|
+
expected_logs = {}
|
256
|
+
expected_logs[:server] = %w( server_accept server_close server_conn_close )
|
257
|
+
expected_logs[:client] = %w( client_connect client_close )
|
258
|
+
expected_logs[:timeout] = []
|
259
|
+
|
260
|
+
actual_logs = {
|
261
|
+
:server => server_log.string.split("\n"),
|
262
|
+
:client => client_log.string.split("\n"),
|
263
|
+
:timeout => timeout_log.string.split("\n"),
|
264
|
+
}
|
265
|
+
|
266
|
+
assert_equal expected_logs, actual_logs
|
267
|
+
|
268
|
+
# Checl that the addresses are what we expect
|
269
|
+
assert_equal server_addr, server_local_addr, "server_addr should== server_local_addr"
|
270
|
+
assert_equal server_addr, client_remote_addr, "server_addr should== client_remote_addr"
|
271
|
+
assert_equal server_remote_addr, client_local_addr, "server_remote_addr should== client_local_addr"
|
272
|
+
end
|
273
|
+
|
274
|
+
# Regression test: The event loop should exit on its own when a
|
275
|
+
# connection is closed, even if there are no on_close or on_receive_bytes
|
276
|
+
# callbacks defined.
|
277
|
+
def test_client_and_server_exit_with_minimal_callbacks
|
278
|
+
evloop = ScripTTY::Net::EventLoop.new
|
279
|
+
server = evloop.listen(['localhost', 0])
|
280
|
+
#server.on_accept { |conn| server.close } # stop accepting new connections, but keep the current connection open
|
281
|
+
server.on_accept { |conn| server.close; conn.close } # stop accepting new connections, and close the connection
|
282
|
+
|
283
|
+
client = evloop.connect(server.local_address)
|
284
|
+
|
285
|
+
# Timeout
|
286
|
+
timeout = false
|
287
|
+
evloop.timer(5, :daemon => true) { timeout = true; evloop.exit }
|
288
|
+
|
289
|
+
evloop.main
|
290
|
+
|
291
|
+
assert !timeout, "TIMEOUT"
|
292
|
+
end
|
293
|
+
|
294
|
+
def test_accept_multiple
|
295
|
+
evloop = ScripTTY::Net::EventLoop.new
|
296
|
+
|
297
|
+
# Listen on two different ports (chosen by the operating system)
|
298
|
+
alice_accepted = []
|
299
|
+
alice_addrs = evloop.on_accept([['localhost', 0], ['localhost', 0]], :multiple => true) { |conn|
|
300
|
+
alice_accepted << conn.local_address
|
301
|
+
conn.on_close { conn.master.exit if alice_accepted.length == 2 }
|
302
|
+
conn.close
|
303
|
+
}.map{|lw| lw.local_address}
|
304
|
+
|
305
|
+
bob_connected = nil
|
306
|
+
bob_addr = evloop.on_connect(alice_addrs[0]) { |conn|
|
307
|
+
bob_connected = conn.remote_address
|
308
|
+
conn.close
|
309
|
+
}.local_address
|
310
|
+
|
311
|
+
carol_connected = nil
|
312
|
+
carol_addr = evloop.on_connect(alice_addrs[1]) { |conn|
|
313
|
+
carol_connected = conn.remote_address
|
314
|
+
conn.close
|
315
|
+
}.local_address
|
316
|
+
|
317
|
+
# Execute
|
318
|
+
evloop.main
|
319
|
+
|
320
|
+
assert_equal alice_addrs.sort, alice_accepted.sort, "alice should accepted a connection on both ports"
|
321
|
+
assert_equal alice_addrs[0], bob_connected, "bob should connect to alice's first port"
|
322
|
+
assert_equal alice_addrs[1], carol_connected, "carol should connect to alice's second port"
|
323
|
+
end
|
324
|
+
|
325
|
+
def test_graceful_close
|
326
|
+
sequence = []
|
327
|
+
evloop = ScripTTY::Net::EventLoop.new
|
328
|
+
alice = evloop.listen(['localhost', 0])
|
329
|
+
alice.on_accept { |conn|
|
330
|
+
alice.close
|
331
|
+
conn.on_receive_bytes { |bytes| sequence << :alice_received }
|
332
|
+
conn.on_close { sequence << :alice_closed }
|
333
|
+
}
|
334
|
+
alice.on_close { sequence << :server_closed }
|
335
|
+
|
336
|
+
bob = evloop.connect(alice.local_address)
|
337
|
+
bob.on_connect { |conn|
|
338
|
+
conn.on_close { sequence << :bob_closed }
|
339
|
+
conn.write("Hello world!") {
|
340
|
+
sequence << :bob_wrote
|
341
|
+
evloop.timer(0.2) {
|
342
|
+
sequence << :bob_closing; conn.close
|
343
|
+
}
|
344
|
+
}
|
345
|
+
}
|
346
|
+
|
347
|
+
evloop.timer(5, :daemon => true) { sequence << :TIMEOUT; evloop.exit }
|
348
|
+
|
349
|
+
evloop.main
|
350
|
+
# :server_closed and :bob_wrote might happen in reverse order. If that happens, make this assertion smarter.
|
351
|
+
assert_equal [:server_closed, :bob_wrote, :alice_received, :bob_closing, :alice_closed, :bob_closed], sequence
|
352
|
+
end
|
353
|
+
|
354
|
+
# The timer should work, and not be too fast (or too slow).
|
355
|
+
def test_timer_works
|
356
|
+
evloop = ScripTTY::Net::EventLoop.new
|
357
|
+
t0 = Time.now
|
358
|
+
evloop.timer(1) { true }
|
359
|
+
evloop.main
|
360
|
+
t1 = Time.now
|
361
|
+
delta = t1 - t0
|
362
|
+
assert delta >= 1.0, "Timeout too short: #{delta.inspect}" # This should never fail
|
363
|
+
assert delta < 5.0, "warning: Timeout too long: #{delta.inspect}" # This might fail on a slow machine
|
364
|
+
end
|
365
|
+
|
366
|
+
# Daemon timers should not prevent the main loop from exiting
|
367
|
+
def test_daemon_timers
|
368
|
+
evloop = ScripTTY::Net::EventLoop.new
|
369
|
+
user_timer_fired = false
|
370
|
+
daemon_timer_fired = false
|
371
|
+
t0 = Time.now
|
372
|
+
evloop.timer(0.002) { user_timer_fired = true }
|
373
|
+
evloop.timer(10, :daemon => true) { daemon_timer_fired = true ; evloop.exit }
|
374
|
+
evloop.main
|
375
|
+
t1 = Time.now
|
376
|
+
delta = t1 - t0
|
377
|
+
assert user_timer_fired, "user timer should have fired"
|
378
|
+
assert !daemon_timer_fired, "daemon timer should not have fired"
|
379
|
+
end
|
380
|
+
|
381
|
+
# A timer with a zero-second delay should get executed immediately.
|
382
|
+
def test_timer_with_zero_delay
|
383
|
+
result = []
|
384
|
+
evloop = ScripTTY::Net::EventLoop.new
|
385
|
+
evloop.timer(0) { result << :t }
|
386
|
+
evloop.main
|
387
|
+
assert_equal [:t], result
|
388
|
+
end
|
389
|
+
|
390
|
+
# Timers should be able to be cancelled.
|
391
|
+
def test_timer_cancel
|
392
|
+
result = []
|
393
|
+
evloop = ScripTTY::Net::EventLoop.new
|
394
|
+
b = nil
|
395
|
+
a = evloop.timer(0.00001) { result << :a; b.cancel }
|
396
|
+
b = evloop.timer(0.00002) { result << :b }
|
397
|
+
c = evloop.timer(0.00003) { result << :c }
|
398
|
+
evloop.main
|
399
|
+
assert_equal [:a, :c], result
|
400
|
+
end
|
401
|
+
end # defined?(Java)
|
402
|
+
end # class
|