ttytest 0.4.0 → 0.5.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/.gitignore +2 -0
- data/README.md +20 -3
- data/Rakefile +5 -0
- data/lib/ttytest.rb +6 -0
- data/lib/ttytest/capture.rb +16 -2
- data/lib/ttytest/matchers.rb +16 -1
- data/lib/ttytest/terminal.rb +38 -6
- data/lib/ttytest/tmux/driver.rb +14 -7
- data/lib/ttytest/tmux/session.rb +13 -4
- data/lib/ttytest/tmux/tmux.conf +2 -0
- data/lib/ttytest/version.rb +1 -1
- data/ttytest.gemspec +1 -0
- metadata +16 -3
- data/lib/ttytest/dummy.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 91adbf5abccf48f1626c908ebbccc8ad13bcc67b
|
4
|
+
data.tar.gz: f244bd9c2970b4af1704ac84f6bf1a6fb2b8975e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe2ec46d18232b2e0e16c54d827a8efba1628d114cc4c42a78254a114cfd9eb8e9ec009074d298de95fafb8820a2ee283b816b1cce59ab0f1eea4070b57e0025
|
7
|
+
data.tar.gz: 5e23a41408ff1cc4840e33a4049258434b16f17c3614994c4850df1e9adafda0dc9c1cdb57430035b3ebdf1d504b75d43df8301e32a53ebe7d2706ed03fe0f5b
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
TTYtest is an
|
1
|
+
TTYtest is an acceptance test framework for interactive console applications. It's like [capybara](https://github.com/teamcapybara/capybara) for the terminal.
|
2
2
|
|
3
3
|
It works by running commands inside a tmux session, capturing the pane, and comparing the content. The assertions will wait a specified amount of time (default 2 seconds) for the expected content to appear.
|
4
4
|
|
5
5
|
[](https://travis-ci.org/jhawthorn/ttytest)
|
6
|
+
[](https://rubygems.org/gems/ttytest)
|
6
7
|
|
7
8
|
## Requirements
|
8
9
|
|
@@ -11,6 +12,8 @@ It works by running commands inside a tmux session, capturing the pane, and comp
|
|
11
12
|
|
12
13
|
## Usage
|
13
14
|
|
15
|
+
### Example
|
16
|
+
|
14
17
|
``` ruby
|
15
18
|
@tty = TTYtest.new_terminal(%{PS1='$ ' /bin/sh}, width: 80, height: 24)
|
16
19
|
@tty.assert_row(0, '$')
|
@@ -18,8 +21,11 @@ It works by running commands inside a tmux session, capturing the pane, and comp
|
|
18
21
|
|
19
22
|
@tty.send_keys(%{echo "Hello, world"\n})
|
20
23
|
|
21
|
-
@tty.
|
22
|
-
|
24
|
+
@tty.assert_contents <<TTY
|
25
|
+
$ echo "Hello, world"
|
26
|
+
Hello, world
|
27
|
+
$
|
28
|
+
TTY
|
23
29
|
@tty.assert_cursor_position(x: 2, y: 2)
|
24
30
|
|
25
31
|
p @tty.rows # => ["$ echo \"Hello, world\"", "Hello, world", "$", "", "", "", ...]
|
@@ -27,6 +33,17 @@ p @tty.rows # => ["$ echo \"Hello, world\"", "Hello, world", "$", "", "", "", ..
|
|
27
33
|
|
28
34
|
See also [fzy's integration test](https://github.com/jhawthorn/fzy/blob/master/test/integration/integration_test.rb) for a full example.
|
29
35
|
|
36
|
+
### Assertions
|
37
|
+
|
38
|
+
The main way to use TTYtest is through assertions. When called on a `TTYtest::Terminal`, each of these will be retried (for up to 2 seconds by default).
|
39
|
+
|
40
|
+
Available assertions:
|
41
|
+
* `assert_row(row_number, expected_text)`
|
42
|
+
* `assert_cursor_position(x: x, y: y)`
|
43
|
+
* `assert_cursor_visible`
|
44
|
+
* `assert_cursor_hidden`
|
45
|
+
* `assert_contents(lines_of_terminal)`
|
46
|
+
|
30
47
|
## TravisCI
|
31
48
|
|
32
49
|
TTYtest can run on [TravisCI](https://travis-ci.org/), but the version of tmux made available with their default ubuntu 12.04 environment is too old. However the TravisCI ubuntu 14.04 "trusty" image provides tmux 1.8, which works great.
|
data/Rakefile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rake/testtask"
|
3
|
+
require "yard"
|
3
4
|
|
4
5
|
Rake::TestTask.new(:test) do |t|
|
5
6
|
t.libs << "test"
|
@@ -7,6 +8,10 @@ Rake::TestTask.new(:test) do |t|
|
|
7
8
|
t.test_files = FileList['test/**/*_test.rb']
|
8
9
|
end
|
9
10
|
|
11
|
+
YARD::Rake::YardocTask.new do |t|
|
12
|
+
t.files = ['lib/**/*.rb']
|
13
|
+
end
|
14
|
+
|
10
15
|
task :console do
|
11
16
|
system 'irb -Ilib -rttytest'
|
12
17
|
end
|
data/lib/ttytest.rb
CHANGED
@@ -8,6 +8,12 @@ module TTYtest
|
|
8
8
|
attr_accessor :default_max_wait_time
|
9
9
|
|
10
10
|
extend Forwardable
|
11
|
+
# @!method new_terminal(command, width: 80, height: 24)
|
12
|
+
# Create a new terminal through the current driver.
|
13
|
+
# @param [String] command a valid shell command to run
|
14
|
+
# @param [Integer] width width of the new terminal
|
15
|
+
# @param [Integer] height height of the new terminal
|
16
|
+
# @return [Terminal] a new terminal running the specified command
|
11
17
|
def_delegators :driver, :new_terminal
|
12
18
|
end
|
13
19
|
|
data/lib/ttytest/capture.rb
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
module TTYtest
|
2
|
+
# Represents the complete state of a {TTYtest::Terminal} at the time it was captured (contents, cursor position, etc).
|
3
|
+
# @attr_reader [Integer] width the number of columns in the captured terminal
|
4
|
+
# @attr_reader [Integer] height the number of rows in the captured terminal
|
5
|
+
# @attr_reader [Integer] cursor_x the cursor's column (starting at 0) in the captured terminal
|
6
|
+
# @attr_reader [Integer] cursor_y the cursor's row (starting at 0) in the captured terminal
|
2
7
|
class Capture
|
3
8
|
include TTYtest::Matchers
|
4
9
|
|
5
10
|
attr_reader :cursor_x, :cursor_y
|
6
11
|
attr_reader :width, :height
|
7
12
|
|
13
|
+
# Used internally by drivers when called by {Terminal#capture}
|
14
|
+
# @api private
|
8
15
|
def initialize(contents, cursor_x: 0, cursor_y: 0, width: nil, height: nil, cursor_visible: true)
|
9
16
|
@rows = (contents+"\nEND").split("\n")[0...-1].map do |row|
|
10
17
|
row || ""
|
@@ -16,26 +23,33 @@ module TTYtest
|
|
16
23
|
@cursor_visible = cursor_visible
|
17
24
|
end
|
18
25
|
|
26
|
+
# @return [Array<String>] An array of each row's contend from the captured terminal
|
19
27
|
def rows
|
20
28
|
@rows
|
21
29
|
end
|
22
30
|
|
23
|
-
|
24
|
-
|
31
|
+
# @param [Integer] the row to return
|
32
|
+
# @return [String] the content of the row from the captured terminal
|
33
|
+
def row(row_number)
|
34
|
+
rows[row_number]
|
25
35
|
end
|
26
36
|
|
37
|
+
# @return [true,false] Whether the cursor is visible in the captured terminal
|
27
38
|
def cursor_visible?
|
28
39
|
@cursor_visible
|
29
40
|
end
|
30
41
|
|
42
|
+
# @return [true,false] Whether the cursor is hidden in the captured terminal
|
31
43
|
def cursor_hidden?
|
32
44
|
!cursor_visible?
|
33
45
|
end
|
34
46
|
|
47
|
+
# @return [Capture] returns self
|
35
48
|
def capture
|
36
49
|
self
|
37
50
|
end
|
38
51
|
|
52
|
+
# @return [String] All rows of the captured terminal, separated by newlines
|
39
53
|
def to_s
|
40
54
|
rows.join("\n")
|
41
55
|
end
|
data/lib/ttytest/matchers.rb
CHANGED
@@ -1,12 +1,21 @@
|
|
1
1
|
module TTYtest
|
2
2
|
module Matchers
|
3
|
+
# Asserts the contents of a single row
|
4
|
+
# @param [Integer] row_number the row (starting from 0) to test against
|
5
|
+
# @param [String] expected the expected value of the row. Any trailing whitespace is ignored
|
6
|
+
# @raise [MatchError] if the row doesn't match
|
3
7
|
def assert_row(row_number, expected)
|
8
|
+
expected = expected.rstrip
|
4
9
|
actual = row(row_number)
|
5
10
|
if actual != expected
|
6
11
|
raise MatchError, "expected row #{row_number} to be #{expected.inspect} but got #{actual.inspect}\nEntire screen:\n#{to_s}"
|
7
12
|
end
|
8
13
|
end
|
9
14
|
|
15
|
+
# Asserts that the cursor is in the expected position
|
16
|
+
# @param [Integer] x cursor x (row) position, starting from 0
|
17
|
+
# @param [Integer] y cursor y (column) position, starting from 0
|
18
|
+
# @raise [MatchError] if the cursor position doesn't match
|
10
19
|
def assert_cursor_position(x:, y:)
|
11
20
|
expected = [x, y]
|
12
21
|
actual = [cursor_x, cursor_y]
|
@@ -15,19 +24,24 @@ module TTYtest
|
|
15
24
|
end
|
16
25
|
end
|
17
26
|
|
27
|
+
# @raise [MatchError] if the cursor is hidden
|
18
28
|
def assert_cursor_visible
|
19
29
|
if !cursor_visible?
|
20
30
|
raise MatchError, "expected cursor to be visible was hidden\nEntire screen:\n#{to_s}"
|
21
31
|
end
|
22
32
|
end
|
23
33
|
|
34
|
+
# @raise [MatchError] if the cursor is visible
|
24
35
|
def assert_cursor_hidden
|
25
36
|
if !cursor_hidden?
|
26
37
|
raise MatchError, "expected cursor to be hidden was visible\nEntire screen:\n#{to_s}"
|
27
38
|
end
|
28
39
|
end
|
29
40
|
|
30
|
-
|
41
|
+
# Asserts the full contents of the terminal
|
42
|
+
# @param [String] expected the full expected contents of the terminal. Trailing whitespace on each line is ignored
|
43
|
+
# @raise [MatchError] if the terminal doesn't match the expected content
|
44
|
+
def assert_contents(expected)
|
31
45
|
expected_rows = expected.split("\n")
|
32
46
|
diff = []
|
33
47
|
matched = true
|
@@ -46,6 +60,7 @@ module TTYtest
|
|
46
60
|
raise MatchError, "screen did not match expected content:\n--- expected\n+++ actual\n#{diff.join("\n")}"
|
47
61
|
end
|
48
62
|
end
|
63
|
+
alias_method :assert_matches, :assert_contents
|
49
64
|
|
50
65
|
METHODS = public_instance_methods
|
51
66
|
end
|
data/lib/ttytest/terminal.rb
CHANGED
@@ -3,20 +3,52 @@ require 'ttytest/matchers'
|
|
3
3
|
require 'ttytest/capture'
|
4
4
|
|
5
5
|
module TTYtest
|
6
|
+
# @attr [Integer] max_wait_time the maximum amount of time (in seconds) to retry assertions before failing.
|
6
7
|
class Terminal
|
7
8
|
extend Forwardable
|
8
9
|
|
10
|
+
attr_accessor :max_wait_time
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
# @see TTYtest.new_terminal
|
9
14
|
def initialize(driver_terminal, max_wait_time: nil)
|
10
15
|
@driver_terminal = driver_terminal
|
11
|
-
@max_wait_time = max_wait_time
|
16
|
+
@max_wait_time = max_wait_time || TTYtest.default_max_wait_time
|
12
17
|
end
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
# @!method send_keys(*keys)
|
20
|
+
# Simulate typing keys into the terminal
|
21
|
+
# @param [String] keys keys to send to the terminal
|
22
|
+
# @!method capture
|
23
|
+
# Capture the current state of the terminal
|
24
|
+
# @return [Capture] instantaneous state of the terminal when called
|
25
|
+
def_delegators :@driver_terminal, :send_keys, :capture
|
17
26
|
|
18
|
-
|
19
|
-
|
27
|
+
# @!method rows
|
28
|
+
# @return [Array<String>]
|
29
|
+
# @see Capture#rows
|
30
|
+
# @!method row(row)
|
31
|
+
# @return [String]
|
32
|
+
# @see Capture#row
|
33
|
+
# @!method width
|
34
|
+
# @see Capture#width
|
35
|
+
# @return [Integer]
|
36
|
+
# @!method height
|
37
|
+
# @see Capture#height
|
38
|
+
# @return [Integer]
|
39
|
+
# @!method cursor_x
|
40
|
+
# @see Capture#cursor_x
|
41
|
+
# @return [Integer]
|
42
|
+
# @!method cursor_y
|
43
|
+
# @see Capture#cursor_y
|
44
|
+
# @return [Integer]
|
45
|
+
# @!method cursor_visible?
|
46
|
+
# @see Capture#cursor_visible?
|
47
|
+
# @return [true,false]
|
48
|
+
# @!method cursor_hidden?
|
49
|
+
# @see Capture#cursor_hidden?
|
50
|
+
# @return [true,false]
|
51
|
+
def_delegators :capture, :rows, :row, :width, :height, :cursor_x, :cursor_y, :cursor_visible?, :cursor_hidden?
|
20
52
|
|
21
53
|
TTYtest::Matchers::METHODS.each do |matcher_name|
|
22
54
|
define_method matcher_name do |*args|
|
data/lib/ttytest/tmux/driver.rb
CHANGED
@@ -12,26 +12,33 @@ module TTYtest
|
|
12
12
|
COMMAND = 'tmux'
|
13
13
|
SOCKET_NAME = 'ttytest'
|
14
14
|
REQUIRED_TMUX_VERSION = '1.8'
|
15
|
-
|
15
|
+
DEFAULT_CONFING_FILE_PATH = File.expand_path('../tmux.conf', __FILE__)
|
16
16
|
SLEEP_INFINITY = 'read x < /dev/fd/1'
|
17
17
|
|
18
18
|
class TmuxError < StandardError; end
|
19
19
|
|
20
|
-
def initialize(
|
20
|
+
def initialize(
|
21
|
+
debug: false,
|
22
|
+
command: COMMAND,
|
23
|
+
socket_name: SOCKET_NAME,
|
24
|
+
config_file_path: DEFAULT_CONFING_FILE_PATH
|
25
|
+
)
|
21
26
|
@debug = debug
|
22
27
|
@tmux_cmd = command
|
23
28
|
@socket_name = socket_name
|
29
|
+
@config_file_path = config_file_path
|
24
30
|
end
|
25
31
|
|
26
32
|
def new_terminal(cmd, width: 80, height: 24)
|
27
33
|
cmd = "#{cmd}\n#{SLEEP_INFINITY}"
|
28
34
|
|
29
35
|
session_name = "ttytest-#{SecureRandom.uuid}"
|
30
|
-
tmux(*%W[-f #{
|
36
|
+
tmux(*%W[-f #{@config_file_path} new-session -s #{session_name} -d -x #{width} -y #{height} #{cmd}])
|
31
37
|
session = Session.new(self, session_name)
|
32
38
|
Terminal.new(session)
|
33
39
|
end
|
34
40
|
|
41
|
+
# @api private
|
35
42
|
def tmux(*args)
|
36
43
|
ensure_available
|
37
44
|
puts "tmux(#{args.inspect[1...-1]})" if debug?
|
@@ -45,18 +52,18 @@ module TTYtest
|
|
45
52
|
@available ||= (Gem::Version.new(tmux_version) >= Gem::Version.new(REQUIRED_TMUX_VERSION))
|
46
53
|
end
|
47
54
|
|
55
|
+
private
|
56
|
+
|
48
57
|
def debug?
|
49
58
|
@debug
|
50
59
|
end
|
51
60
|
|
52
|
-
private
|
53
|
-
|
54
61
|
def ensure_available
|
55
62
|
if !available?
|
56
63
|
if !tmux_version
|
57
|
-
raise TmuxError, "tmux
|
64
|
+
raise TmuxError, "Running `tmux -V` to determine version failed. Is tmux installed?"
|
58
65
|
else
|
59
|
-
raise TmuxError, "tmux version #{tmux_version} does not meet requirement >= #{REQUIRED_TMUX_VERSION}"
|
66
|
+
raise TmuxError, "tmux version #{tmux_version} does not meet requirement >= #{REQUIRED_TMUX_VERSION}"
|
60
67
|
end
|
61
68
|
end
|
62
69
|
end
|
data/lib/ttytest/tmux/session.rb
CHANGED
@@ -3,8 +3,7 @@
|
|
3
3
|
module TTYtest
|
4
4
|
module Tmux
|
5
5
|
class Session
|
6
|
-
|
7
|
-
|
6
|
+
# @api private
|
8
7
|
def initialize(driver, name)
|
9
8
|
@driver = driver
|
10
9
|
@name = name
|
@@ -12,14 +11,20 @@ module TTYtest
|
|
12
11
|
ObjectSpace.define_finalizer(self, self.class.finalize(driver, name))
|
13
12
|
end
|
14
13
|
|
14
|
+
# @api private
|
15
15
|
def self.finalize(driver, name)
|
16
16
|
proc { driver.tmux(*%W[kill-session -t #{name}]) }
|
17
17
|
end
|
18
18
|
|
19
19
|
def capture
|
20
20
|
contents = driver.tmux(*%W[capture-pane -t #{name} -p])
|
21
|
-
str = driver.tmux(*%W[display-message -t #{name} -p #\{cursor_x},#\{cursor_y},#\{cursor_flag},#\{pane_width},#\{pane_height}])
|
22
|
-
x, y, cursor_flag, width, height = str.split(',')
|
21
|
+
str = driver.tmux(*%W[display-message -t #{name} -p #\{cursor_x},#\{cursor_y},#\{cursor_flag},#\{pane_width},#\{pane_height},#\{pane_dead},#\{pane_dead_status},])
|
22
|
+
x, y, cursor_flag, width, height, pane_dead, pane_dead_status, _newline = str.split(',')
|
23
|
+
|
24
|
+
if pane_dead == "1"
|
25
|
+
raise Driver::TmuxError, "Tmux pane has died\nCommand exited with status: #{pane_dead_status}\nEntire screen:\n#{contents}"
|
26
|
+
end
|
27
|
+
|
23
28
|
TTYtest::Capture.new(
|
24
29
|
contents.chomp("\n"),
|
25
30
|
cursor_x: x.to_i,
|
@@ -33,6 +38,10 @@ module TTYtest
|
|
33
38
|
def send_keys(*keys)
|
34
39
|
driver.tmux(*%W[send-keys -t #{name} -l], *keys)
|
35
40
|
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :driver, :name
|
36
45
|
end
|
37
46
|
end
|
38
47
|
end
|
data/lib/ttytest/tmux/tmux.conf
CHANGED
data/lib/ttytest/version.rb
CHANGED
data/ttytest.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ttytest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Hawthorn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-02-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: yard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: TTYtest allows running applications inside of a terminal emulator (like
|
56
70
|
tmux) and making assertions on the output.
|
57
71
|
email:
|
@@ -67,7 +81,6 @@ files:
|
|
67
81
|
- Rakefile
|
68
82
|
- lib/ttytest.rb
|
69
83
|
- lib/ttytest/capture.rb
|
70
|
-
- lib/ttytest/dummy.rb
|
71
84
|
- lib/ttytest/matchers.rb
|
72
85
|
- lib/ttytest/terminal.rb
|
73
86
|
- lib/ttytest/tmux/driver.rb
|