scriptty 0.5.0-java

Sign up to get free protection for your applications and to get access to all the features.
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,123 @@
1
+ # = Tests for the Capture App
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
+ require 'scriptty/util/transcript/reader'
21
+ require 'tempfile'
22
+
23
+ class CaptureAppTest < Test::Unit::TestCase
24
+ LISTEN_PORT = 46457 # Randomly-chosen port; change if necessary
25
+
26
+ def setup
27
+ require 'scriptty/apps/capture_app'
28
+ require 'scriptty/net/event_loop'
29
+ raise "CaptureAppTest disabled" # FIXME
30
+ end
31
+
32
+ def test_basic
33
+ app = nil
34
+ app_thread = nil
35
+ Tempfile.open("test") do |transcript_file|
36
+ # Create an event loop
37
+ evloop = ScripTTY::Net::EventLoop.new
38
+
39
+ # Create an echo server
40
+ echo_server = evloop.listen(['localhost', 0])
41
+ echo_server.on_accept { |conn|
42
+ conn.on_receive_bytes { |bytes| conn.write(bytes) }
43
+ }
44
+ echo_addr = "[#{echo_server.local_address[0]}]:#{echo_server.local_address[1]}" # format as [HOST]:PORT
45
+
46
+ # Start the capture app
47
+ app = ScripTTY::Apps::CaptureApp.new(%W( -l localhost:#{LISTEN_PORT} -c #{echo_addr} -o #{transcript_file.path} ))
48
+ app_thread = Thread.new { app.main }
49
+ sleep 2 # Wait for the application to bind the socket
50
+
51
+ # Connect to the capture app, and play "ping-pong" with the echo server
52
+ # via the capture app: Send one byte at a time, then wait for it to come
53
+ # back before sending the next byte.
54
+ bytes_to_send = "Hello".split("")
55
+ bytes_received = ""
56
+ evloop.on_connect(['localhost', LISTEN_PORT]) { |conn|
57
+ conn.on_close { evloop.exit }
58
+ write_next = nil
59
+ conn.on_receive_bytes { |bytes|
60
+ bytes_received += bytes
61
+ #puts "RECEIVED"
62
+ write_next.call
63
+ }
64
+ write_next = Proc.new {
65
+ unless bytes_to_send.empty?
66
+ #puts "WRITING"
67
+ conn.write(bytes_to_send.shift)
68
+ else
69
+ #puts "CLOSING"
70
+ conn.close
71
+ end
72
+ }
73
+ write_next.call
74
+ }
75
+ evloop.main
76
+
77
+ sleep 2 # Wait for the application to finish
78
+ app.exit
79
+
80
+ # Expected transcript
81
+ expected_transcript = [
82
+ [:client_open, "127.0.0.1", nil],
83
+ [:server_open, "127.0.0.1", nil],
84
+ [:from_client, "H"],
85
+ [:from_server, "H"],
86
+ [:from_client, "e"],
87
+ [:from_server, "e"],
88
+ [:from_client, "l"],
89
+ [:from_server, "l"],
90
+ [:from_client, "l"],
91
+ [:from_server, "l"],
92
+ [:from_client, "o"],
93
+ [:from_server, "o"],
94
+ [:client_close, "Client connection closed"],
95
+ [:server_close, "Server connection closed"],
96
+ ]
97
+
98
+ # Read transcript
99
+ reader = ScripTTY::Util::Transcript::Reader.new
100
+ raw_transcript = ""
101
+ actual_transcript = []
102
+ until transcript_file.eof?
103
+ line = transcript_file.readline
104
+ raw_transcript << line
105
+ timestamp, type, args = reader.parse_line(line)
106
+ actual_transcript << [type] + args
107
+ end
108
+
109
+ # Canonicalize the transcript for this test: Replace ports with nil
110
+ actual_transcript.each {|t|
111
+ if [:client_open, :server_open].include?(t[0])
112
+ assert_kind_of Integer, t[2], "port should be an Integer"
113
+ t[2] = nil
114
+ end
115
+ }
116
+
117
+ assert_equal expected_transcript, actual_transcript, raw_transcript
118
+ end
119
+ ensure
120
+ app.exit if app
121
+ app_thread.join if app_thread
122
+ end
123
+ end
@@ -0,0 +1,118 @@
1
+ # = Tests for the TranscriptParseApp
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
+ require 'scriptty/apps/transcript_parse_app'
21
+ require 'scriptty/util/transcript/reader'
22
+ require 'scriptty/util/transcript/writer'
23
+ require 'tempfile'
24
+
25
+ class TranscriptParseAppTest < Test::Unit::TestCase
26
+ def test_basic
27
+ input_events = [
28
+ [0.0, :client_open, '127.0.0.1', 777],
29
+ [0.1, :server_open, '127.0.0.1', 555],
30
+ [0.2, :from_client, "Hello\r\n"],
31
+ [1.0, :from_server, "\036~xx\001xHello\020"],
32
+ [1.1, :from_server, "\005\007\n\020"],
33
+ [2.0, :server_close, "Closed"],
34
+ [2.1, :client_close, "Closed"],
35
+ ]
36
+ # XXX - This isn't quite right (see below) -- the
37
+ # [:server_parsed,".","Hello"] comes out too late and with the wrong
38
+ # timestamp.
39
+ expected_output = [
40
+ [0.0, :client_open, '127.0.0.1', 777],
41
+ [0.1, :server_open, '127.0.0.1', 555],
42
+ [0.2, :from_client, "Hello\r\n"],
43
+ [1.0, :from_server, "\036~xx\001xHello\020"],
44
+ [1.0, :server_parsed, "t_proprietary_escape", "\036~xx\001x"],
45
+ #[1.0, :server_parsed, ".", "Hello"], # XXX - ideally should be here
46
+ [1.1, :from_server, "\005\007\n\020"],
47
+ [1.1, :server_parsed, ".", "Hello"], # XXX - actually is here
48
+ [1.1, :server_parsed, "t_write_window_address", "\020\005\007"],
49
+ [1.1, :server_parsed, "t_new_line", "\n"],
50
+ [2.0, :server_close, "Closed"],
51
+ [2.1, :client_close, "Closed"],
52
+ ]
53
+
54
+ app = nil
55
+ Tempfile.open("testin") do |tf_input|
56
+ Tempfile.open("testout") do |tf_output|
57
+ tf_output.close(false)
58
+
59
+ # Create the test transcript
60
+ writer = ScripTTY::Util::Transcript::Writer.new(tf_input)
61
+ input_events.each do |args|
62
+ timestamp, type = args.shift(2)
63
+ writer.override_timestamp = timestamp
64
+ writer.send(type, *args)
65
+ end
66
+ writer.close
67
+
68
+ ###
69
+ # Run the app (with --keep)
70
+ app = ScripTTY::Apps::TranscriptParseApp.new(%W( -o #{tf_output.path} --keep -t dg410 #{tf_input.path} ))
71
+ app.main
72
+
73
+ # Read transcript
74
+ actual_transcript = load_transcript(tf_output.path)
75
+
76
+ # Compare
77
+ assert_equal_transcripts expected_output, actual_transcript
78
+
79
+ ####
80
+ # Run the app (without --keep)
81
+ app = ScripTTY::Apps::TranscriptParseApp.new(%W( -o #{tf_output.path} -t dg410 #{tf_input.path} ))
82
+ app.main
83
+
84
+ # Read transcript
85
+ actual_transcript = load_transcript(tf_output.path)
86
+
87
+ # Strip :from_server from expected output
88
+ expected_output.reject!{|e| e[1] == :from_server}
89
+
90
+ # Compare
91
+ assert_equal_transcripts expected_output, actual_transcript
92
+ end
93
+ end
94
+ end
95
+
96
+ private
97
+ def load_transcript(path)
98
+ reader = ScripTTY::Util::Transcript::Reader.new
99
+ raw_transcript = ""
100
+ transcript = []
101
+ File.open(path, "r") do |transcript_file|
102
+ until transcript_file.eof?
103
+ line = transcript_file.readline
104
+ raw_transcript << line
105
+ timestamp, type, args = reader.parse_line(line)
106
+ transcript << [timestamp, type] + args
107
+ end
108
+ end
109
+ transcript
110
+ end
111
+
112
+ def assert_equal_transcripts(expected_transcript, actual_transcript)
113
+ expected_transcript.zip(actual_transcript).each_with_index do |(expected, actual), i|
114
+ assert_equal expected, actual, "line #{i+1}: should be equal"
115
+ end
116
+ assert_equal expected_transcript, actual_transcript, "transcripts should be equal"
117
+ end
118
+ end
@@ -0,0 +1,51 @@
1
+ # = Tests for ScripTTY::Cursor
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
+ require 'scriptty/cursor'
21
+
22
+ class CursorTest < Test::Unit::TestCase
23
+ def test_row_column
24
+ c = ScripTTY::Cursor.new
25
+ assert_equal 0, c.row, "initial row value"
26
+ assert_equal 0, c.column, "initial column value"
27
+ assert_equal [0,0], c.pos, "initial pos"
28
+ c.row += 1
29
+ assert_equal 1, c.row, "row after adding 1 to row"
30
+ assert_equal 0, c.column, "column after adding 1 to row"
31
+ assert_equal [1,0], c.pos, "pos after adding 1 to row"
32
+ c.pos = [5,8]
33
+ assert_equal 5, c.row, "row after setting pos to [5,8]"
34
+ assert_equal 8, c.column, "column after setting pos to [5,8]"
35
+ assert_equal [5,8], c.pos, "pos after setting pos to [5,8]"
36
+ end
37
+
38
+ # Modifying the array returned by Cursor#pos should not modify the cursor
39
+ def test_pos_should_return_new_array
40
+ c = ScripTTY::Cursor.new
41
+ assert_equal 0, c.row, "initial row value"
42
+ assert_equal 0, c.column, "initial column value"
43
+ assert_equal [0,0], c.pos, "initial pos"
44
+ a = c.pos
45
+ a[0] += 1
46
+ a[1] += 1
47
+ assert_equal 0, c.row, "row should not change"
48
+ assert_equal 0, c.column, "column should not change"
49
+ assert_equal [0,0], c.pos, "pos should not change"
50
+ end
51
+ end
@@ -0,0 +1,220 @@
1
+ # = Tests for ScripTTY::Util::FSM::DefinitionParser
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
+ require 'scriptty/util/fsm/definition_parser'
21
+
22
+ class FSMDefinitionParserTest < Test::Unit::TestCase
23
+ # Test that the parser can parse empty definitions, comments, whitespace, etc.
24
+ def test_empty_definitions
25
+ assert_equal [], parse(""), 'empty string'
26
+ assert_equal [], parse("\t "), 'only whitespace'
27
+ assert_equal [], parse("# This is a comment"), 'comment'
28
+ assert_equal [], parse(" # This is a comment"), 'comment with leading whitespace'
29
+ assert_equal [], parse("\n\n\n"), 'blank lines'
30
+ assert_equal [], parse("\n\n\n # blah\n"), 'blank lines with comment'
31
+ end
32
+
33
+ def test_single_quotes_must_hold_single_characters
34
+ definition = <<-EOF
35
+ 'spam' => foo # correct form is: "spam" => foo
36
+ EOF
37
+ assert_raise ArgumentError do
38
+ parse(definition)
39
+ end
40
+ end
41
+
42
+ def test_flat_fsm
43
+ expected = [
44
+ {:state => 1, :input => "a", :next_state => 1, :event_name => "literal_a"},
45
+ {:state => 1, :input => "b", :next_state => 1, :event_name => "literal_b"},
46
+ {:state => 1, :input => :other, :next_state => 1, :event_name => "other"},
47
+ ]
48
+ definition = <<-EOF
49
+ 'a' => literal_a
50
+ 'b' => literal_b
51
+ * => other
52
+ EOF
53
+ assert_equal normalized(expected), normalized(parse(definition))
54
+ end
55
+
56
+ def test_nested_rules
57
+ expected = [
58
+ {:state => 1, :input => "a", :next_state => 2, :event_name => nil},
59
+ {:state => 2, :input => "b", :next_state => 1, :event_name => "handle_a_b"},
60
+ {:state => 2, :input => "c", :next_state => 1, :event_name => "handle_a_c"},
61
+ {:state => 2, :input => :other, :next_state => 1, :event_name => "handle_a_other"},
62
+ {:state => 1, :input => "b", :next_state => 1, :event_name => "handle_b"},
63
+ {:state => 1, :input => "c", :next_state => 3, :event_name => nil},
64
+ {:state => 3, :input => "a", :next_state => 1, :event_name => "handle_c_a"},
65
+ ]
66
+ definition = <<-EOF
67
+ 'a' => {
68
+ 'b' => handle_a_b
69
+ 'c' => handle_a_c
70
+ * => handle_a_other
71
+ }
72
+ 'b' => handle_b
73
+ 'c' => {
74
+ 'a' => handle_c_a
75
+ }
76
+ EOF
77
+ assert_equal normalized(expected), normalized(parse(definition))
78
+ end
79
+
80
+ def test_nested_rule_with_comments
81
+ expected = [
82
+ {:state => 1, :input => "a", :next_state => 2, :event_name => nil},
83
+ {:state => 2, :input => "b", :next_state => 1, :event_name => "ab"},
84
+ ]
85
+ definition = <<-EOF
86
+ 'a' => { # comment 1
87
+ 'b' => ab # comment 2
88
+ } # comment 3
89
+ EOF
90
+ assert_equal normalized(expected), normalized(parse(definition))
91
+ end
92
+
93
+ def test_rested_rule_with_intermediate_event_name
94
+ expected = [
95
+ {:state => 1, :input => "a", :next_state => 2, :event_name => "intermediate"},
96
+ {:state => 2, :input => "b", :next_state => 1, :event_name => "ab"},
97
+ ]
98
+ definition = <<-EOF
99
+ 'a' => intermediate => {
100
+ 'b' => ab
101
+ }
102
+ EOF
103
+ assert_equal normalized(expected), normalized(parse(definition))
104
+ end
105
+
106
+ def test_string_inputs
107
+ expected = [
108
+ {:state => 1, :input => "spam", :next_state => 2, :event_name => nil},
109
+ {:state => 2, :input => "spam", :next_state => 3, :event_name => nil},
110
+ {:state => 3, :input => "spam", :next_state => 4, :event_name => nil},
111
+ {:state => 3, :input => :other, :next_state => 1, :event_name => "not_enough_spam"},
112
+ {:state => 4, :input => "egg", :next_state => 1, :event_name => "rations"},
113
+ ]
114
+ definition = <<-EOF
115
+ "spam" => {
116
+ "spam" => {
117
+ "spam" => {
118
+ "egg" => rations
119
+ }
120
+ * => not_enough_spam
121
+ }
122
+ }
123
+ EOF
124
+ assert_equal normalized(expected), normalized(parse(definition))
125
+ end
126
+
127
+ # Tabs are not allowed inside quotes. If you want to match tabs, escape them.
128
+ def test_disallow_tabs_inside_quotes
129
+ assert_raise ArgumentError do
130
+ parse("\"\t\" => foo\n")
131
+ end
132
+ assert_raise ArgumentError do
133
+ parse("'\t' => foo\n")
134
+ end
135
+ assert_nothing_raised do
136
+ parse("'\\t' => foo\n")
137
+ end
138
+ end
139
+
140
+ # We should get an error on duplicate rules
141
+ def test_error_on_conflicting_rule
142
+ e = nil
143
+ assert_raise ArgumentError do
144
+ begin
145
+ parse(<<-EOF)
146
+ # Conflicts aren't necessarily obvious
147
+ 'b' => foo
148
+ [a-c] => foo
149
+ EOF
150
+ rescue => e
151
+ raise
152
+ end
153
+ end
154
+ assert_match /^rule conflict/, e.message
155
+ end
156
+
157
+ # Single-state test of character classes
158
+ def test_character_classes_1state
159
+ expected = [
160
+ {:state => 1, :input => "0", :next_state => 1, :event_name => "foo"},
161
+ {:state => 1, :input => "1", :next_state => 1, :event_name => "foo"},
162
+ {:state => 1, :input => "2", :next_state => 1, :event_name => "foo"},
163
+ {:state => 1, :input => "a", :next_state => 1, :event_name => "foo"},
164
+ {:state => 1, :input => "b", :next_state => 1, :event_name => "foo"},
165
+ {:state => 1, :input => "c", :next_state => 1, :event_name => "foo"},
166
+ {:state => 1, :input => "x", :next_state => 1, :event_name => "foo"},
167
+ ]
168
+ definition = <<-EOF
169
+ [0-2a-cx] => foo
170
+ EOF
171
+ assert_equal normalized(expected), normalized(parse(definition))
172
+ end
173
+
174
+ # Test "not" character class
175
+ def test_character_classes_1state_exclude
176
+ expected = []
177
+ (0..255).each { |i|
178
+ next if i == 0x61
179
+ expected << {:state => 1, :input => i.chr, :next_state => 1, :event_name => "foo"}
180
+ }
181
+ definition = <<-EOF
182
+ [^a] => foo
183
+ EOF
184
+ assert_equal normalized(expected), normalized(parse(definition))
185
+ end
186
+
187
+ # Multi-state test of character classes
188
+ def test_character_classes_nested
189
+ expected = [
190
+ {:state => 1, :input => "0", :next_state => 2, :event_name => nil},
191
+ {:state => 1, :input => "1", :next_state => 2, :event_name => nil},
192
+ {:state => 1, :input => "2", :next_state => 2, :event_name => nil},
193
+ {:state => 2, :input => "\x1e", :next_state => 1, :event_name => "foo"},
194
+ {:state => 2, :input => "\x1f", :next_state => 1, :event_name => "foo"},
195
+ {:state => 2, :input => " ", :next_state => 1, :event_name => "foo"},
196
+ {:state => 2, :input => "!", :next_state => 1, :event_name => "foo"},
197
+ {:state => 2, :input => :other, :next_state => 1, :event_name => "bar"},
198
+ ]
199
+ definition = <<-EOF
200
+ [0-2] => {
201
+ [\x1e-!] => foo
202
+ * => bar
203
+ }
204
+ EOF
205
+ assert_equal normalized(expected), normalized(parse(definition))
206
+ end
207
+
208
+ private
209
+
210
+ def parse(definition)
211
+ ::ScripTTY::Util::FSM::DefinitionParser.new.parse(definition)
212
+ end
213
+
214
+ def normalized(table)
215
+ # Set event_name to nil if missing
216
+ table = table.map{|r| r = r.dup; r[:event_name] ||= nil; r}
217
+ # Sort rows
218
+ table.sort{ |a,b| [a[:state], a[:input].to_s] <=> [b[:state], b[:input].to_s] }
219
+ end
220
+ end