ttytest2 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3c87da11ef189a8f5c57cb248f6b141d00d43a64d27f62a2b5d00fb40974c765
4
+ data.tar.gz: 4dbc8af44a477b2c755c9653eb9411946447a295c1186f6f12a709b84dec3ee9
5
+ SHA512:
6
+ metadata.gz: 15737416cb6956c0dc04c07d77ba8970f06a38da74321bb888bb3565d60ef04e28b8b1bf1bf7304af3452a3c480ff32998ffcf339f00848782c5251ad0c3b606
7
+ data.tar.gz: c969a721dc64fec69889482759664b3c7fdba5585292663b470a8903b39336f2e12b6da353f3c40b6080d1c4f1b59e7e35b0c8deb046097dfe59ef5ce1179bc4
@@ -0,0 +1,18 @@
1
+ name: Test
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v4
10
+ - name: Set up Ruby 3.2.3
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 3.2.3
14
+ bundler-cache: true
15
+ - name: Build and test with Rake
16
+ run: |
17
+ bundle install --jobs 4 --retry 3
18
+ bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ Gemfile.lock
2
+ pkg/
3
+ .yardoc/
4
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # ttytest2
2
+
3
+ TTYtest2 is an acceptance test framework for interactive console applications. It's like [capybara](https://github.com/teamcapybara/capybara) for the terminal.
4
+
5
+ Forked from https://github.com/jhawthorn/ttytest, because I had some features I needed for my own project.
6
+
7
+ 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.
8
+
9
+ [![Gem Version](https://badge.fury.io/rb/ttytest.svg)](https://rubygems.org/gems/ttytest)
10
+
11
+ ## Minimum Requirements
12
+
13
+ * tmux >= 1.8
14
+ * Ruby >= 3.2.3
15
+
16
+ ## Usage
17
+
18
+ ### Example
19
+
20
+ ``` ruby
21
+ @tty = TTYtest.new_terminal(%{PS1='$ ' /bin/sh}, width: 80, height: 24)
22
+ @tty.assert_row(0, '$')
23
+ @tty.assert_cursor_position(x: 2, y: 0)
24
+
25
+ @tty.send_keys(%{echo "Hello, world"\n})
26
+
27
+ @tty.assert_contents <<TTY
28
+ $ echo "Hello, world"
29
+ Hello, world
30
+ $
31
+ TTY
32
+ @tty.assert_cursor_position(x: 2, y: 2)
33
+
34
+ p @tty.rows # => ["$ echo \"Hello, world\"", "Hello, world", "$", "", "", "", ...]
35
+ ```
36
+
37
+ ### Assertions
38
+
39
+ 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).
40
+
41
+ Available assertions:
42
+ * `assert_row(row_number, expected_text)`
43
+ * `assert_row_like(row_number, expected_text)`
44
+ * `assert_cursor_position(x: x, y: y)`
45
+ * `assert_cursor_visible`
46
+ * `assert_cursor_hidden`
47
+ * `assert_contents(lines_of_terminal)`
48
+
49
+ ## Docker
50
+
51
+ Easy to use from Docker. Add this to your dockerfile to get started.
52
+
53
+ ``` dockerfile
54
+ RUN apt update && \
55
+ apt install gcc make ruby tmux -y && \
56
+ gem install ttytest2
57
+ ```
58
+
59
+ ## TravisCI
60
+
61
+ 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.
62
+
63
+ Ensure the following is in your `.travis.yml` (see [this project's .travis.yml](./.travis.yml) for an example)
64
+
65
+ ``` yaml
66
+ dist: trusty
67
+ addons:
68
+ apt:
69
+ packages:
70
+ - tmux
71
+ ```
72
+
73
+ ## Contributing
74
+
75
+ Bug reports and pull requests are welcome on GitHub at https://github.com/a-eski/ttytest2.
76
+
77
+ ## License
78
+
79
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+ require 'yard'
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << 'test'
7
+ t.libs << 'lib'
8
+ t.test_files = FileList['test/**/*_test.rb']
9
+ end
10
+
11
+ YARD::Rake::YardocTask.new do |t|
12
+ t.files = ['lib/**/*.rb']
13
+ end
14
+
15
+ task :console do
16
+ system 'irb -Ilib -rttytest'
17
+ end
18
+
19
+ task default: :test
@@ -0,0 +1,54 @@
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
7
+ class Capture
8
+ include TTYtest::Matchers
9
+
10
+ attr_reader :cursor_x, :cursor_y, :width, :height
11
+
12
+ # Used internally by drivers when called by {Terminal#capture}
13
+ # @api private
14
+ def initialize(contents, cursor_x: 0, cursor_y: 0, width: nil, height: nil, cursor_visible: true)
15
+ @rows = "#{contents}\nEND".split("\n")[0...-1].map do |row|
16
+ row || ''
17
+ end
18
+ @cursor_x = cursor_x
19
+ @cursor_y = cursor_y
20
+ @width = width
21
+ @height = height
22
+ @cursor_visible = cursor_visible
23
+ end
24
+
25
+ # @return [Array<String>] An array of each row's contend from the captured terminal
26
+ attr_reader :rows
27
+
28
+ # @param [Integer] the row to return
29
+ # @return [String] the content of the row from the captured terminal
30
+ def row(row_number)
31
+ rows[row_number]
32
+ end
33
+
34
+ # @return [true,false] Whether the cursor is visible in the captured terminal
35
+ def cursor_visible?
36
+ @cursor_visible
37
+ end
38
+
39
+ # @return [true,false] Whether the cursor is hidden in the captured terminal
40
+ def cursor_hidden?
41
+ !cursor_visible?
42
+ end
43
+
44
+ # @return [Capture] returns self
45
+ def capture
46
+ self
47
+ end
48
+
49
+ # @return [String] All rows of the captured terminal, separated by newlines
50
+ def to_s
51
+ rows.join("\n")
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,84 @@
1
+ module TTYtest
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
7
+ def assert_row(row_number, expected)
8
+ expected = expected.rstrip
9
+ actual = row(row_number)
10
+ return unless actual != expected
11
+
12
+ raise MatchError,
13
+ "expected row #{row_number} to be #{expected.inspect} but got #{actual.inspect}\nEntire screen:\n#{self}"
14
+ end
15
+
16
+ # Asserts the contents of a single row
17
+ # @param [Integer] row_number the row (starting from 0) to test against
18
+ # @param [String] expected the expected value of the row. Any trailing whitespace is ignored
19
+ # @raise [MatchError] if the row doesn't match
20
+ def assert_row_like(row_number, expected)
21
+ raise MatchError, "expected a value for 'expected' but recieved nil" if actual.nil?
22
+
23
+ expected = expected.rstrip
24
+ actual = row(row_number)
25
+ return if actual.include?(expected)
26
+
27
+ raise MatchError,
28
+ "expected row #{row_number} to be like #{expected.inspect} but got #{actual.inspect}\nEntire screen:\n#{self}"
29
+ end
30
+
31
+ # Asserts that the cursor is in the expected position
32
+ # @param [Integer] x cursor x (row) position, starting from 0
33
+ # @param [Integer] y cursor y (column) position, starting from 0
34
+ # @raise [MatchError] if the cursor position doesn't match
35
+ def assert_cursor_position(x, y)
36
+ expected = [x, y]
37
+ actual = [cursor_x, cursor_y]
38
+ return unless actual != expected
39
+
40
+ raise MatchError,
41
+ "expected cursor to be at #{expected.inspect} but was at #{actual.inspect}\nEntire screen:\n#{self}"
42
+ end
43
+
44
+ # @raise [MatchError] if the cursor is hidden
45
+ def assert_cursor_visible
46
+ return if cursor_visible?
47
+
48
+ raise MatchError, "expected cursor to be visible was hidden\nEntire screen:\n#{self}"
49
+ end
50
+
51
+ # @raise [MatchError] if the cursor is visible
52
+ def assert_cursor_hidden
53
+ return if cursor_hidden?
54
+
55
+ raise MatchError, "expected cursor to be hidden was visible\nEntire screen:\n#{self}"
56
+ end
57
+
58
+ # Asserts the full contents of the terminal
59
+ # @param [String] expected the full expected contents of the terminal. Trailing whitespace on each line is ignored
60
+ # @raise [MatchError] if the terminal doesn't match the expected content
61
+ def assert_contents(expected)
62
+ expected_rows = expected.split("\n")
63
+ diff = []
64
+ matched = true
65
+ rows.each_with_index do |actual_row, index|
66
+ expected_row = (expected_rows[index] || '').rstrip
67
+ if actual_row != expected_row
68
+ diff << "-#{expected_row}"
69
+ diff << "+#{actual_row}"
70
+ matched = false
71
+ else
72
+ diff << " #{actual_row}".rstrip
73
+ end
74
+ end
75
+
76
+ return if matched
77
+
78
+ raise MatchError, "screen did not match expected content:\n--- expected\n+++ actual\n#{diff.join("\n")}"
79
+ end
80
+ alias assert_matches assert_contents
81
+
82
+ METHODS = public_instance_methods
83
+ end
84
+ end
@@ -0,0 +1,75 @@
1
+ require 'forwardable'
2
+ require 'ttytest/matchers'
3
+ require 'ttytest/capture'
4
+
5
+ module TTYtest
6
+ # @attr [Integer] max_wait_time the maximum amount of time (in seconds) to retry assertions before failing.
7
+ class Terminal
8
+ extend Forwardable
9
+
10
+ attr_accessor :max_wait_time
11
+
12
+ # @api private
13
+ # @see TTYtest.new_terminal
14
+ def initialize(driver_terminal, max_wait_time: nil)
15
+ @driver_terminal = driver_terminal
16
+ @max_wait_time = max_wait_time || TTYtest.default_max_wait_time
17
+ end
18
+
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
26
+
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?
52
+
53
+ TTYtest::Matchers::METHODS.each do |matcher_name|
54
+ define_method matcher_name do |*args|
55
+ synchronize do
56
+ capture.public_send(matcher_name, *args)
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def synchronize(seconds = max_wait_time)
64
+ start_time = Time.now
65
+ begin
66
+ yield
67
+ rescue MatchError => e
68
+ raise e if (Time.now - start_time) >= seconds
69
+
70
+ sleep 0.05
71
+ retry
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,78 @@
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
+ DEFAULT_CONFING_FILE_PATH = File.expand_path('tmux.conf', __dir__)
16
+ SLEEP_INFINITY = 'read x < /dev/fd/1'
17
+
18
+ class TmuxError < StandardError; end
19
+
20
+ def initialize(
21
+ debug: false,
22
+ command: COMMAND,
23
+ socket_name: SOCKET_NAME,
24
+ config_file_path: DEFAULT_CONFING_FILE_PATH
25
+ )
26
+ @debug = debug
27
+ @tmux_cmd = command
28
+ @socket_name = socket_name
29
+ @config_file_path = config_file_path
30
+ end
31
+
32
+ def new_terminal(cmd, width: 80, height: 24)
33
+ cmd = "#{cmd}\n#{SLEEP_INFINITY}"
34
+
35
+ session_name = "ttytest-#{SecureRandom.uuid}"
36
+ tmux(*%W[-f #{@config_file_path} new-session -s #{session_name} -d -x #{width} -y #{height} #{cmd}])
37
+ session = Session.new(self, session_name)
38
+ Terminal.new(session)
39
+ end
40
+
41
+ # @api private
42
+ def tmux(*args)
43
+ ensure_available
44
+ puts "tmux(#{args.inspect[1...-1]})" if debug?
45
+
46
+ stdout, stderr, status = Open3.capture3(@tmux_cmd, '-L', SOCKET_NAME, *args)
47
+ raise TmuxError, "tmux(#{args.inspect[1...-1]}) failed\n#{stderr}" unless status.success?
48
+
49
+ stdout
50
+ end
51
+
52
+ def available?
53
+ return false unless tmux_version
54
+
55
+ @available ||= (Gem::Version.new(tmux_version) >= Gem::Version.new(REQUIRED_TMUX_VERSION))
56
+ end
57
+
58
+ private
59
+
60
+ def debug?
61
+ @debug
62
+ end
63
+
64
+ def ensure_available
65
+ return if available?
66
+ raise TmuxError, 'Running `tmux -V` to determine version failed. Is tmux installed?' unless tmux_version
67
+
68
+ raise TmuxError, "tmux version #{tmux_version} does not meet requirement >= #{REQUIRED_TMUX_VERSION}"
69
+ end
70
+
71
+ def tmux_version
72
+ @tmux_version ||= `#{@tmux_cmd} -V`[/tmux (\d+.\d+)/, 1]
73
+ rescue Errno::ENOENT
74
+ nil
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTYtest
4
+ module Tmux
5
+ class Session
6
+ # @api private
7
+ def initialize(driver, name)
8
+ @driver = driver
9
+ @name = name
10
+
11
+ ObjectSpace.define_finalizer(self, self.class.finalize(driver, name))
12
+ end
13
+
14
+ # @api private
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
22
+ #\{cursor_x},#\{cursor_y},#\{cursor_flag},#\{pane_width},#\{pane_height},#\{pane_dead},#\{pane_dead_status},])
23
+ x, y, cursor_flag, width, height, pane_dead, pane_dead_status, _newline = str.split(',')
24
+
25
+ if pane_dead == '1'
26
+ raise Driver::TmuxError,
27
+ "Tmux pane has died\nCommand exited with status: #{pane_dead_status}\nEntire screen:\n#{contents}"
28
+ end
29
+
30
+ TTYtest::Capture.new(
31
+ contents.chomp("\n"),
32
+ cursor_x: x.to_i,
33
+ cursor_y: y.to_i,
34
+ width: width.to_i,
35
+ height: height.to_i,
36
+ cursor_visible: (cursor_flag != '0')
37
+ )
38
+ end
39
+
40
+ def send_keys(*keys)
41
+ driver.tmux(*%W[send-keys -t #{name} -l], *keys)
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :driver, :name
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ set -g status off
2
+ set -g remain-on-exit on
3
+ set -g default-shell /bin/sh
@@ -0,0 +1,3 @@
1
+ module TTYtest
2
+ VERSION = '0.7.0'.freeze
3
+ end
data/lib/ttytest.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'forwardable'
2
+ require 'ttytest/tmux/driver'
3
+ require 'ttytest/tmux/session'
4
+
5
+ module TTYtest
6
+ class << self
7
+ attr_accessor :driver, :default_max_wait_time
8
+
9
+ extend Forwardable
10
+ # @!method new_terminal(command, width: 80, height: 24)
11
+ # Create a new terminal through the current driver.
12
+ # @param [String] command a valid shell command to run
13
+ # @param [Integer] width width of the new terminal
14
+ # @param [Integer] height height of the new terminal
15
+ # @return [Terminal] a new terminal running the specified command
16
+ def_delegators :driver, :new_terminal
17
+ end
18
+
19
+ class MatchError < StandardError; end
20
+
21
+ self.driver = TTYtest::Tmux::Driver.new
22
+ self.default_max_wait_time = 2
23
+ end
data/notes.txt ADDED
@@ -0,0 +1,7 @@
1
+ to push new version to github,
2
+ git tag v0.7.0
3
+ git push origin --tags
4
+
5
+ to push new version to rubygems.org
6
+ gem build ttytest2.gemspec
7
+ gem push ttytest2-0.7.0.gem
data/ttytest2.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'ttytest/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'ttytest2'
7
+ spec.version = TTYtest::VERSION
8
+ spec.authors = ['Alex Eski']
9
+ spec.email = ['alexeski@gmail.com']
10
+
11
+ spec.summary = 'ttytest2 is an integration test framework for interactive tty applications. Based on TTYtest!'
12
+ spec.description = 'ttytest2 allows running shell and/or cli applications inside of tmux and then making assertions on the output.'
13
+ spec.homepage = 'https://github.com/a-eski/ttytest2'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.required_ruby_version = '>= 3.2.3'
24
+
25
+ spec.add_development_dependency 'bundler'
26
+ spec.add_development_dependency 'minitest', '~> 5.0'
27
+ spec.add_development_dependency 'rake', '~> 13.0'
28
+ spec.add_development_dependency 'yard'
29
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ttytest2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.0
5
+ platform: ruby
6
+ authors:
7
+ - Alex Eski
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-11-13 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: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.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'
69
+ description: ttytest2 allows running shell and/or cli applications inside of tmux
70
+ and then making assertions on the output.
71
+ email:
72
+ - alexeski@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".github/workflows/test.yml"
78
+ - ".gitignore"
79
+ - Gemfile
80
+ - README.md
81
+ - Rakefile
82
+ - lib/ttytest.rb
83
+ - lib/ttytest/capture.rb
84
+ - lib/ttytest/matchers.rb
85
+ - lib/ttytest/terminal.rb
86
+ - lib/ttytest/tmux/driver.rb
87
+ - lib/ttytest/tmux/session.rb
88
+ - lib/ttytest/tmux/tmux.conf
89
+ - lib/ttytest/version.rb
90
+ - notes.txt
91
+ - ttytest2.gemspec
92
+ homepage: https://github.com/a-eski/ttytest2
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 3.2.3
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubygems_version: 3.4.20
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: ttytest2 is an integration test framework for interactive tty applications.
115
+ Based on TTYtest!
116
+ test_files: []