jet_black 0.4.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +21 -2
- data/CHANGELOG.md +25 -1
- data/Dockerfile +1 -5
- data/Gemfile +2 -0
- data/Gemfile.lock +29 -35
- data/README.md +52 -8
- data/Rakefile +2 -0
- data/jet_black.gemspec +2 -2
- data/lib/jet_black.rb +2 -0
- data/lib/jet_black/configuration.rb +2 -0
- data/lib/jet_black/environment.rb +5 -3
- data/lib/jet_black/errors.rb +15 -0
- data/lib/jet_black/executed_command.rb +3 -1
- data/lib/jet_black/file_helper.rb +2 -0
- data/lib/jet_black/interactive_command.rb +24 -0
- data/lib/jet_black/non_interactive_command.rb +21 -0
- data/lib/jet_black/rspec.rb +2 -0
- data/lib/jet_black/rspec/matchers.rb +2 -0
- data/lib/jet_black/session.rb +34 -19
- data/lib/jet_black/terminal_session.rb +92 -0
- data/lib/jet_black/version.rb +3 -1
- metadata +23 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '059fc435c59412f13bc2fe11be1e7d9c83c691bdb1ff64dcc1851de10285ddcd'
|
4
|
+
data.tar.gz: f5b67925853b78170d6129745c7bbdc72a855c6a263a3f89c73e16878e8cf3bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20c2774abd978220ed19c4c0cd6e7992c409d8deb1f257eab013f2b6ec6e5002b09dbff7e380e67c65aa5d0553b2d6c2b88b4ad7fb56c02847873a7e6215d55a
|
7
|
+
data.tar.gz: 459626286ba5ca1f8b6c76c20f19268fc4c994519a9c493fde2011157f14ce2843379e75dd83e2f3845ba4a62c39ee8c5f77f41f4e118c97b2900e89c9ef72d1
|
data/.circleci/config.yml
CHANGED
@@ -16,7 +16,9 @@ base_job: &base_job
|
|
16
16
|
|
17
17
|
- run:
|
18
18
|
name: install dependencies
|
19
|
-
command:
|
19
|
+
command: |
|
20
|
+
gem install bundler
|
21
|
+
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
20
22
|
|
21
23
|
- save_cache:
|
22
24
|
paths:
|
@@ -49,14 +51,31 @@ jobs:
|
|
49
51
|
<<: *base_job
|
50
52
|
docker:
|
51
53
|
- image: circleci/ruby:2.5
|
54
|
+
ruby-2.6:
|
55
|
+
<<: *base_job
|
56
|
+
docker:
|
57
|
+
- image: circleci/ruby:2.6
|
58
|
+
ruby-2.7:
|
59
|
+
<<: *base_job
|
60
|
+
docker:
|
61
|
+
- image: circleci/ruby:2.7
|
52
62
|
environment:
|
53
63
|
ENABLE_COVERAGE: 1
|
64
|
+
ruby-3.0:
|
65
|
+
<<: *base_job
|
66
|
+
docker:
|
67
|
+
- image: circleci/ruby:3.0
|
54
68
|
|
55
69
|
#-------------------------------------------------------------------------------
|
56
70
|
|
71
|
+
# TODO: Migrate to CircleCI matrix
|
72
|
+
|
57
73
|
workflows:
|
58
74
|
version: 2
|
59
75
|
multiple-rubies:
|
60
76
|
jobs:
|
61
|
-
- ruby-2.5
|
62
77
|
- ruby-2.4
|
78
|
+
- ruby-2.5
|
79
|
+
- ruby-2.6
|
80
|
+
- ruby-2.7
|
81
|
+
- ruby-3.0
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,32 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## v0.7.1
|
4
|
+
|
5
|
+
- Fix file descriptor leak in `run_interactive`
|
6
|
+
|
7
|
+
## v0.7.0
|
8
|
+
|
9
|
+
- Adds `run_interactive` to allow pseudo-terminal interaction
|
10
|
+
|
11
|
+
## v0.6.0
|
12
|
+
|
13
|
+
- Freeze string literals
|
14
|
+
- Fix deprecation warning: `Bundler.with_clean_env` has been deprecated in
|
15
|
+
favor of `Bundler.with_unbundled_env`
|
16
|
+
|
17
|
+
## v0.5.1
|
18
|
+
|
19
|
+
- Fix missing `bundler` require - thanks @lpender via [#6][pr-6]
|
20
|
+
|
21
|
+
[pr-6]: https://github.com/odlp/jet_black/pull/6
|
22
|
+
|
23
|
+
## v0.5.0
|
24
|
+
|
25
|
+
- `stdout` and `stderr` now keep any trailing newlines at the end of the string.
|
26
|
+
|
3
27
|
## v0.4.0
|
4
28
|
|
5
|
-
- Scrub ANSI escape sequences in stdout and stderr
|
29
|
+
- Scrub ANSI escape sequences in `stdout` and `stderr`
|
6
30
|
- Allow environment variables to be unset
|
7
31
|
|
8
32
|
## v0.3.0
|
data/Dockerfile
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,69 +1,63 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
jet_black (0.
|
4
|
+
jet_black (0.7.1)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
coderay (1.1.2)
|
10
|
-
coveralls (0.
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
coveralls (0.8.22)
|
11
|
+
json (>= 1.8, < 3)
|
12
|
+
simplecov (~> 0.16.1)
|
13
|
+
term-ansicolor (~> 1.3)
|
14
|
+
thor (~> 0.19.4)
|
15
|
+
tins (~> 1.6)
|
16
16
|
diff-lcs (1.3)
|
17
|
-
docile (1.3.
|
18
|
-
json (2.1
|
17
|
+
docile (1.3.1)
|
18
|
+
json (2.3.1)
|
19
19
|
method_source (0.9.0)
|
20
|
-
mime-types (3.1)
|
21
|
-
mime-types-data (~> 3.2015)
|
22
|
-
mime-types-data (3.2016.0521)
|
23
|
-
multi_json (1.13.1)
|
24
20
|
pry (0.11.3)
|
25
21
|
coderay (~> 1.1.0)
|
26
22
|
method_source (~> 0.9.0)
|
27
|
-
rake (
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
rspec-
|
32
|
-
|
33
|
-
rspec-
|
34
|
-
rspec-
|
35
|
-
rspec-support (~> 3.7.0)
|
36
|
-
rspec-expectations (3.7.0)
|
23
|
+
rake (13.0.1)
|
24
|
+
rspec (3.8.0)
|
25
|
+
rspec-core (~> 3.8.0)
|
26
|
+
rspec-expectations (~> 3.8.0)
|
27
|
+
rspec-mocks (~> 3.8.0)
|
28
|
+
rspec-core (3.8.0)
|
29
|
+
rspec-support (~> 3.8.0)
|
30
|
+
rspec-expectations (3.8.1)
|
37
31
|
diff-lcs (>= 1.2.0, < 2.0)
|
38
|
-
rspec-support (~> 3.
|
39
|
-
rspec-mocks (3.
|
32
|
+
rspec-support (~> 3.8.0)
|
33
|
+
rspec-mocks (3.8.0)
|
40
34
|
diff-lcs (>= 1.2.0, < 2.0)
|
41
|
-
rspec-support (~> 3.
|
42
|
-
rspec-support (3.
|
43
|
-
rspec_junit_formatter (0.
|
35
|
+
rspec-support (~> 3.8.0)
|
36
|
+
rspec-support (3.8.0)
|
37
|
+
rspec_junit_formatter (0.4.1)
|
44
38
|
rspec-core (>= 2, < 4, != 2.12.0)
|
45
39
|
simplecov (0.16.1)
|
46
40
|
docile (~> 1.1)
|
47
41
|
json (>= 1.8, < 3)
|
48
42
|
simplecov-html (~> 0.10.0)
|
49
43
|
simplecov-html (0.10.2)
|
50
|
-
term-ansicolor (1.
|
51
|
-
tins (~> 0
|
52
|
-
thor (0.
|
53
|
-
tins (
|
44
|
+
term-ansicolor (1.6.0)
|
45
|
+
tins (~> 1.0)
|
46
|
+
thor (0.19.4)
|
47
|
+
tins (1.16.3)
|
54
48
|
|
55
49
|
PLATFORMS
|
56
50
|
ruby
|
57
51
|
|
58
52
|
DEPENDENCIES
|
59
|
-
bundler (
|
53
|
+
bundler (>= 1.16, < 3)
|
60
54
|
coveralls
|
61
55
|
jet_black!
|
62
56
|
pry
|
63
|
-
rake (
|
57
|
+
rake (>= 12.0)
|
64
58
|
rspec (~> 3.0)
|
65
59
|
rspec_junit_formatter
|
66
60
|
simplecov
|
67
61
|
|
68
62
|
BUNDLED WITH
|
69
|
-
1.
|
63
|
+
2.1.4
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# JetBlack
|
2
2
|
|
3
|
-
[![CircleCI](https://circleci.com/gh/odlp/jet_black.svg?style=
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/jet_black.svg)](https://rubygems.org/gems/jet_black) [![CircleCI](https://circleci.com/gh/odlp/jet_black.svg?style=shield)](https://circleci.com/gh/odlp/jet_black) [![Coverage Status](https://coveralls.io/repos/github/odlp/jet_black/badge.svg?branch=master)](https://coveralls.io/github/odlp/jet_black?branch=master)
|
4
4
|
|
5
5
|
A black-box testing utility for command line tools and gems. Written in Ruby,
|
6
6
|
with [RSpec] in mind. Features:
|
@@ -11,6 +11,7 @@ with [RSpec] in mind. Features:
|
|
11
11
|
- Synchronously [run commands](#running-commands) then write assertions on:
|
12
12
|
- The `stdout` / `stderr` content
|
13
13
|
- The exit status of the process
|
14
|
+
- Exercise [interactive command line interfaces](#running-interactive-commands)
|
14
15
|
- Manipulate files in the temporary directory:
|
15
16
|
- [Create files](#file-manipulation)
|
16
17
|
- [Create executable files](#file-manipulation)
|
@@ -73,7 +74,7 @@ require "jet_black"
|
|
73
74
|
session = JetBlack::Session.new
|
74
75
|
result = session.run("echo foo")
|
75
76
|
|
76
|
-
result.stdout # => "foo"
|
77
|
+
result.stdout # => "foo\n"
|
77
78
|
result.stderr # => ""
|
78
79
|
result.exit_status # => 0
|
79
80
|
```
|
@@ -85,6 +86,38 @@ session = JetBlack::Session.new
|
|
85
86
|
session.run("./hello-world", stdin: "Alice")
|
86
87
|
```
|
87
88
|
|
89
|
+
### Running interactive commands
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
session = JetBlack::Session.new
|
93
|
+
|
94
|
+
result = session.run_interactive("./hello-world") do |terminal|
|
95
|
+
terminal.expect("What's your name?", reply: "Alice")
|
96
|
+
terminal.expect("What's your location?", reply: "Wonderland")
|
97
|
+
end
|
98
|
+
|
99
|
+
expect(result.exit_status).to eq 0
|
100
|
+
expect(result.stdout).to eq <<~TXT
|
101
|
+
What's your name?
|
102
|
+
Alice
|
103
|
+
What's your location?
|
104
|
+
Wonderland
|
105
|
+
Hello Alice in Wonderland
|
106
|
+
TXT
|
107
|
+
```
|
108
|
+
|
109
|
+
If you don't want to wait for a process to finish, you can end the interactive
|
110
|
+
session early:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
session = JetBlack::Session.new
|
114
|
+
|
115
|
+
result = session.run_interactive("./long-cli-flow") do |terminal|
|
116
|
+
terminal.expect("Question 1", reply: "Y")
|
117
|
+
terminal.end_session(signal: "INT")
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
88
121
|
### File manipulation
|
89
122
|
|
90
123
|
```ruby
|
@@ -145,7 +178,7 @@ non-human-readable file.
|
|
145
178
|
|
146
179
|
```ruby
|
147
180
|
session = JetBlack::Session.new
|
148
|
-
result =
|
181
|
+
result = session.run("printf $FOO", env: { FOO: "bar" })
|
149
182
|
|
150
183
|
result.stdout # => "bar"
|
151
184
|
```
|
@@ -159,15 +192,19 @@ but you want to run commands like `bundle install` and `bundle exec` with a
|
|
159
192
|
different Gemfile in a given spec, you can configure the session or individual
|
160
193
|
commands to run with a clean Bundler environment.
|
161
194
|
|
195
|
+
Per command:
|
196
|
+
|
162
197
|
```ruby
|
163
|
-
# Per command
|
164
198
|
session = JetBlack::Session.new
|
165
|
-
|
199
|
+
session.run("bundle install", options: { clean_bundler_env: true })
|
200
|
+
```
|
201
|
+
|
202
|
+
Per session:
|
166
203
|
|
167
|
-
|
204
|
+
```ruby
|
168
205
|
session = JetBlack::Session.new(options: { clean_bundler_env: true })
|
169
|
-
|
170
|
-
|
206
|
+
session.run("bundle install")
|
207
|
+
session.run("bundle exec rake")
|
171
208
|
```
|
172
209
|
|
173
210
|
### `$PATH` prefix
|
@@ -250,3 +287,10 @@ RSpec.describe "my command line tool" do
|
|
250
287
|
end
|
251
288
|
end
|
252
289
|
```
|
290
|
+
|
291
|
+
## More examples
|
292
|
+
|
293
|
+
- JetBlack's own [higher-level tests](https://github.com/odlp/jet_black/tree/master/spec/features)
|
294
|
+
- A more complex scenario testing a [gem in a fresh Rails app](https://github.com/thoughtbot/capybara_discoball/blob/master/spec/black_box/rails_app_spec.rb#L8-L39). Shows how to:
|
295
|
+
- Include the [gem-under-test via the Rails app's Gemfile](https://github.com/thoughtbot/capybara_discoball/blob/4e89bfe5531eea1bf6dac42c46c26d0c687d6ddf/spec/black_box/rails_app_spec.rb#L99-L104)
|
296
|
+
- Use a [clean Bundler environment](https://github.com/thoughtbot/capybara_discoball/blob/4e89bfe5531eea1bf6dac42c46c26d0c687d6ddf/spec/black_box/rails_app_spec.rb#L5) to use the Gemfile of the new Rails app (instead of the Bundler context of the gem's test suite)
|
data/Rakefile
CHANGED
data/jet_black.gemspec
CHANGED
@@ -18,10 +18,10 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_development_dependency "bundler", "
|
21
|
+
spec.add_development_dependency "bundler", ">= 1.16", "< 3"
|
22
22
|
spec.add_development_dependency "coveralls"
|
23
23
|
spec.add_development_dependency "pry"
|
24
|
-
spec.add_development_dependency "rake", "
|
24
|
+
spec.add_development_dependency "rake", ">= 12.0"
|
25
25
|
spec.add_development_dependency "rspec_junit_formatter"
|
26
26
|
spec.add_development_dependency "rspec", "~> 3.0"
|
27
27
|
spec.add_development_dependency "simplecov"
|
data/lib/jet_black.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JetBlack
|
2
4
|
class Environment
|
3
5
|
def initialize(raw_env)
|
@@ -13,9 +15,9 @@ module JetBlack
|
|
13
15
|
attr_reader :raw_env
|
14
16
|
|
15
17
|
def stringify_env(env)
|
16
|
-
env.
|
17
|
-
[key.to_s
|
18
|
-
end
|
18
|
+
env.each_with_object({}) do |(key, value), memo|
|
19
|
+
memo[key.to_s] = value&.to_s
|
20
|
+
end
|
19
21
|
end
|
20
22
|
|
21
23
|
def apply_path_prefix(env)
|
data/lib/jet_black/errors.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JetBlack
|
2
4
|
class Error < ::StandardError
|
3
5
|
end
|
@@ -31,4 +33,17 @@ module JetBlack
|
|
31
33
|
MSG
|
32
34
|
end
|
33
35
|
end
|
36
|
+
|
37
|
+
class TerminalSessionTimeoutError < Error
|
38
|
+
attr_reader :terminal
|
39
|
+
|
40
|
+
def initialize(terminal, expected_value, timeout)
|
41
|
+
@terminal = terminal
|
42
|
+
|
43
|
+
super <<~MSG
|
44
|
+
Interactive terminal session timed out after #{timeout} second(s).
|
45
|
+
Waiting for: '#{expected_value}'
|
46
|
+
MSG
|
47
|
+
end
|
48
|
+
end
|
34
49
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "ansi_scrubber"
|
2
4
|
|
3
5
|
module JetBlack
|
@@ -25,7 +27,7 @@ module JetBlack
|
|
25
27
|
private
|
26
28
|
|
27
29
|
def scrub(output_string)
|
28
|
-
AnsiScrubber.call(output_string.to_s
|
30
|
+
AnsiScrubber.call(output_string.to_s)
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative "environment"
|
2
|
+
require_relative "terminal_session"
|
3
|
+
|
4
|
+
module JetBlack
|
5
|
+
class InteractiveCommand
|
6
|
+
def call(raw_command:, raw_env:, directory:, block:)
|
7
|
+
env = Environment.new(raw_env).to_h
|
8
|
+
terminal = TerminalSession.new(raw_command, env: env, directory: directory)
|
9
|
+
|
10
|
+
unless block.nil?
|
11
|
+
block.call(terminal)
|
12
|
+
end
|
13
|
+
|
14
|
+
terminal.wait_for_finish
|
15
|
+
|
16
|
+
ExecutedCommand.new(
|
17
|
+
raw_command: raw_command,
|
18
|
+
stdout: terminal.stdout,
|
19
|
+
stderr: terminal.stderr,
|
20
|
+
exit_status: terminal.exit_status,
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "open3"
|
2
|
+
require_relative "environment"
|
3
|
+
|
4
|
+
module JetBlack
|
5
|
+
class NonInteractiveCommand
|
6
|
+
def call(raw_command:, stdin:, raw_env:, directory:)
|
7
|
+
env = Environment.new(raw_env).to_h
|
8
|
+
|
9
|
+
stdout, stderr, exit_status = Open3.capture3(
|
10
|
+
env, raw_command, chdir: directory, stdin_data: stdin
|
11
|
+
)
|
12
|
+
|
13
|
+
ExecutedCommand.new(
|
14
|
+
raw_command: raw_command,
|
15
|
+
stdout: stdout,
|
16
|
+
stderr: stderr,
|
17
|
+
exit_status: exit_status,
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/jet_black/rspec.rb
CHANGED
data/lib/jet_black/session.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "bundler"
|
3
4
|
require "fileutils"
|
4
5
|
require "forwardable"
|
5
|
-
require "open3"
|
6
6
|
require "tmpdir"
|
7
|
-
require_relative "environment"
|
8
7
|
require_relative "errors"
|
9
8
|
require_relative "executed_command"
|
10
9
|
require_relative "file_helper"
|
10
|
+
require_relative "non_interactive_command"
|
11
|
+
require_relative "interactive_command"
|
11
12
|
|
12
13
|
module JetBlack
|
13
14
|
class Session
|
@@ -26,38 +27,52 @@ module JetBlack
|
|
26
27
|
end
|
27
28
|
|
28
29
|
def run(command, stdin: nil, env: {}, options: {})
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
exec_non_interactive(raw_command: command, stdin: stdin, raw_env: env, options: options).tap do |executed_command|
|
31
|
+
commands << executed_command
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_interactive(command, env: {}, options: {}, &block)
|
36
|
+
exec_interactive(raw_command: command, raw_env: env, options: options, block: block).tap do |executed_command|
|
37
|
+
commands << executed_command
|
38
|
+
end
|
33
39
|
end
|
34
40
|
|
35
41
|
private
|
36
42
|
|
37
43
|
attr_reader :session_options, :file_helper
|
38
44
|
|
39
|
-
def
|
40
|
-
|
45
|
+
def exec_non_interactive(raw_command:, stdin:, raw_env:, options:)
|
46
|
+
combined_options = session_options.merge(options)
|
47
|
+
|
48
|
+
execution_context(combined_options) do
|
49
|
+
NonInteractiveCommand.new.call(raw_command: raw_command, stdin: stdin, raw_env: raw_env, directory: directory)
|
50
|
+
end
|
51
|
+
end
|
41
52
|
|
42
|
-
|
43
|
-
|
44
|
-
Open3.capture3(env, raw_command, chdir: directory, stdin_data: stdin)
|
53
|
+
def exec_interactive(raw_command:, raw_env:, options:, block:)
|
54
|
+
combined_options = session_options.merge(options)
|
45
55
|
|
46
|
-
|
47
|
-
|
48
|
-
stdout: stdout,
|
49
|
-
stderr: stderr,
|
50
|
-
exit_status: exit_status,
|
51
|
-
)
|
56
|
+
execution_context(combined_options) do
|
57
|
+
InteractiveCommand.new.call(raw_command: raw_command, raw_env: raw_env, directory: directory, block: block)
|
52
58
|
end
|
53
59
|
end
|
54
60
|
|
55
|
-
def
|
61
|
+
def execution_context(options)
|
56
62
|
if options[:clean_bundler_env]
|
57
|
-
Bundler.
|
63
|
+
Bundler.public_send(bundler_clean_environment_method) { yield }
|
58
64
|
else
|
59
65
|
yield
|
60
66
|
end
|
61
67
|
end
|
68
|
+
|
69
|
+
def bundler_clean_environment_method
|
70
|
+
# Bundler 2.x
|
71
|
+
if Bundler.respond_to?(:with_unbundled_env)
|
72
|
+
:with_unbundled_env
|
73
|
+
else
|
74
|
+
:with_clean_env
|
75
|
+
end
|
76
|
+
end
|
62
77
|
end
|
63
78
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require "pty"
|
2
|
+
require "expect"
|
3
|
+
require_relative "errors"
|
4
|
+
|
5
|
+
module JetBlack
|
6
|
+
class TerminalSession
|
7
|
+
DEFAULT_TIMEOUT = 10
|
8
|
+
|
9
|
+
attr_reader :exit_status
|
10
|
+
|
11
|
+
def initialize(raw_command, env:, directory:)
|
12
|
+
@stderr_reader, @stderr_writer = IO.pipe
|
13
|
+
@output, @input, @pid = PTY.spawn(env, raw_command, chdir: directory, err: stderr_writer.fileno)
|
14
|
+
self.raw_stdout = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def expect(expected_value, reply: nil, timeout: DEFAULT_TIMEOUT, signal_on_timeout: "KILL")
|
18
|
+
output_matches = output.expect(expected_value, timeout)
|
19
|
+
|
20
|
+
if output_matches.nil?
|
21
|
+
end_session(signal: signal_on_timeout)
|
22
|
+
raise TerminalSessionTimeoutError.new(self, expected_value, timeout)
|
23
|
+
end
|
24
|
+
|
25
|
+
raw_stdout.concat(output_matches)
|
26
|
+
|
27
|
+
if reply != nil
|
28
|
+
input.puts(reply)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def stdout
|
33
|
+
raw_stdout.join.gsub("\r", "")
|
34
|
+
end
|
35
|
+
|
36
|
+
def stderr
|
37
|
+
raw_std_err
|
38
|
+
end
|
39
|
+
|
40
|
+
def wait_for_finish
|
41
|
+
return if finished?
|
42
|
+
|
43
|
+
finalize_io
|
44
|
+
|
45
|
+
self.exit_status = wait_for_exit_status
|
46
|
+
end
|
47
|
+
|
48
|
+
def end_session(signal: "INT")
|
49
|
+
Process.kill(signal, pid)
|
50
|
+
finalize_io
|
51
|
+
|
52
|
+
self.exit_status = wait_for_exit_status
|
53
|
+
end
|
54
|
+
|
55
|
+
def finished?
|
56
|
+
!exit_status.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
attr_accessor :raw_stdout, :raw_std_err
|
62
|
+
attr_reader :input, :output, :pid, :stderr_reader, :stderr_writer
|
63
|
+
attr_writer :exit_status
|
64
|
+
|
65
|
+
def finalize_io
|
66
|
+
drain_stdout
|
67
|
+
drain_stderr
|
68
|
+
end
|
69
|
+
|
70
|
+
def wait_for_exit_status
|
71
|
+
_, pty_status = Process.waitpid2(pid)
|
72
|
+
pty_status.exitstatus || pty_status.termsig
|
73
|
+
end
|
74
|
+
|
75
|
+
def drain_stdout
|
76
|
+
until output.eof? do
|
77
|
+
raw_stdout << output.readline
|
78
|
+
end
|
79
|
+
|
80
|
+
input.close
|
81
|
+
output.close
|
82
|
+
rescue Errno::EIO => e # https://github.com/ruby/ruby/blob/57fb2199059cb55b632d093c2e64c8a3c60acfbb/ext/pty/pty.c#L521
|
83
|
+
warn("Rescued #{e.message}") if ENV.key?("DEBUG")
|
84
|
+
end
|
85
|
+
|
86
|
+
def drain_stderr
|
87
|
+
stderr_writer.close
|
88
|
+
self.raw_std_err = stderr_reader.read
|
89
|
+
stderr_reader.close
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/jet_black/version.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jet_black
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oli Peate
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.16'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3'
|
20
23
|
type: :development
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
|
-
- - "
|
27
|
+
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: '1.16'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: coveralls
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,16 +62,16 @@ dependencies:
|
|
56
62
|
name: rake
|
57
63
|
requirement: !ruby/object:Gem::Requirement
|
58
64
|
requirements:
|
59
|
-
- - "
|
65
|
+
- - ">="
|
60
66
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
67
|
+
version: '12.0'
|
62
68
|
type: :development
|
63
69
|
prerelease: false
|
64
70
|
version_requirements: !ruby/object:Gem::Requirement
|
65
71
|
requirements:
|
66
|
-
- - "
|
72
|
+
- - ">="
|
67
73
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
74
|
+
version: '12.0'
|
69
75
|
- !ruby/object:Gem::Dependency
|
70
76
|
name: rspec_junit_formatter
|
71
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,8 +114,8 @@ dependencies:
|
|
108
114
|
- - ">="
|
109
115
|
- !ruby/object:Gem::Version
|
110
116
|
version: '0'
|
111
|
-
description:
|
112
|
-
email:
|
117
|
+
description:
|
118
|
+
email:
|
113
119
|
executables: []
|
114
120
|
extensions: []
|
115
121
|
extra_rdoc_files: []
|
@@ -135,15 +141,18 @@ files:
|
|
135
141
|
- lib/jet_black/errors.rb
|
136
142
|
- lib/jet_black/executed_command.rb
|
137
143
|
- lib/jet_black/file_helper.rb
|
144
|
+
- lib/jet_black/interactive_command.rb
|
145
|
+
- lib/jet_black/non_interactive_command.rb
|
138
146
|
- lib/jet_black/rspec.rb
|
139
147
|
- lib/jet_black/rspec/matchers.rb
|
140
148
|
- lib/jet_black/session.rb
|
149
|
+
- lib/jet_black/terminal_session.rb
|
141
150
|
- lib/jet_black/version.rb
|
142
151
|
homepage: https://github.com/odlp/jet_black
|
143
152
|
licenses:
|
144
153
|
- MIT
|
145
154
|
metadata: {}
|
146
|
-
post_install_message:
|
155
|
+
post_install_message:
|
147
156
|
rdoc_options: []
|
148
157
|
require_paths:
|
149
158
|
- lib
|
@@ -158,9 +167,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
167
|
- !ruby/object:Gem::Version
|
159
168
|
version: '0'
|
160
169
|
requirements: []
|
161
|
-
|
162
|
-
|
163
|
-
signing_key:
|
170
|
+
rubygems_version: 3.1.4
|
171
|
+
signing_key:
|
164
172
|
specification_version: 4
|
165
173
|
summary: Black box testing
|
166
174
|
test_files: []
|