ttytest2 1.1.0 → 1.3.0
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.
- checksums.yaml +4 -4
- data/README.md +36 -4
- data/images/ttytest2.png +0 -0
- data/lib/ttytest/assertions/column_assertions.rb +41 -0
- data/lib/ttytest/assertions/cursor_assertions.rb +36 -0
- data/lib/ttytest/assertions/file_assertions.rb +89 -0
- data/lib/ttytest/assertions/row_assertions.rb +144 -0
- data/lib/ttytest/assertions/screen_assertions.rb +76 -0
- data/lib/ttytest/assertions.rb +11 -318
- data/lib/ttytest/capture.rb +1 -1
- data/lib/ttytest/constants.rb +2 -1
- data/lib/ttytest/terminal.rb +21 -7
- data/lib/ttytest/tmux/driver.rb +7 -6
- data/lib/ttytest/tmux/session.rb +36 -3
- data/lib/ttytest/version.rb +1 -1
- data/lib/ttytest.rb +16 -8
- data/notes.txt +2 -2
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15e01162a84bf95dce1f5694dfc57daa77decd5c7136af567c469d4e2148daa6
|
4
|
+
data.tar.gz: e29d3aae969a70b6bb0ba1003a87b4da7e2b819c0e80595f6a9604b11c52f7a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd61cb3a8e293c928e1d3b5fcb3637273bd6a27a55ecae96ea737b1f0b6959df44c223aae366131175411320d0c185d3b3a339a128c519e43a4d75dbe8990147
|
7
|
+
data.tar.gz: 38a1a8dfa89b91b8209fb033fe2a7b3af161f4bfdc87dd07bc2f30154c1f925cf48b1c4697eef25a11773e1a65eb0422edd6811ffcef56559167709e85e63180
|
data/README.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# ttytest2
|
2
2
|
|
3
|
+
<a href="https://rubygems.org/gems/ttytest2">
|
4
|
+
<img src="images/ttytest2.png" alt="ttytest2 logo" style="width:70%; height:auto;">
|
5
|
+
</a>
|
6
|
+
|
3
7
|
ttytest2 is a user acceptance test framework for CLI & shell applications.
|
4
8
|
|
5
9
|
ttytest2 is a fork and a drop-in replacement for [ttytest](https://github.com/jhawthorn/ttytest).
|
@@ -31,6 +35,7 @@ The assertions will wait a specified amount of time (configurable, default 2 sec
|
|
31
35
|
|
32
36
|
## Usage
|
33
37
|
|
38
|
+
* Download the [gem](https://rubygems.org/gems/ttytest2) here.
|
34
39
|
* More documentation available at [ttytest2 docs](https://www.rubydoc.info/gems/ttytest2).
|
35
40
|
* There are more examples in the examples folder.
|
36
41
|
|
@@ -191,8 +196,10 @@ Helper functions to make sending output easier! They use the methods above under
|
|
191
196
|
|
192
197
|
* `send_newline` # simulate hitting enter, equivalent to @tty.send_keys(%(\n))
|
193
198
|
* `send_newlines(number_of_times)` # equivalent to calling send_newline number_of_times
|
194
|
-
|
195
|
-
* `
|
199
|
+
|
200
|
+
* `send_return` # simulate hitting enter, equivalent to @tty.send_keys(%(\r))
|
201
|
+
* `send_returns(number_of_times)` # equivalent to calling send_return number_of_times
|
202
|
+
|
196
203
|
|
197
204
|
* `send_backspace` # simulate hitting backspace, equivalent to @tty.send_keys(TTYtest::BACKSPACE)
|
198
205
|
* `send_backspaces(number_of_times)` # equivalent to calling send_backspace number_of_times
|
@@ -220,6 +227,9 @@ Helper functions to make sending output easier! They use the methods above under
|
|
220
227
|
* `send_escape`
|
221
228
|
* `send_escapes(number_of_times)`
|
222
229
|
|
230
|
+
* `send_tab`
|
231
|
+
* `send_tab(number_of_times)`
|
232
|
+
|
223
233
|
### F keys?
|
224
234
|
|
225
235
|
Send F keys like F1, F2, etc. as shown below:
|
@@ -253,7 +263,7 @@ There are some commonly used keys available as constants to make interacting wit
|
|
253
263
|
TTYtest::SHIFT_ENTER # \v
|
254
264
|
TTYtest::FORM_FEED # \f or New Page NP
|
255
265
|
TTYtest::CTRLL
|
256
|
-
TTYtest::
|
266
|
+
TTYtest::RETURN # \r
|
257
267
|
TTYtest::CTRLU
|
258
268
|
TTYtest::CTRLW
|
259
269
|
TTYtest::ESCAPE # 27 decimal or ^[ or /033
|
@@ -273,7 +283,9 @@ There are some commonly used keys available as constants to make interacting wit
|
|
273
283
|
|
274
284
|
## Configurables
|
275
285
|
|
276
|
-
|
286
|
+
There are 2 main configurations for ttytest2: max_wait_time, and use_return_for_newline.
|
287
|
+
|
288
|
+
### Max wait time
|
277
289
|
|
278
290
|
Max wait time represents the amount of time in seconds that ttytest2 will keep retrying an assertion before failing.
|
279
291
|
|
@@ -287,6 +299,24 @@ You can configure max wait time as shown below.
|
|
287
299
|
@tty.assert_row(0, 'echo Hello, world') # this assertion would fail after 3 seconds
|
288
300
|
```
|
289
301
|
|
302
|
+
### Use return for newline
|
303
|
+
|
304
|
+
Use return for newline tells ttytest2 to use return ('//r') instead of newline ('//n') for methods like send_line.
|
305
|
+
|
306
|
+
Some line readers may interpreter return and newline differently, so this can be useful in those cases.
|
307
|
+
|
308
|
+
You can still send newline via send_newline when this is enabled.
|
309
|
+
|
310
|
+
You can also send return by itself with send_return.
|
311
|
+
|
312
|
+
``` ruby
|
313
|
+
@tty = TTYtest::new_terminal('', use_return_for_newline: true) # specified to use return in place of newline
|
314
|
+
|
315
|
+
@tty.send_line('hello, world!') # will have return sent after 'hello, world!'
|
316
|
+
@tty.use_return_for_newline = false
|
317
|
+
@tty.assert_row(0, 'echo Hello, world') # will have newline sent after 'echo Hello, world'
|
318
|
+
```
|
319
|
+
|
290
320
|
## Troubleshooting
|
291
321
|
|
292
322
|
You can use the method rows to get all rows of the terminal as an array, of use the method capture to get the contents of the terminal window. This can be useful when troubleshooting.
|
@@ -320,6 +350,8 @@ If you are using ttyest2 to test your CLI, using sh is easier than bash because
|
|
320
350
|
|
321
351
|
If you are using ttytest2 to test your shell, using assertions like `assert_row_like`, `assert_row_starts_with`, and `assert_row_ends_with` are going to be extremely helpful, especially if trying to test your shell in different environments or using a docker container.
|
322
352
|
|
353
|
+
Most line readers use '\n' for newline, but some may interpret it differently and expect '\r'.
|
354
|
+
|
323
355
|
## Docker
|
324
356
|
|
325
357
|
Easy to use from Docker. Add this to your dockerfile to get started.
|
data/images/ttytest2.png
ADDED
Binary file
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTYtest
|
4
|
+
# Column Assertions for ttytest2.
|
5
|
+
module ColumnAssertions
|
6
|
+
# Asserts the contents of a single column match the value expected
|
7
|
+
# @param [Integer] col_number the column (starting from 0) to test against
|
8
|
+
# @param [String] expected the expected value of the column. Any trailing whitespace is ignored
|
9
|
+
# @raise [MatchError] if the column doesn't match exactly
|
10
|
+
def assert_column(col_number, expected)
|
11
|
+
validate(col_number)
|
12
|
+
expected = expected.rstrip
|
13
|
+
|
14
|
+
rows.each_with_index do |row, i|
|
15
|
+
break if row.nil?
|
16
|
+
|
17
|
+
next if row[col_number] == expected[i]
|
18
|
+
|
19
|
+
raise MatchError,
|
20
|
+
"expected column #{col_number} to be #{expected.inspect}\n
|
21
|
+
Entire screen:\n#{self}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Asserts the specified column is empty
|
26
|
+
# @param [Integer] col_number the column (starting from 0) to test against
|
27
|
+
# @raise [MatchError] if the column isn't empty
|
28
|
+
def assert_column_is_empty(col_number)
|
29
|
+
validate(col_number)
|
30
|
+
|
31
|
+
rows.each do |row|
|
32
|
+
break if row.nil?
|
33
|
+
|
34
|
+
next if row == '' || row.length < col_number + 1 || row[col_number] == ' '
|
35
|
+
|
36
|
+
raise MatchError,
|
37
|
+
"expected column #{col_number} to be empty\nEntire screen:\n#{self}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTYtest
|
4
|
+
# Cursor Assertions for ttytest2.
|
5
|
+
module CursorAssertions
|
6
|
+
# Asserts that the cursor is in the expected position
|
7
|
+
# @param [Integer] x cursor x (row) position, starting from 0
|
8
|
+
# @param [Integer] y cursor y (column) position, starting from 0
|
9
|
+
# @raise [MatchError] if the cursor position doesn't match
|
10
|
+
def assert_cursor_position(x, y)
|
11
|
+
expected = [x, y]
|
12
|
+
actual = [cursor_x, cursor_y]
|
13
|
+
|
14
|
+
return if actual == expected
|
15
|
+
|
16
|
+
raise MatchError,
|
17
|
+
"expected cursor to be at #{expected.inspect} but was at #{get_inspection(actual)}\nEntire screen:\n#{self}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Asserts the cursor is currently visible
|
21
|
+
# @raise [MatchError] if the cursor is hidden
|
22
|
+
def assert_cursor_visible
|
23
|
+
return if cursor_visible?
|
24
|
+
|
25
|
+
raise MatchError, "expected cursor to be visible was hidden\nEntire screen:\n#{self}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Asserts the cursor is currently hidden
|
29
|
+
# @raise [MatchError] if the cursor is visible
|
30
|
+
def assert_cursor_hidden
|
31
|
+
return if cursor_hidden?
|
32
|
+
|
33
|
+
raise MatchError, "expected cursor to be hidden was visible\nEntire screen:\n#{self}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTYtest
|
4
|
+
# File Assertions for ttytest2.
|
5
|
+
module FileAssertions
|
6
|
+
# Asserts the specified file exists
|
7
|
+
# @param [String] file_path the path to the file
|
8
|
+
# @raise [MatchError] if the file is not found or is a directory/symlink
|
9
|
+
def assert_file_exists(file_path)
|
10
|
+
raise file_not_found_error(file_path) unless File.exist?(file_path)
|
11
|
+
raise file_is_dir_error(file_path) unless File.file?(file_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Asserts the specified file does not exists
|
15
|
+
# @param [String] file_path the path to the file
|
16
|
+
# @raise [MatchError] if the file is found or is a directory/symlink
|
17
|
+
def assert_file_doesnt_exist(file_path)
|
18
|
+
return unless File.exist?(file_path) || File.file?(file_path)
|
19
|
+
|
20
|
+
raise MatchError,
|
21
|
+
"File with path #{file_path} was found or is a directory when it was asserted it did not exist.\nEntire screen:\n#{self}"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Asserts the specified file contains the passed in string value
|
25
|
+
# @param [String] file_path the path to the file
|
26
|
+
# @param [String] needle the value to search for in the file
|
27
|
+
# @raise [MatchError] if the file does not contain value in variable needle
|
28
|
+
def assert_file_contains(file_path, needle)
|
29
|
+
raise file_not_found_error(file_path) unless File.exist?(file_path)
|
30
|
+
raise file_is_dir_error(file_path) unless File.file?(file_path)
|
31
|
+
|
32
|
+
file_contains = false
|
33
|
+
File.foreach(file_path) do |line|
|
34
|
+
if line.include?(needle)
|
35
|
+
file_contains = true
|
36
|
+
break
|
37
|
+
end
|
38
|
+
end
|
39
|
+
return if file_contains
|
40
|
+
|
41
|
+
raise MatchError,
|
42
|
+
"File with path #{file_path} did not contain #{needle}.\nEntire screen:\n#{self}"
|
43
|
+
end
|
44
|
+
alias assert_file_like assert_file_contains
|
45
|
+
|
46
|
+
# Asserts the specified file has the permissions specified
|
47
|
+
# @param [String] file_path the path to the file
|
48
|
+
# @param [String] permissions the expected permissions of the file (in form '644' or '775')
|
49
|
+
# @raise [MatchError] if the file has different permissions than specified
|
50
|
+
def assert_file_has_permissions(file_path, permissions)
|
51
|
+
raise file_not_found_error(file_path) unless File.exist?(file_path)
|
52
|
+
raise file_is_dir_error(file_path) unless File.file?(file_path)
|
53
|
+
|
54
|
+
file_mode = File.stat(file_path).mode
|
55
|
+
perms_octal = format('%o', file_mode)[-3...]
|
56
|
+
return if perms_octal == permissions
|
57
|
+
|
58
|
+
raise MatchError,
|
59
|
+
"File had permissions #{perms_octal}, not #{permissions} as expected.\n Entire screen:\n#{self}"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Asserts the specified file has line count specified
|
63
|
+
# @param [String] file_path the path to the file
|
64
|
+
# @param [String] expected_count the expected line count of the file
|
65
|
+
# @raise [MatchError] if the file has a different line count than specified
|
66
|
+
def assert_file_has_line_count(file_path, expected_count)
|
67
|
+
raise file_not_found_error(file_path) unless File.exist?(file_path)
|
68
|
+
raise file_is_dir_error(file_path) unless File.file?(file_path)
|
69
|
+
|
70
|
+
actual_count = File.foreach(file_path).count
|
71
|
+
return if actual_count == expected_count
|
72
|
+
|
73
|
+
raise MatchError,
|
74
|
+
"File had #{actual_count} lines, not #{expected_count} lines as expected.\nEntire screen:\n#{self}"
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def file_not_found_error(file_path)
|
80
|
+
raise MatchError,
|
81
|
+
"File with path #{file_path} was not found when asserted it did exist.\nEntire screen:\n#{self}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def file_is_dir_error(file_path)
|
85
|
+
raise MatchError,
|
86
|
+
"File with path #{file_path} is a directory.\nEntire screen:\n#{self}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTYtest
|
4
|
+
# Row Assertions for ttytest2.
|
5
|
+
module RowAssertions
|
6
|
+
# Asserts the contents of a single row match the value expected
|
7
|
+
# @param [Integer] row_number the row (starting from 0) to test against
|
8
|
+
# @param [String] expected the expected value of the row. Any trailing whitespace is ignored
|
9
|
+
# @raise [MatchError] if the row doesn't match exactly
|
10
|
+
def assert_row(row_number, expected)
|
11
|
+
validate(row_number)
|
12
|
+
expected = expected.rstrip
|
13
|
+
actual = row(row_number)
|
14
|
+
|
15
|
+
return if !actual.nil? && actual == expected
|
16
|
+
|
17
|
+
raise MatchError,
|
18
|
+
"expected row #{row_number} to be #{expected.inspect} but got #{get_inspection(actual)}\n
|
19
|
+
Entire screen:\n#{self}"
|
20
|
+
end
|
21
|
+
alias assert_line assert_row
|
22
|
+
|
23
|
+
# Asserts the specified row is empty
|
24
|
+
# @param [Integer] row_number the row (starting from 0) to test against
|
25
|
+
# @raise [MatchError] if the row isn't empty
|
26
|
+
def assert_row_is_empty(row_number)
|
27
|
+
validate(row_number)
|
28
|
+
actual = row(row_number)
|
29
|
+
|
30
|
+
return if actual == ''
|
31
|
+
|
32
|
+
raise MatchError,
|
33
|
+
"expected row #{row_number} to be empty but got #{get_inspection(actual)}\nEntire screen:\n#{self}"
|
34
|
+
end
|
35
|
+
alias assert_line_is_empty assert_row_is_empty
|
36
|
+
|
37
|
+
# Asserts the contents of a single row contains the expected string at a specific position
|
38
|
+
# @param [Integer] row_number the row (starting from 0) to test against
|
39
|
+
# @param [Integer] column_start the column position to start comparing expected against
|
40
|
+
# @param [Integer] columns_end the column position to end comparing expected against
|
41
|
+
# @param [String] expected the expected value that the row starts with. Any trailing whitespace is ignored
|
42
|
+
# @raise [MatchError] if the row doesn't match
|
43
|
+
def assert_row_at(row_number, column_start, column_end, expected)
|
44
|
+
validate(row_number)
|
45
|
+
expected = expected.rstrip
|
46
|
+
actual = row(row_number)
|
47
|
+
column_end += 1
|
48
|
+
|
49
|
+
return if !actual.nil? && actual[column_start, column_end].eql?(expected)
|
50
|
+
|
51
|
+
inspection = get_inspection_bounded(actual, column_start, column_end)
|
52
|
+
|
53
|
+
raise MatchError,
|
54
|
+
"expected row #{row_number} to contain #{expected[column_start,
|
55
|
+
column_end]} at #{column_start}-#{column_end} and got #{inspection}\nEntire screen:\n#{self}"
|
56
|
+
end
|
57
|
+
alias assert_line_at assert_row_at
|
58
|
+
|
59
|
+
# Asserts the contents of a single row contains the value expected
|
60
|
+
# @param [Integer] row_number the row (starting from 0) to test against
|
61
|
+
# @param [String] expected the expected value contained in the row. Any trailing whitespace is ignored
|
62
|
+
# @raise [MatchError] if the row doesn't match
|
63
|
+
def assert_row_like(row_number, expected)
|
64
|
+
validate(row_number)
|
65
|
+
expected = expected.rstrip
|
66
|
+
actual = row(row_number)
|
67
|
+
|
68
|
+
return if !actual.nil? && actual.include?(expected)
|
69
|
+
|
70
|
+
raise MatchError,
|
71
|
+
"expected row #{row_number} to be like #{expected.inspect} but got #{get_inspection(actual)}\nEntire screen:\n#{self}"
|
72
|
+
end
|
73
|
+
alias assert_row_contains assert_row_like
|
74
|
+
alias assert_line_contains assert_row_like
|
75
|
+
alias assert_line_like assert_row_like
|
76
|
+
|
77
|
+
# Asserts the contents of a single row starts with expected string
|
78
|
+
# @param [Integer] row_number the row (starting from 0) to test against
|
79
|
+
# @param [String] expected the expected value that the row starts with. Any trailing whitespace is ignored
|
80
|
+
# @raise [MatchError] if the row doesn't match
|
81
|
+
def assert_row_starts_with(row_number, expected)
|
82
|
+
validate(row_number)
|
83
|
+
expected = expected.rstrip
|
84
|
+
actual = row(row_number)
|
85
|
+
|
86
|
+
return if !actual.nil? && actual.start_with?(expected)
|
87
|
+
|
88
|
+
raise MatchError,
|
89
|
+
"expected row #{row_number} to start with #{expected.inspect} and got #{get_inspection(actual)}\nEntire screen:\n#{self}"
|
90
|
+
end
|
91
|
+
alias assert_line_starts_with assert_row_starts_with
|
92
|
+
|
93
|
+
# Asserts the contents of a single row end with expected
|
94
|
+
# @param [Integer] row_number the row (starting from 0) to test against
|
95
|
+
# @param [String] expected the expected value that the row starts with. Any trailing whitespace is ignored
|
96
|
+
# @raise [MatchError] if the row doesn't match
|
97
|
+
def assert_row_ends_with(row_number, expected)
|
98
|
+
validate(row_number)
|
99
|
+
expected = expected.rstrip
|
100
|
+
actual = row(row_number)
|
101
|
+
|
102
|
+
return if !actual.nil? && actual.end_with?(expected)
|
103
|
+
|
104
|
+
raise MatchError,
|
105
|
+
"expected row #{row_number} to end with #{expected.inspect} and got #{get_inspection(actual)}\nEntire screen:\n#{self}"
|
106
|
+
end
|
107
|
+
alias assert_line_ends_with assert_row_ends_with
|
108
|
+
|
109
|
+
# Asserts the contents of a single row match against the passed in regular expression
|
110
|
+
# @param [Integer] row_number the row (starting from 0) to test against
|
111
|
+
# @param [String] regexp_str the regular expression as a string that will be used to match with.
|
112
|
+
# @raise [MatchError] if the row doesn't match against the regular expression
|
113
|
+
def assert_row_regexp(row_number, regexp_str)
|
114
|
+
validate(row_number)
|
115
|
+
regexp = Regexp.new(regexp_str)
|
116
|
+
actual = row(row_number)
|
117
|
+
|
118
|
+
return if !actual.nil? && actual.match?(regexp)
|
119
|
+
|
120
|
+
raise MatchError,
|
121
|
+
"expected row #{row_number} to match regexp #{regexp_str} but it did not. Row value #{get_inspection(actual)}\nEntire screen:\n#{self}"
|
122
|
+
end
|
123
|
+
alias assert_line_regexp assert_row_regexp
|
124
|
+
|
125
|
+
# Asserts the contents of a multiple rows each match against the passed in regular expression
|
126
|
+
# @param [Integer] row_start the row (starting from 0) to test against
|
127
|
+
# @param [Integer] row_end the last row to test against
|
128
|
+
# @param [String] regexp_str the regular expression as a string that will be used to match with.
|
129
|
+
# @raise [MatchError] if the row doesn't match against the regular expression
|
130
|
+
def assert_rows_each_match_regexp(row_start, row_end, regexp_str)
|
131
|
+
validate(row_end)
|
132
|
+
regexp = Regexp.new(regexp_str)
|
133
|
+
row_end += 1 if row_end.zero?
|
134
|
+
|
135
|
+
rows.slice(row_start, row_end).each_with_index do |actual_row, index|
|
136
|
+
next if !actual_row.nil? && actual_row.match?(regexp)
|
137
|
+
|
138
|
+
raise MatchError,
|
139
|
+
"expected row #{index} to match regexp #{regexp_str} but it did not. Row value #{get_inspection(actual_row)}\nEntire screen:\n#{self}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
alias assert_lines_each_match_regexp assert_rows_each_match_regexp
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTYtest
|
4
|
+
# Screen Assertions for ttytest2.
|
5
|
+
module ScreenAssertions
|
6
|
+
# Asserts the full contents of the terminal
|
7
|
+
# @param [String] expected the full expected contents of the terminal. Trailing whitespace on each line is ignored
|
8
|
+
# @raise [MatchError] if the terminal doesn't match the expected content
|
9
|
+
def assert_contents(expected)
|
10
|
+
matched, diff = get_diff(expected, rows)
|
11
|
+
|
12
|
+
return if matched
|
13
|
+
|
14
|
+
raise MatchError,
|
15
|
+
"screen did not match expected content:\n--- expected\n+++ actual\n#{diff.join("\n")}"
|
16
|
+
end
|
17
|
+
alias assert_matches assert_contents
|
18
|
+
alias assert_screen assert_contents
|
19
|
+
|
20
|
+
# Asserts the contents of the terminal at specified rows
|
21
|
+
# @param [String] expected the expected contents of the terminal at specified rows. Trailing whitespace on each line is ignored
|
22
|
+
# @raise [MatchError] if the terminal doesn't match the expected content
|
23
|
+
def assert_contents_at(row_start, row_end, expected)
|
24
|
+
validate(row_end)
|
25
|
+
row_end += 1 if row_end.zero?
|
26
|
+
|
27
|
+
matched, diff = get_diff(expected, rows.slice(row_start, row_end))
|
28
|
+
|
29
|
+
return if matched
|
30
|
+
|
31
|
+
raise MatchError,
|
32
|
+
"screen did not match expected content:\n--- expected\n+++ actual\n#{diff.join("\n")}"
|
33
|
+
end
|
34
|
+
alias assert_matches_at assert_contents_at
|
35
|
+
alias assert_rows assert_contents_at
|
36
|
+
|
37
|
+
# Asserts the contents of the screen include the passed in string
|
38
|
+
# @param [String] expected the string value expected to be found in the screen contents
|
39
|
+
# @raise [MatchError] if the screen does not contain the expected value
|
40
|
+
def assert_contents_include(expected)
|
41
|
+
found = false
|
42
|
+
rows.each do |row|
|
43
|
+
found = true if row.include?(expected)
|
44
|
+
end
|
45
|
+
return if found
|
46
|
+
|
47
|
+
raise MatchError,
|
48
|
+
"Expected screen contents to include #{expected}, but it was not found.\nEntire screen:\n#{self}"
|
49
|
+
end
|
50
|
+
alias assert_screen_includes assert_contents_include
|
51
|
+
|
52
|
+
# Asserts the contents of the screen are empty
|
53
|
+
# @raise [MatchError] if the screen is not empty
|
54
|
+
def assert_contents_empty
|
55
|
+
return if rows.all? { |s| s.to_s.empty? }
|
56
|
+
|
57
|
+
raise MatchError,
|
58
|
+
"Expected screen to be empty, but found content.\nEntire screen:\n#{self}"
|
59
|
+
end
|
60
|
+
alias assert_screen_empty assert_contents_empty
|
61
|
+
|
62
|
+
# Asserts the contents of the screen as a single string match the passed in regular expression
|
63
|
+
# @param [String] regexp_str the regular expression as a string that will be used to match with
|
64
|
+
# @raise [MatchError] if the screen as a string doesn't match against the regular expression
|
65
|
+
def assert_contents_match_regexp(regexp_str)
|
66
|
+
regexp = Regexp.new(regexp_str)
|
67
|
+
screen = capture.to_s
|
68
|
+
|
69
|
+
return if !screen.nil? && screen.match?(regexp)
|
70
|
+
|
71
|
+
raise MatchError,
|
72
|
+
"Expected screen contents to match regexp #{regexp_str} but they did not\nEntire screen:\n#{self}"
|
73
|
+
end
|
74
|
+
alias assert_screen_matches_regexp assert_contents_match_regexp
|
75
|
+
end
|
76
|
+
end
|
data/lib/ttytest/assertions.rb
CHANGED
@@ -1,316 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'ttytest/assertions/file_assertions'
|
4
|
+
require 'ttytest/assertions/row_assertions'
|
5
|
+
require 'ttytest/assertions/column_assertions'
|
6
|
+
require 'ttytest/assertions/screen_assertions'
|
7
|
+
require 'ttytest/assertions/cursor_assertions'
|
8
|
+
|
3
9
|
module TTYtest
|
4
10
|
# Assertions for ttytest2.
|
5
11
|
module Assertions
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
validate(row_number)
|
12
|
-
expected = expected.rstrip
|
13
|
-
actual = row(row_number)
|
14
|
-
|
15
|
-
return if !actual.nil? && actual == expected
|
16
|
-
|
17
|
-
raise MatchError,
|
18
|
-
"expected row #{row_number} to be #{expected.inspect} but got #{get_inspection(actual)}\n
|
19
|
-
Entire screen:\n#{self}"
|
20
|
-
end
|
21
|
-
alias assert_line assert_row
|
22
|
-
|
23
|
-
# Asserts the specified row is empty
|
24
|
-
# @param [Integer] row_number the row (starting from 0) to test against
|
25
|
-
# @raise [MatchError] if the row isn't empty
|
26
|
-
def assert_row_is_empty(row_number)
|
27
|
-
validate(row_number)
|
28
|
-
actual = row(row_number)
|
29
|
-
|
30
|
-
return if actual == ''
|
31
|
-
|
32
|
-
raise MatchError,
|
33
|
-
"expected row #{row_number} to be empty but got #{get_inspection(actual)}\nEntire screen:\n#{self}"
|
34
|
-
end
|
35
|
-
alias assert_line_is_empty assert_row_is_empty
|
36
|
-
|
37
|
-
# Asserts the contents of a single row contains the expected string at a specific position
|
38
|
-
# @param [Integer] row_number the row (starting from 0) to test against
|
39
|
-
# @param [Integer] column_start the column position to start comparing expected against
|
40
|
-
# @param [Integer] columns_end the column position to end comparing expected against
|
41
|
-
# @param [String] expected the expected value that the row starts with. Any trailing whitespace is ignored
|
42
|
-
# @raise [MatchError] if the row doesn't match
|
43
|
-
def assert_row_at(row_number, column_start, column_end, expected)
|
44
|
-
validate(row_number)
|
45
|
-
expected = expected.rstrip
|
46
|
-
actual = row(row_number)
|
47
|
-
column_end += 1
|
48
|
-
|
49
|
-
return if !actual.nil? && actual[column_start, column_end].eql?(expected)
|
50
|
-
|
51
|
-
inspection = get_inspection_bounded(actual, column_start, column_end)
|
52
|
-
|
53
|
-
raise MatchError,
|
54
|
-
"expected row #{row_number} to contain #{expected[column_start,
|
55
|
-
column_end]} at #{column_start}-#{column_end} and got #{inspection}\nEntire screen:\n#{self}"
|
56
|
-
end
|
57
|
-
alias assert_line_at assert_row_at
|
58
|
-
|
59
|
-
# Asserts the contents of a single row contains the value expected
|
60
|
-
# @param [Integer] row_number the row (starting from 0) to test against
|
61
|
-
# @param [String] expected the expected value contained in the row. Any trailing whitespace is ignored
|
62
|
-
# @raise [MatchError] if the row doesn't match
|
63
|
-
def assert_row_like(row_number, expected)
|
64
|
-
validate(row_number)
|
65
|
-
expected = expected.rstrip
|
66
|
-
actual = row(row_number)
|
67
|
-
|
68
|
-
return if !actual.nil? && actual.include?(expected)
|
69
|
-
|
70
|
-
raise MatchError,
|
71
|
-
"expected row #{row_number} to be like #{expected.inspect} but got #{get_inspection(actual)}\nEntire screen:\n#{self}"
|
72
|
-
end
|
73
|
-
alias assert_row_contains assert_row_like
|
74
|
-
alias assert_line_contains assert_row_like
|
75
|
-
alias assert_line_like assert_row_like
|
76
|
-
|
77
|
-
# Asserts the contents of a single row starts with expected string
|
78
|
-
# @param [Integer] row_number the row (starting from 0) to test against
|
79
|
-
# @param [String] expected the expected value that the row starts with. Any trailing whitespace is ignored
|
80
|
-
# @raise [MatchError] if the row doesn't match
|
81
|
-
def assert_row_starts_with(row_number, expected)
|
82
|
-
validate(row_number)
|
83
|
-
expected = expected.rstrip
|
84
|
-
actual = row(row_number)
|
85
|
-
|
86
|
-
return if !actual.nil? && actual.start_with?(expected)
|
87
|
-
|
88
|
-
raise MatchError,
|
89
|
-
"expected row #{row_number} to start with #{expected.inspect} and got #{get_inspection(actual)}\nEntire screen:\n#{self}"
|
90
|
-
end
|
91
|
-
alias assert_line_starts_with assert_row_starts_with
|
92
|
-
|
93
|
-
# Asserts the contents of a single row end with expected
|
94
|
-
# @param [Integer] row_number the row (starting from 0) to test against
|
95
|
-
# @param [String] expected the expected value that the row starts with. Any trailing whitespace is ignored
|
96
|
-
# @raise [MatchError] if the row doesn't match
|
97
|
-
def assert_row_ends_with(row_number, expected)
|
98
|
-
validate(row_number)
|
99
|
-
expected = expected.rstrip
|
100
|
-
actual = row(row_number)
|
101
|
-
|
102
|
-
return if !actual.nil? && actual.end_with?(expected)
|
103
|
-
|
104
|
-
raise MatchError,
|
105
|
-
"expected row #{row_number} to end with #{expected.inspect} and got #{get_inspection(actual)}\nEntire screen:\n#{self}"
|
106
|
-
end
|
107
|
-
alias assert_line_ends_with assert_row_ends_with
|
108
|
-
|
109
|
-
# Asserts the contents of a single row match against the passed in regular expression
|
110
|
-
# @param [Integer] row_number the row (starting from 0) to test against
|
111
|
-
# @param [String] regexp_str the regular expression as a string that will be used to match with.
|
112
|
-
# @raise [MatchError] if the row doesn't match against the regular expression
|
113
|
-
def assert_row_regexp(row_number, regexp_str)
|
114
|
-
validate(row_number)
|
115
|
-
regexp = Regexp.new(regexp_str)
|
116
|
-
actual = row(row_number)
|
117
|
-
|
118
|
-
return if !actual.nil? && actual.match?(regexp)
|
119
|
-
|
120
|
-
raise MatchError,
|
121
|
-
"expected row #{row_number} to match regexp #{regexp_str} but it did not. Row value #{get_inspection(actual)}\nEntire screen:\n#{self}"
|
122
|
-
end
|
123
|
-
alias assert_line_regexp assert_row_regexp
|
124
|
-
|
125
|
-
# Asserts the contents of a multiple rows each match against the passed in regular expression
|
126
|
-
# @param [Integer] row_start the row (starting from 0) to test against
|
127
|
-
# @param [Integer] row_end the last row to test against
|
128
|
-
# @param [String] regexp_str the regular expression as a string that will be used to match with.
|
129
|
-
# @raise [MatchError] if the row doesn't match against the regular expression
|
130
|
-
def assert_rows_each_match_regexp(row_start, row_end, regexp_str)
|
131
|
-
validate(row_end)
|
132
|
-
regexp = Regexp.new(regexp_str)
|
133
|
-
row_end += 1 if row_end.zero?
|
134
|
-
|
135
|
-
rows.slice(row_start, row_end).each_with_index do |actual_row, index|
|
136
|
-
next if !actual_row.nil? && actual_row.match?(regexp)
|
137
|
-
|
138
|
-
raise MatchError,
|
139
|
-
"expected row #{index} to match regexp #{regexp_str} but it did not. Row value #{get_inspection(actual_row)}\nEntire screen:\n#{self}"
|
140
|
-
end
|
141
|
-
end
|
142
|
-
alias assert_lines_each_match_regexp assert_rows_each_match_regexp
|
143
|
-
|
144
|
-
# Asserts that the cursor is in the expected position
|
145
|
-
# @param [Integer] x cursor x (row) position, starting from 0
|
146
|
-
# @param [Integer] y cursor y (column) position, starting from 0
|
147
|
-
# @raise [MatchError] if the cursor position doesn't match
|
148
|
-
def assert_cursor_position(x, y)
|
149
|
-
expected = [x, y]
|
150
|
-
actual = [cursor_x, cursor_y]
|
151
|
-
|
152
|
-
return if actual == expected
|
153
|
-
|
154
|
-
raise MatchError,
|
155
|
-
"expected cursor to be at #{expected.inspect} but was at #{get_inspection(actual)}\nEntire screen:\n#{self}"
|
156
|
-
end
|
157
|
-
|
158
|
-
# Asserts the cursor is currently visible
|
159
|
-
# @raise [MatchError] if the cursor is hidden
|
160
|
-
def assert_cursor_visible
|
161
|
-
return if cursor_visible?
|
162
|
-
|
163
|
-
raise MatchError, "expected cursor to be visible was hidden\nEntire screen:\n#{self}"
|
164
|
-
end
|
165
|
-
|
166
|
-
# Asserts the cursor is currently hidden
|
167
|
-
# @raise [MatchError] if the cursor is visible
|
168
|
-
def assert_cursor_hidden
|
169
|
-
return if cursor_hidden?
|
170
|
-
|
171
|
-
raise MatchError, "expected cursor to be hidden was visible\nEntire screen:\n#{self}"
|
172
|
-
end
|
173
|
-
|
174
|
-
# Asserts the full contents of the terminal
|
175
|
-
# @param [String] expected the full expected contents of the terminal. Trailing whitespace on each line is ignored
|
176
|
-
# @raise [MatchError] if the terminal doesn't match the expected content
|
177
|
-
def assert_contents(expected)
|
178
|
-
matched, diff = get_diff(expected, rows)
|
179
|
-
|
180
|
-
return if matched
|
181
|
-
|
182
|
-
raise MatchError,
|
183
|
-
"screen did not match expected content:\n--- expected\n+++ actual\n#{diff.join("\n")}"
|
184
|
-
end
|
185
|
-
alias assert_matches assert_contents
|
186
|
-
alias assert_screen assert_contents
|
187
|
-
|
188
|
-
# Asserts the contents of the terminal at specified rows
|
189
|
-
# @param [String] expected the expected contents of the terminal at specified rows. Trailing whitespace on each line is ignored
|
190
|
-
# @raise [MatchError] if the terminal doesn't match the expected content
|
191
|
-
def assert_contents_at(row_start, row_end, expected)
|
192
|
-
validate(row_end)
|
193
|
-
row_end += 1 if row_end.zero?
|
194
|
-
|
195
|
-
matched, diff = get_diff(expected, rows.slice(row_start, row_end))
|
196
|
-
|
197
|
-
return if matched
|
198
|
-
|
199
|
-
raise MatchError,
|
200
|
-
"screen did not match expected content:\n--- expected\n+++ actual\n#{diff.join("\n")}"
|
201
|
-
end
|
202
|
-
alias assert_matches_at assert_contents_at
|
203
|
-
alias assert_rows assert_contents_at
|
204
|
-
|
205
|
-
# Asserts the contents of the screen include the passed in string
|
206
|
-
# @param [String] expected the string value expected to be found in the screen contents
|
207
|
-
# @raise [MatchError] if the screen does not contain the expected value
|
208
|
-
def assert_contents_include(expected)
|
209
|
-
found = false
|
210
|
-
rows.each do |row|
|
211
|
-
found = true if row.include?(expected)
|
212
|
-
end
|
213
|
-
return if found
|
214
|
-
|
215
|
-
raise MatchError,
|
216
|
-
"Expected screen contents to include #{expected}, but it was not found.\nEntire screen:\n#{self}"
|
217
|
-
end
|
218
|
-
alias assert_screen_includes assert_contents_include
|
219
|
-
|
220
|
-
# Asserts the contents of the screen are empty
|
221
|
-
# @raise [MatchError] if the screen is not empty
|
222
|
-
def assert_contents_empty
|
223
|
-
return if rows.all? { |s| s.to_s.empty? }
|
224
|
-
|
225
|
-
raise MatchError,
|
226
|
-
"Expected screen to be empty, but found content.\nEntire screen:\n#{self}"
|
227
|
-
end
|
228
|
-
alias assert_screen_empty assert_contents_empty
|
229
|
-
|
230
|
-
# Asserts the contents of the screen as a single string match the passed in regular expression
|
231
|
-
# @param [String] regexp_str the regular expression as a string that will be used to match with
|
232
|
-
# @raise [MatchError] if the screen as a string doesn't match against the regular expression
|
233
|
-
def assert_contents_match_regexp(regexp_str)
|
234
|
-
regexp = Regexp.new(regexp_str)
|
235
|
-
screen = capture.to_s
|
236
|
-
|
237
|
-
return if !screen.nil? && screen.match?(regexp)
|
238
|
-
|
239
|
-
raise MatchError,
|
240
|
-
"Expected screen contents to match regexp #{regexp_str} but they did not\nEntire screen:\n#{self}"
|
241
|
-
end
|
242
|
-
alias assert_screen_matches_regexp assert_contents_match_regexp
|
243
|
-
|
244
|
-
# Asserts the specified file exists
|
245
|
-
# @param [String] file_path the path to the file
|
246
|
-
# @raise [MatchError] if the file is not found or is a directory/symlink
|
247
|
-
def assert_file_exists(file_path)
|
248
|
-
raise file_not_found_error(file_path) unless File.exist?(file_path)
|
249
|
-
raise file_is_dir_error(file_path) unless File.file?(file_path)
|
250
|
-
end
|
251
|
-
|
252
|
-
# Asserts the specified file does not exists
|
253
|
-
# @param [String] file_path the path to the file
|
254
|
-
# @raise [MatchError] if the file is found or is a directory/symlink
|
255
|
-
def assert_file_doesnt_exist(file_path)
|
256
|
-
return unless File.exist?(file_path) || File.file?(file_path)
|
257
|
-
|
258
|
-
raise MatchError,
|
259
|
-
"File with path #{file_path} was found or is a directory when it was asserted it did not exist.\nEntire screen:\n#{self}"
|
260
|
-
end
|
261
|
-
|
262
|
-
# Asserts the specified file contains the passed in string value
|
263
|
-
# @param [String] file_path the path to the file
|
264
|
-
# @param [String] needle the value to search for in the file
|
265
|
-
# @raise [MatchError] if the file does not contain value in variable needle
|
266
|
-
def assert_file_contains(file_path, needle)
|
267
|
-
raise file_not_found_error(file_path) unless File.exist?(file_path)
|
268
|
-
raise file_is_dir_error(file_path) unless File.file?(file_path)
|
269
|
-
|
270
|
-
file_contains = false
|
271
|
-
File.foreach(file_path) do |line|
|
272
|
-
if line.include?(needle)
|
273
|
-
file_contains = true
|
274
|
-
break
|
275
|
-
end
|
276
|
-
end
|
277
|
-
return if file_contains
|
278
|
-
|
279
|
-
raise MatchError,
|
280
|
-
"File with path #{file_path} did not contain #{needle}.\nEntire screen:\n#{self}"
|
281
|
-
end
|
282
|
-
alias assert_file_like assert_file_contains
|
283
|
-
|
284
|
-
# Asserts the specified file has the permissions specified
|
285
|
-
# @param [String] file_path the path to the file
|
286
|
-
# @param [String] permissions the expected permissions of the file (in form '644' or '775')
|
287
|
-
# @raise [MatchError] if the file has different permissions than specified
|
288
|
-
def assert_file_has_permissions(file_path, permissions)
|
289
|
-
raise file_not_found_error(file_path) unless File.exist?(file_path)
|
290
|
-
raise file_is_dir_error(file_path) unless File.file?(file_path)
|
291
|
-
|
292
|
-
file_mode = File.stat(file_path).mode
|
293
|
-
perms_octal = format('%o', file_mode)[-3...]
|
294
|
-
return if perms_octal == permissions
|
295
|
-
|
296
|
-
raise MatchError,
|
297
|
-
"File had permissions #{perms_octal}, not #{permissions} as expected.\n Entire screen:\n#{self}"
|
298
|
-
end
|
299
|
-
|
300
|
-
# Asserts the specified file has line count specified
|
301
|
-
# @param [String] file_path the path to the file
|
302
|
-
# @param [String] expected_count the expected line count of the file
|
303
|
-
# @raise [MatchError] if the file has a different line count than specified
|
304
|
-
def assert_file_has_line_count(file_path, expected_count)
|
305
|
-
raise file_not_found_error(file_path) unless File.exist?(file_path)
|
306
|
-
raise file_is_dir_error(file_path) unless File.file?(file_path)
|
307
|
-
|
308
|
-
actual_count = File.foreach(file_path).count
|
309
|
-
return if actual_count == expected_count
|
310
|
-
|
311
|
-
raise MatchError,
|
312
|
-
"File had #{actual_count} lines, not #{expected_count} lines as expected.\nEntire screen:\n#{self}"
|
313
|
-
end
|
12
|
+
include TTYtest::FileAssertions
|
13
|
+
include TTYtest::RowAssertions
|
14
|
+
include TTYtest::ColumnAssertions
|
15
|
+
include TTYtest::ScreenAssertions
|
16
|
+
include TTYtest::CursorAssertions
|
314
17
|
|
315
18
|
METHODS = public_instance_methods
|
316
19
|
|
@@ -358,15 +61,5 @@ module TTYtest
|
|
358
61
|
|
359
62
|
[matched, diff]
|
360
63
|
end
|
361
|
-
|
362
|
-
def file_not_found_error(file_path)
|
363
|
-
raise MatchError,
|
364
|
-
"File with path #{file_path} was not found when asserted it did exist.\nEntire screen:\n#{self}"
|
365
|
-
end
|
366
|
-
|
367
|
-
def file_is_dir_error(file_path)
|
368
|
-
raise MatchError,
|
369
|
-
"File with path #{file_path} is a directory.\nEntire screen:\n#{self}"
|
370
|
-
end
|
371
64
|
end
|
372
65
|
end
|
data/lib/ttytest/capture.rb
CHANGED
@@ -9,7 +9,7 @@ module TTYtest
|
|
9
9
|
class Capture
|
10
10
|
include TTYtest::Assertions
|
11
11
|
|
12
|
-
attr_reader :cursor_x, :cursor_y, :width, :height
|
12
|
+
attr_reader :cursor_x, :cursor_y, :width, :height, :max_wait_time, :use_return_for_newline
|
13
13
|
|
14
14
|
# Used internally by drivers when called by {Terminal#capture}
|
15
15
|
# @api private
|
data/lib/ttytest/constants.rb
CHANGED
@@ -16,7 +16,8 @@ module TTYtest
|
|
16
16
|
SHIFT_ENTER = 11.chr # \v
|
17
17
|
FORM_FEED = 12.chr # \f
|
18
18
|
CTRLL = 12.chr
|
19
|
-
CARRIAGE_RETURN = 13.chr # \r
|
19
|
+
CARRIAGE_RETURN = 13.chr # \r, left for backwards compat
|
20
|
+
RETURN = 13.chr # \r, same as CARRIAGE_RETURN
|
20
21
|
CTRLU = 21.chr
|
21
22
|
CTRLW = 23.chr
|
22
23
|
ESCAPE = 27.chr # ^[ or /033 or /e
|
data/lib/ttytest/terminal.rb
CHANGED
@@ -9,14 +9,15 @@ module TTYtest
|
|
9
9
|
class Terminal
|
10
10
|
extend Forwardable
|
11
11
|
|
12
|
-
attr_accessor :max_wait_time
|
12
|
+
attr_accessor :max_wait_time, :use_return_for_newline
|
13
13
|
|
14
14
|
# @api private
|
15
15
|
# @see TTYtest.new_terminal, use this or other new_* methods instead.
|
16
16
|
# Internal constructor.
|
17
|
-
def initialize(driver_terminal)
|
17
|
+
def initialize(driver_terminal, max_wait_time, use_return_for_newline)
|
18
|
+
@max_wait_time = max_wait_time
|
19
|
+
@use_return_for_newline = use_return_for_newline
|
18
20
|
@driver_terminal = driver_terminal
|
19
|
-
@max_wait_time = TTYtest.default_max_wait_time
|
20
21
|
end
|
21
22
|
|
22
23
|
# @!method send_keys(*keys)
|
@@ -67,17 +68,21 @@ module TTYtest
|
|
67
68
|
# @param [Integer] sleep_time the amount of time to sleep after sending the line
|
68
69
|
|
69
70
|
# @!method send_newline
|
70
|
-
# Simulate typing enter by sending newline character to the terminal.
|
71
|
+
# Simulate typing enter by sending newline (ALT + enter) character to the terminal.
|
72
|
+
# Many line readers interpreter newline as return, but in some cases you may have to use send_return.
|
71
73
|
|
72
74
|
# @!method send_newlines(number_of_times)
|
73
|
-
# Simulates sending newline the specified number of times.
|
75
|
+
# Simulates sending newline (ALT + enter) the specified number of times.
|
76
|
+
# Many line readers interpreter newline as return, but in some cases you may have to use send_return.
|
74
77
|
# @param [Integer] number_of_times number of times to send newline
|
75
78
|
|
76
|
-
# @!method
|
79
|
+
# @!method send_return
|
77
80
|
# Simulate typing enter by sending newline character to the terminal.
|
81
|
+
# Many line readers interpreter newline as return, but in some cases you may have to use send_return.
|
78
82
|
|
79
|
-
# @!method
|
83
|
+
# @!method send_returns(number_of_times)
|
80
84
|
# Simulates sending newline the specified number of times.
|
85
|
+
# Many line readers interpreter newline as return, but in some cases you may have to use send_return.
|
81
86
|
# @param [Integer] number of times to send newline
|
82
87
|
|
83
88
|
# @!method send_delete
|
@@ -142,6 +147,13 @@ module TTYtest
|
|
142
147
|
# Simulates typing in the Escape (ESC) key in the terminal the specified number of times.
|
143
148
|
# @param [Integer] number of times to send escape
|
144
149
|
|
150
|
+
# @!method send_tab
|
151
|
+
# Simulates typing in the Tab (\t) key in the terminal.
|
152
|
+
|
153
|
+
# @!method send_tabs(number_of_times)
|
154
|
+
# Simulates typing in the Tab (\t) key in the terminal the specified number of times.
|
155
|
+
# @param [Integer] number of times to send tab
|
156
|
+
|
145
157
|
# @!method capture
|
146
158
|
# Capture represents the current state of the terminal.
|
147
159
|
# @return [Capture] The current state of the terminal when called
|
@@ -152,11 +164,13 @@ module TTYtest
|
|
152
164
|
:send_line_exact, :send_lines_exact,
|
153
165
|
:send_newline, :send_newlines,
|
154
166
|
:send_enter, :send_enters,
|
167
|
+
:send_return, :send_returns,
|
155
168
|
:send_delete, :send_deletes,
|
156
169
|
:send_backspace, :send_backspaces,
|
157
170
|
:send_left_arrow, :send_left_arrows, :send_right_arrow, :send_right_arrows,
|
158
171
|
:send_down_arrow, :send_down_arrows, :send_up_arrow, :send_up_arrows,
|
159
172
|
:send_keys_exact, :send_home, :send_end, :send_clear, :send_escape, :send_escapes,
|
173
|
+
:send_tab, :send_tabs,
|
160
174
|
:capture
|
161
175
|
|
162
176
|
# @!method print
|
data/lib/ttytest/tmux/driver.rb
CHANGED
@@ -31,21 +31,22 @@ module TTYtest
|
|
31
31
|
@config_file_path = config_file_path
|
32
32
|
end
|
33
33
|
|
34
|
-
def new_terminal(cmd, width: 80, height: 24)
|
34
|
+
def new_terminal(cmd, width: 80, height: 24, max_wait_time: 2, use_return_for_newline: false)
|
35
35
|
cmd = "#{cmd}\n#{SLEEP_INFINITY}"
|
36
36
|
|
37
37
|
session_name = "ttytest-#{SecureRandom.uuid}"
|
38
38
|
tmux(*%W[-f #{@config_file_path} new-session -s #{session_name} -d -x #{width} -y #{height} #{cmd}])
|
39
|
-
session = Session.new(self, session_name)
|
40
|
-
Terminal.new(session)
|
39
|
+
session = Session.new(self, session_name, use_return_for_newline)
|
40
|
+
Terminal.new(session, max_wait_time, use_return_for_newline)
|
41
41
|
end
|
42
42
|
|
43
43
|
def new_default_sh_terminal
|
44
|
-
new_terminal(%(PS1='$ ' /bin/sh), width: 80, height: 24)
|
44
|
+
new_terminal(%(PS1='$ ' /bin/sh), width: 80, height: 24, max_wait_time: 2, use_return_for_newline: false)
|
45
45
|
end
|
46
46
|
|
47
|
-
def new_sh_terminal(width: 80, height: 24)
|
48
|
-
new_terminal(%(PS1='$ ' /bin/sh), width: width, height: height
|
47
|
+
def new_sh_terminal(width: 80, height: 24, max_wait_time: 2, use_return_for_newline: false)
|
48
|
+
new_terminal(%(PS1='$ ' /bin/sh), width: width, height: height, max_wait_time: max_wait_time,
|
49
|
+
use_return_for_newline: use_return_for_newline)
|
49
50
|
end
|
50
51
|
|
51
52
|
# @api private
|
data/lib/ttytest/tmux/session.rb
CHANGED
@@ -5,10 +5,11 @@ module TTYtest
|
|
5
5
|
# represents a tmux session and how to send output to the current tmux session
|
6
6
|
class Session
|
7
7
|
# @api private
|
8
|
-
def initialize(driver, name)
|
8
|
+
def initialize(driver, name, use_return_for_newline)
|
9
9
|
@id = SecureRandom.uuid
|
10
10
|
@driver = driver
|
11
11
|
@name = name
|
12
|
+
@use_return_for_newline = use_return_for_newline
|
12
13
|
|
13
14
|
ObjectSpace.define_finalizer(@id, proc {
|
14
15
|
begin
|
@@ -58,6 +59,10 @@ module TTYtest
|
|
58
59
|
# Send line to tmux, no need to worry about newline character
|
59
60
|
def send_line(line)
|
60
61
|
send_keys_one_at_a_time(line)
|
62
|
+
if @use_return_for_newline
|
63
|
+
send_return unless ['\n', '\r'].include?(line[-1])
|
64
|
+
return
|
65
|
+
end
|
61
66
|
send_newline unless line[-1] == '\n'
|
62
67
|
end
|
63
68
|
|
@@ -75,6 +80,10 @@ module TTYtest
|
|
75
80
|
|
76
81
|
def send_line_exact(line)
|
77
82
|
send_keys_exact(line)
|
83
|
+
if @use_return_for_newline
|
84
|
+
send_return unless ['\n', '\r'].include?(line[-1])
|
85
|
+
return
|
86
|
+
end
|
78
87
|
send_newline unless line[-1] == '\n'
|
79
88
|
end
|
80
89
|
|
@@ -100,7 +109,6 @@ module TTYtest
|
|
100
109
|
def send_newline
|
101
110
|
driver.tmux(*%W[send-keys -t #{name} -l], %(\n))
|
102
111
|
end
|
103
|
-
alias send_enter send_newline
|
104
112
|
|
105
113
|
def send_newlines(number_of_times)
|
106
114
|
while number_of_times.positive?
|
@@ -108,7 +116,17 @@ module TTYtest
|
|
108
116
|
number_of_times -= 1
|
109
117
|
end
|
110
118
|
end
|
111
|
-
|
119
|
+
|
120
|
+
def send_return
|
121
|
+
driver.tmux(*%W[send-keys -t #{name} -l], %(\r))
|
122
|
+
end
|
123
|
+
|
124
|
+
def send_returns(number_of_times)
|
125
|
+
while number_of_times.positive?
|
126
|
+
send_return
|
127
|
+
number_of_times -= 1
|
128
|
+
end
|
129
|
+
end
|
112
130
|
|
113
131
|
def send_delete
|
114
132
|
send_keys_exact(%(DC))
|
@@ -194,6 +212,10 @@ module TTYtest
|
|
194
212
|
|
195
213
|
def send_clear
|
196
214
|
send_keys_one_at_a_time(TTYtest::CLEAR)
|
215
|
+
if @use_return_for_newline
|
216
|
+
send_return
|
217
|
+
return
|
218
|
+
end
|
197
219
|
send_newline
|
198
220
|
end
|
199
221
|
|
@@ -208,6 +230,17 @@ module TTYtest
|
|
208
230
|
end
|
209
231
|
end
|
210
232
|
|
233
|
+
def send_tab
|
234
|
+
send_keys_exact(TTYtest::TAB)
|
235
|
+
end
|
236
|
+
|
237
|
+
def send_tabs(number_of_times)
|
238
|
+
while number_of_times.positive?
|
239
|
+
send_tab
|
240
|
+
number_of_times -= 1
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
211
244
|
private
|
212
245
|
|
213
246
|
attr_reader :driver, :name
|
data/lib/ttytest/version.rb
CHANGED
data/lib/ttytest.rb
CHANGED
@@ -16,30 +16,38 @@ module TTYtest
|
|
16
16
|
# @param [String] command a valid shell command to run
|
17
17
|
# @param [Integer] width width of the new terminal
|
18
18
|
# @param [Integer] height height of the new terminal
|
19
|
+
# @param [Integer] max_wait_time max time to wait for screen to update before an assertion fails
|
20
|
+
# @param [bool] use_return_for_newline use return instead of newline for functions like send_line
|
19
21
|
# @return [Terminal] a new terminal running the specified command
|
20
22
|
|
21
23
|
# @!method new_default_sh_terminal
|
22
24
|
# Create a new terminal using '/bin/sh' with width: 80 and height: 24.
|
23
25
|
# Useful for Unixes.
|
26
|
+
# @return [Terminal] a new sh terminal
|
24
27
|
|
25
28
|
# @!method new_sh_terminal(width: 80, height: 24)
|
26
29
|
# Create a new terminal using '/bin/sh' with ability to set width and height.
|
27
30
|
# Useful for Unixes.
|
31
|
+
# @param [Integer] max_wait_time max time to wait for screen to update before an assertion fails
|
32
|
+
# @param [bool] use_return_for_newline use return instead of newline for functions like send_line
|
33
|
+
# @return [Terminal] a new sh terminal with specified width and height
|
34
|
+
|
28
35
|
def_delegators :driver
|
29
36
|
|
30
|
-
def new_terminal(cmd, width: 80, height: 24, max_wait_time: 2)
|
31
|
-
@max_wait_time = max_wait_time
|
32
|
-
|
37
|
+
def new_terminal(cmd, width: 80, height: 24, max_wait_time: 2, use_return_for_newline: false)
|
38
|
+
# @max_wait_time = max_wait_time
|
39
|
+
# @use_return_for_newline = use_return_for_newline
|
40
|
+
driver.new_terminal(cmd, width: width, height: height, max_wait_time: max_wait_time,
|
41
|
+
use_return_for_newline: use_return_for_newline)
|
33
42
|
end
|
34
43
|
|
35
|
-
def new_default_sh_terminal
|
36
|
-
@max_wait_time = max_wait_time
|
44
|
+
def new_default_sh_terminal
|
37
45
|
driver.new_default_sh_terminal
|
38
46
|
end
|
39
47
|
|
40
|
-
def new_sh_terminal(width: 80, height: 24, max_wait_time: 2)
|
41
|
-
|
42
|
-
|
48
|
+
def new_sh_terminal(width: 80, height: 24, max_wait_time: 2, use_return_for_newline: false)
|
49
|
+
driver.new_sh_terminal(width: width, height: height, max_wait_time: max_wait_time,
|
50
|
+
use_return_for_newline: use_return_for_newline)
|
43
51
|
end
|
44
52
|
end
|
45
53
|
|
data/notes.txt
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ttytest2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Eski
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -95,8 +95,14 @@ files:
|
|
95
95
|
- README.md
|
96
96
|
- Rakefile
|
97
97
|
- examples/integration_tests.rb
|
98
|
+
- images/ttytest2.png
|
98
99
|
- lib/ttytest.rb
|
99
100
|
- lib/ttytest/assertions.rb
|
101
|
+
- lib/ttytest/assertions/column_assertions.rb
|
102
|
+
- lib/ttytest/assertions/cursor_assertions.rb
|
103
|
+
- lib/ttytest/assertions/file_assertions.rb
|
104
|
+
- lib/ttytest/assertions/row_assertions.rb
|
105
|
+
- lib/ttytest/assertions/screen_assertions.rb
|
100
106
|
- lib/ttytest/capture.rb
|
101
107
|
- lib/ttytest/constants.rb
|
102
108
|
- lib/ttytest/terminal.rb
|