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,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
|