scriptty 0.5.0-java
Sign up to get free protection for your applications and to get access to all the features.
- 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,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
|
data/test/cursor_test.rb
ADDED
@@ -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
|