ttytest 0.1.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 +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +14 -0
- data/Gemfile +3 -0
- data/README.md +47 -0
- data/Rakefile +10 -0
- data/lib/ttytest.rb +14 -0
- data/lib/ttytest/capture.rb +36 -0
- data/lib/ttytest/dummy.rb +15 -0
- data/lib/ttytest/matchers.rb +20 -0
- data/lib/ttytest/terminal.rb +40 -0
- data/lib/ttytest/tmux/driver.rb +67 -0
- data/lib/ttytest/tmux/session.rb +36 -0
- data/lib/ttytest/version.rb +3 -0
- data/ttytest.gemspec +27 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 58e7cfbcc044cae295f1667619da61dbff469b00
|
4
|
+
data.tar.gz: b7a3fde532a3a2eb07098e648fefb472a7996d3f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9f99ff25102a617841af7ede0537860c71a431826082666d3fe605d55fad9208ff2c2edcc3eb6d2ee505bf3d56f290f52c287c0ca505e53e0ba90f5bf3f99404
|
7
|
+
data.tar.gz: a9acb9d56b24443740c51df19f338aaa77279e56876b4ac170976922605d783db214d7ae6f829e0d06e2d2750e802bc71e3c7d8aee3a84a76dc13916127f8efe
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
TTYtest is an integration test framework for interactive tty applications. It's like [capybara](https://github.com/teamcapybara/capybara) for the terminal.
|
2
|
+
|
3
|
+
[](https://travis-ci.org/jhawthorn/ttytest)
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
|
7
|
+
* tmux >= 1.8
|
8
|
+
* Ruby >= 2.1
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
``` ruby
|
13
|
+
class TTYtestTest < Minitest::Test
|
14
|
+
def test_shell_hello_world
|
15
|
+
@tty = TTYtest.driver.new_terminal(%{PS1='$ ' /bin/sh})
|
16
|
+
@tty.assert_row(0, '$')
|
17
|
+
|
18
|
+
@tty.send_raw('echo "Hello, world"', "\n")
|
19
|
+
|
20
|
+
@tty.assert_row(0, '$ echo "Hello, world"')
|
21
|
+
@tty.assert_row(1, 'Hello, world')
|
22
|
+
@tty.assert_cursor_position(2, 2)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
## TravisCI
|
28
|
+
|
29
|
+
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.
|
30
|
+
|
31
|
+
Ensure the following is in your `.travis.yml` (see [this project's .travis.yml](./.travis.yml) for an example)
|
32
|
+
|
33
|
+
``` yaml
|
34
|
+
dist: trusty
|
35
|
+
addons:
|
36
|
+
apt:
|
37
|
+
packages:
|
38
|
+
- tmux
|
39
|
+
```
|
40
|
+
|
41
|
+
## Contributing
|
42
|
+
|
43
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jhawthorn/execjs-fastnode.
|
44
|
+
|
45
|
+
## License
|
46
|
+
|
47
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/lib/ttytest.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'ttytest/tmux/driver'
|
2
|
+
require 'ttytest/tmux/session'
|
3
|
+
|
4
|
+
module TTYtest
|
5
|
+
class << self
|
6
|
+
attr_accessor :driver
|
7
|
+
attr_accessor :default_max_wait_time
|
8
|
+
end
|
9
|
+
|
10
|
+
class MatchError < StandardError; end
|
11
|
+
|
12
|
+
self.driver = TTYtest::Tmux::Driver.new
|
13
|
+
self.default_max_wait_time = 2
|
14
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module TTYtest
|
2
|
+
class Capture
|
3
|
+
include TTYtest::Matchers
|
4
|
+
|
5
|
+
attr_reader :cursor_x, :cursor_y, :cursor_visible
|
6
|
+
|
7
|
+
def initialize(contents, cursor_x: 0, cursor_y: 0, cursor_visible: true)
|
8
|
+
@rows = (contents+"\nEND").split("\n")[0...-1].map do |row|
|
9
|
+
row || ""
|
10
|
+
end
|
11
|
+
@cursor_x = cursor_x
|
12
|
+
@cursor_y = cursor_y
|
13
|
+
@cursor_visible = cursor_visible
|
14
|
+
end
|
15
|
+
|
16
|
+
def cursor_position
|
17
|
+
[@cursor_x, @cursor_y]
|
18
|
+
end
|
19
|
+
|
20
|
+
def rows
|
21
|
+
@rows
|
22
|
+
end
|
23
|
+
|
24
|
+
def row(row)
|
25
|
+
rows[row]
|
26
|
+
end
|
27
|
+
|
28
|
+
def capture
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
rows.join("\n")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module TTYtest
|
2
|
+
class Dummy
|
3
|
+
attr_accessor :contents, :cursor_position
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@contents = "\n"*23
|
7
|
+
@cursor_position = [0,0]
|
8
|
+
end
|
9
|
+
|
10
|
+
def capture
|
11
|
+
x, y = cursor_position
|
12
|
+
Capture.new(contents, cursor_x: x, cursor_y: y)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module TTYtest
|
2
|
+
module Matchers
|
3
|
+
def assert_row(row_number, expected)
|
4
|
+
actual = row(row_number)
|
5
|
+
if actual != expected
|
6
|
+
raise MatchError, "expected row #{row_number} to be #{expected.inspect} but got #{actual.inspect}\nEntire screen:\n#{to_s}"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def assert_cursor_position(x, y)
|
11
|
+
expected = [x, y]
|
12
|
+
actual = cursor_position
|
13
|
+
if actual != expected
|
14
|
+
raise MatchError, "expected cursor to be at #{expected.inspect} but was at #{actual.inspect}\nEntire screen:\n#{to_s}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
METHODS = public_instance_methods
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'ttytest/matchers'
|
3
|
+
require 'ttytest/capture'
|
4
|
+
|
5
|
+
module TTYtest
|
6
|
+
class Terminal
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def initialize(driver_terminal, max_wait_time: nil)
|
10
|
+
@driver_terminal = driver_terminal
|
11
|
+
@max_wait_time = max_wait_time
|
12
|
+
end
|
13
|
+
|
14
|
+
def max_wait_time
|
15
|
+
@max_wait_time || TTYtest.default_max_wait_time
|
16
|
+
end
|
17
|
+
|
18
|
+
def_delegators :@driver_terminal, :send_keys, :send_raw, :capture
|
19
|
+
def_delegators :capture, :rows, :row, :cursor_position
|
20
|
+
|
21
|
+
def synchronize(seconds=max_wait_time)
|
22
|
+
start_time = Time.now
|
23
|
+
begin
|
24
|
+
yield
|
25
|
+
rescue MatchError => e
|
26
|
+
raise e if (Time.now - start_time) >= seconds
|
27
|
+
sleep 0.05
|
28
|
+
retry
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
TTYtest::Matchers::METHODS.each do |matcher_name|
|
33
|
+
define_method matcher_name do |*args|
|
34
|
+
synchronize do
|
35
|
+
capture.public_send(matcher_name, *args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
require 'ttytest/terminal'
|
7
|
+
require 'ttytest/tmux/session'
|
8
|
+
|
9
|
+
module TTYtest
|
10
|
+
module Tmux
|
11
|
+
class Driver
|
12
|
+
COMMAND = 'tmux'
|
13
|
+
SOCKET_NAME = 'ttytest'
|
14
|
+
REQUIRED_TMUX_VERSION = '1.8'
|
15
|
+
|
16
|
+
class TmuxError < StandardError; end
|
17
|
+
|
18
|
+
def initialize(debug: false, command: COMMAND, socket_name: SOCKET_NAME)
|
19
|
+
@debug = debug
|
20
|
+
@tmux_cmd = command
|
21
|
+
@socket_name = socket_name
|
22
|
+
end
|
23
|
+
|
24
|
+
def new_terminal(cmd, width: 80, height: 24)
|
25
|
+
session_name = "ttytest-#{SecureRandom.uuid}"
|
26
|
+
tmux(*%W[new-session -s #{session_name} -d -x #{width} -y #{height} #{cmd}])
|
27
|
+
session = Session.new(self, session_name)
|
28
|
+
Terminal.new(session)
|
29
|
+
end
|
30
|
+
|
31
|
+
def tmux(*args)
|
32
|
+
ensure_available
|
33
|
+
puts "tmux(#{args.inspect[1...-1]})" if debug?
|
34
|
+
|
35
|
+
stdout, stderr, status = Open3.capture3(@tmux_cmd, '-L', SOCKET_NAME, *args)
|
36
|
+
raise TmuxError, "tmux(#{args.inspect[1...-1]}) failed\n#{stderr}" unless status.success?
|
37
|
+
stdout
|
38
|
+
end
|
39
|
+
|
40
|
+
def available?
|
41
|
+
@available ||= (Gem::Version.new(tmux_version) >= Gem::Version.new(REQUIRED_TMUX_VERSION))
|
42
|
+
end
|
43
|
+
|
44
|
+
def debug?
|
45
|
+
@debug
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def ensure_available
|
51
|
+
if !available?
|
52
|
+
if !tmux_version
|
53
|
+
raise TmuxError, "tmux doesn't seem to be unstalled" unless available?
|
54
|
+
else
|
55
|
+
raise TmuxError, "tmux version #{tmux_version} does not meet requirement >= #{REQUIRED_TMUX_VERSION}" unless available?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def tmux_version
|
61
|
+
@tmux_version ||= `#{@tmux_cmd} -V`[/tmux (\d+.\d+)/, 1]
|
62
|
+
rescue Errno::ENOENT
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTYtest
|
4
|
+
module Tmux
|
5
|
+
class Session
|
6
|
+
attr_reader :driver, :name
|
7
|
+
|
8
|
+
def initialize(driver, name)
|
9
|
+
@driver = driver
|
10
|
+
@name = name
|
11
|
+
|
12
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(driver, name))
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.finalize(driver, name)
|
16
|
+
proc { driver.tmux(*%W[kill-session -t #{name}]) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def capture
|
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}])
|
22
|
+
x, y, cursor_flag = str.split(',').map(&:to_i)
|
23
|
+
cursor_visible = (cursor_flag != 0)
|
24
|
+
TTYtest::Capture.new(contents, cursor_x: x, cursor_y: y, cursor_visible: cursor_visible)
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_keys(*keys)
|
28
|
+
driver.tmux(*%W[send-keys -t #{name}], *keys)
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_raw(*keys)
|
32
|
+
driver.tmux(*%W[send-keys -t #{name} -l], *keys)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/ttytest.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ttytest/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ttytest"
|
8
|
+
spec.version = TTYtest::VERSION
|
9
|
+
spec.authors = ["John Hawthorn"]
|
10
|
+
spec.email = ["john.hawthorn@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{TTYtest is an integration test framework for interactive tty applications}
|
13
|
+
spec.description = %q{TTYtest allows running applications inside of a terminal emulator (like tmux) and making assertions on the output.}
|
14
|
+
spec.homepage = "https://github.com/jhawthorn/ttytest"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ttytest
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Hawthorn
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
description: TTYtest allows running applications inside of a terminal emulator (like
|
56
|
+
tmux) and making assertions on the output.
|
57
|
+
email:
|
58
|
+
- john.hawthorn@gmail.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- lib/ttytest.rb
|
69
|
+
- lib/ttytest/capture.rb
|
70
|
+
- lib/ttytest/dummy.rb
|
71
|
+
- lib/ttytest/matchers.rb
|
72
|
+
- lib/ttytest/terminal.rb
|
73
|
+
- lib/ttytest/tmux/driver.rb
|
74
|
+
- lib/ttytest/tmux/session.rb
|
75
|
+
- lib/ttytest/version.rb
|
76
|
+
- ttytest.gemspec
|
77
|
+
homepage: https://github.com/jhawthorn/ttytest
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.5.2
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: TTYtest is an integration test framework for interactive tty applications
|
101
|
+
test_files: []
|