ttytest 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/jhawthorn/ttytest.svg?branch=master)](https://travis-ci.org/jhawthorn/ttytest)
|
6
|
+
[![Gem Version](https://badge.fury.io/rb/ttytest.svg)](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
|