jet_black 0.4.0 → 0.7.1
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/.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
|
-
[](https://rubygems.org/gems/jet_black) [](https://circleci.com/gh/odlp/jet_black) [](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: []
|