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.
Files changed (69) hide show
  1. data/.gitattributes +1 -0
  2. data/.gitignore +3 -0
  3. data/COPYING +674 -0
  4. data/COPYING.LESSER +165 -0
  5. data/README.rdoc +31 -0
  6. data/Rakefile +49 -0
  7. data/VERSION +1 -0
  8. data/bin/scriptty-capture +5 -0
  9. data/bin/scriptty-dump-screens +4 -0
  10. data/bin/scriptty-replay +5 -0
  11. data/bin/scriptty-term-test +4 -0
  12. data/bin/scriptty-transcript-parse +4 -0
  13. data/examples/captures/xterm-overlong-line-prompt.bin +9 -0
  14. data/examples/captures/xterm-vim-session.bin +262 -0
  15. data/examples/demo-capture.rb +19 -0
  16. data/examples/telnet-nego.rb +55 -0
  17. data/lib/scriptty/apps/capture_app/console.rb +104 -0
  18. data/lib/scriptty/apps/capture_app/password_prompt.rb +65 -0
  19. data/lib/scriptty/apps/capture_app.rb +213 -0
  20. data/lib/scriptty/apps/dump_screens_app.rb +166 -0
  21. data/lib/scriptty/apps/replay_app.rb +229 -0
  22. data/lib/scriptty/apps/term_test_app.rb +124 -0
  23. data/lib/scriptty/apps/transcript_parse_app.rb +143 -0
  24. data/lib/scriptty/cursor.rb +39 -0
  25. data/lib/scriptty/exception.rb +38 -0
  26. data/lib/scriptty/expect.rb +392 -0
  27. data/lib/scriptty/multiline_buffer.rb +192 -0
  28. data/lib/scriptty/net/event_loop.rb +610 -0
  29. data/lib/scriptty/screen_pattern/generator.rb +398 -0
  30. data/lib/scriptty/screen_pattern/parser.rb +558 -0
  31. data/lib/scriptty/screen_pattern.rb +104 -0
  32. data/lib/scriptty/term/dg410/dg410-client-escapes.txt +37 -0
  33. data/lib/scriptty/term/dg410/dg410-escapes.txt +82 -0
  34. data/lib/scriptty/term/dg410/parser.rb +162 -0
  35. data/lib/scriptty/term/dg410.rb +489 -0
  36. data/lib/scriptty/term/xterm/xterm-escapes.txt +73 -0
  37. data/lib/scriptty/term/xterm.rb +661 -0
  38. data/lib/scriptty/term.rb +40 -0
  39. data/lib/scriptty/util/fsm/definition_parser.rb +111 -0
  40. data/lib/scriptty/util/fsm/scriptty_fsm_definition.treetop +189 -0
  41. data/lib/scriptty/util/fsm.rb +177 -0
  42. data/lib/scriptty/util/transcript/reader.rb +96 -0
  43. data/lib/scriptty/util/transcript/writer.rb +111 -0
  44. data/test/apps/capture_app_test.rb +123 -0
  45. data/test/apps/transcript_parse_app_test.rb +118 -0
  46. data/test/cursor_test.rb +51 -0
  47. data/test/fsm_definition_parser_test.rb +220 -0
  48. data/test/fsm_test.rb +322 -0
  49. data/test/multiline_buffer_test.rb +275 -0
  50. data/test/net/event_loop_test.rb +402 -0
  51. data/test/screen_pattern/generator_test.rb +408 -0
  52. data/test/screen_pattern/parser_test/explicit_cursor_pattern.txt +14 -0
  53. data/test/screen_pattern/parser_test/explicit_fields.txt +22 -0
  54. data/test/screen_pattern/parser_test/multiple_patterns.txt +42 -0
  55. data/test/screen_pattern/parser_test/simple_pattern.txt +14 -0
  56. data/test/screen_pattern/parser_test/truncated_heredoc.txt +12 -0
  57. data/test/screen_pattern/parser_test/utf16bebom_pattern.bin +0 -0
  58. data/test/screen_pattern/parser_test/utf16lebom_pattern.bin +0 -0
  59. data/test/screen_pattern/parser_test/utf8_pattern.bin +14 -0
  60. data/test/screen_pattern/parser_test/utf8_unix_pattern.bin +14 -0
  61. data/test/screen_pattern/parser_test/utf8bom_pattern.bin +14 -0
  62. data/test/screen_pattern/parser_test.rb +266 -0
  63. data/test/term/dg410/parser_test.rb +139 -0
  64. data/test/term/xterm_test.rb +327 -0
  65. data/test/test_helper.rb +3 -0
  66. data/test/util/transcript/reader_test.rb +131 -0
  67. data/test/util/transcript/writer_test.rb +126 -0
  68. data/test.watchr +29 -0
  69. 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