jet_black 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa0fa42b8b1218e280038ce18fa583a3a8288d4cd91c5a6445c34d03b8c68c5f
4
- data.tar.gz: d5c94f0eb5852106f2959dd4745f27d401f67adcc2d79247e9979dd3760a8935
3
+ metadata.gz: 492919b04c06523b9dca80a4d30939272ae1d435ea83c183f94cae6ab7d11501
4
+ data.tar.gz: c5157a559982f91bfe2560a6283b96229dade980b35fd52f9e446c4e339772e5
5
5
  SHA512:
6
- metadata.gz: 5ca15f316fac15876965b704f37432ee80ceb4867c13b1a0cc0e4063f79bd50ec3b60a2a462f600afb753c08c260c665a71065ba3e35d8dd470436acb0128e83
7
- data.tar.gz: c371bf859447eeac0514167f9f1869132595cf745d6086dcb1482d0feb7622c886ed3f89ffd8cd881c8f5855718be2386bb0c17bd236ad664a9594e8353dbdde
6
+ metadata.gz: d2e3877e059b410b276486990e042722d134abb2c9a4111183a9fcfdd3f902bcd742ff426003fb4c44966e4f0d70ba651709f3833f1cc44048fd3ccddee6e764
7
+ data.tar.gz: ed0f71a6940b5cde28e0c61bcdcbcd76db183a0808a0d7d97d56ba476aab4f885954ab24386f2329c167972430a04d0591105273a93ee4e63a005647ad38df17
@@ -2,7 +2,9 @@ version: 2
2
2
  jobs:
3
3
  build:
4
4
  docker:
5
- - image: circleci/ruby:2.5
5
+ - image: circleci/ruby:2.5
6
+ environment:
7
+ ENABLE_COVERAGE: 1
6
8
 
7
9
  working_directory: ~/repo
8
10
 
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.3.0
4
+
5
+ - Allows `stdin` data to be provided when running commands
6
+ - Adds `create_executable` to which writes a file and adds execute permission
7
+
3
8
  ## v0.2.0
4
9
 
5
10
  - Adds RSpec matchers `have_stdout`, `have_stderr`, `have_no_stdout`, &
@@ -1,18 +1,32 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jet_black (0.2.0)
4
+ jet_black (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  coderay (1.1.2)
10
+ coveralls (0.7.2)
11
+ multi_json (~> 1.3)
12
+ rest-client (= 1.6.7)
13
+ simplecov (>= 0.7)
14
+ term-ansicolor (= 1.2.2)
15
+ thor (= 0.18.1)
10
16
  diff-lcs (1.3)
17
+ docile (1.3.0)
18
+ json (2.1.0)
11
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)
12
24
  pry (0.11.3)
13
25
  coderay (~> 1.1.0)
14
26
  method_source (~> 0.9.0)
15
27
  rake (10.5.0)
28
+ rest-client (1.6.7)
29
+ mime-types (>= 1.16)
16
30
  rspec (3.7.0)
17
31
  rspec-core (~> 3.7.0)
18
32
  rspec-expectations (~> 3.7.0)
@@ -28,17 +42,28 @@ GEM
28
42
  rspec-support (3.7.1)
29
43
  rspec_junit_formatter (0.3.0)
30
44
  rspec-core (>= 2, < 4, != 2.12.0)
45
+ simplecov (0.16.1)
46
+ docile (~> 1.1)
47
+ json (>= 1.8, < 3)
48
+ simplecov-html (~> 0.10.0)
49
+ simplecov-html (0.10.2)
50
+ term-ansicolor (1.2.2)
51
+ tins (~> 0.8)
52
+ thor (0.18.1)
53
+ tins (0.13.2)
31
54
 
32
55
  PLATFORMS
33
56
  ruby
34
57
 
35
58
  DEPENDENCIES
36
59
  bundler (~> 1.16)
60
+ coveralls
37
61
  jet_black!
38
62
  pry
39
63
  rake (~> 10.0)
40
64
  rspec (~> 3.0)
41
65
  rspec_junit_formatter
66
+ simplecov
42
67
 
43
68
  BUNDLED WITH
44
69
  1.16.1
data/README.md CHANGED
@@ -1,16 +1,248 @@
1
1
  # JetBlack
2
2
 
3
- [![CircleCI](https://circleci.com/gh/odlp/jet_black.svg?style=svg)](https://circleci.com/gh/odlp/jet_black)
4
-
5
- A black-box testing utility for command line tools, written in Ruby. Features:
6
-
7
- - Each session runs in a temporary directory
8
- - Captures `stdout`, `stderr` and exit status of a command
9
- - Keeps a history of commands executed
10
- - Temporarily overriding environment variables for a command
11
- - Copying fixture files to the temporary directory
12
- - Appending content to files in the temporary directory
13
- - Adding a path prefix to include your executable
14
- - Option to escape the Bundler environment (allowing `bundle exec` with
15
- different Gemfiles)
16
- - RSpec matchers for `stdout` and `stderr`
3
+ [![CircleCI](https://circleci.com/gh/odlp/jet_black.svg?style=svg)](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
+
5
+ A black-box testing utility for command line tools and gems. Written in Ruby,
6
+ with [RSpec] in mind. Features:
7
+
8
+ [RSpec]: http://rspec.info/
9
+
10
+ - Each session takes place within a unique temporary directory, outside the project
11
+ - Synchronously [run commands](#running-commands) then write assertions on the:
12
+ - `stdout` / `stderr` content
13
+ - exit status of the process
14
+ - Conveniently manipulate files in the temporary directory:
15
+ - [Create files](#file-manipulation)
16
+ - [Create executable files](#file-manipulation)
17
+ - [Append content to files](#file-manipulation)
18
+ - [Copy fixture files](#copying-fixture-files) from your project
19
+ - Modify the environment without modifying the parent test process:
20
+ - [Override environment variables](#environment-variable-overrides)
21
+ - [Escape the current Bundler context](#clean-bundler-environment)
22
+ - [Adjust `$PATH`](#path-prefix) to include your executable / Subject Under Test
23
+ - [RSpec matchers](#rspec-matchers) (optional)
24
+
25
+ The temporary directory is discarded after each spec. This means you can write
26
+ & modify files and run commands (like `git init`) without worrying about tidying
27
+ up after or impacting your actual project.
28
+
29
+ ## Setup
30
+
31
+ ```ruby
32
+ group :test do
33
+ gem "jet_black"
34
+ end
35
+ ```
36
+
37
+ ### RSpec setup
38
+
39
+ If you're using RSpec, you can load matchers with the following require
40
+ (optional):
41
+
42
+ ```ruby
43
+ # spec/spec_helper.rb
44
+
45
+ require "jet_black/rspec"
46
+ ```
47
+
48
+ Any specs you write in the `spec/black_box` folder will then have an inferred
49
+ `:black_box` meta type, and the matchers will be available in those examples.
50
+
51
+ Alternatively you can manually include the matchers:
52
+
53
+ ```ruby
54
+ # spec/cli/example_spec.rb
55
+
56
+ require "jet_black"
57
+ require "jet_black/rspec/matchers"
58
+
59
+ RSpec.describe "my command line tool" do
60
+ include JetBlack::RSpec::Matchers
61
+ end
62
+ ```
63
+
64
+ ## Usage
65
+
66
+ ### Running commands
67
+
68
+ ```ruby
69
+ require "jet_black"
70
+
71
+ session = JetBlack::Session.new
72
+ result = session.run("echo foo")
73
+
74
+ result.stdout # => "foo"
75
+ result.stderr # => ""
76
+ result.exit_status # => 0
77
+ ```
78
+
79
+ Providing `stdin` data:
80
+
81
+ ```ruby
82
+ session = JetBlack::Session.new
83
+ session.run("./hello-world", stdin: "Alice")
84
+ ```
85
+
86
+ ### File manipulation
87
+
88
+ ```ruby
89
+ session = JetBlack::Session.new
90
+
91
+ session.create_file "file.txt", <<~TXT
92
+ The quick brown fox
93
+ jumps over the lazy dog
94
+ TXT
95
+
96
+ session.create_executable "hello-world.sh", <<~SH
97
+ #!/bin/sh
98
+ echo "Hello world"
99
+ SH
100
+
101
+ session.append_to_file "file.txt", <<~TXT
102
+ shiny
103
+ new
104
+ lines
105
+ TXT
106
+
107
+ # Subdirectories are created for you:
108
+ session.create_file "deeper/underground/jamiroquai.txt", <<~TXT
109
+ I'm going deeper underground, hey ha
110
+ There's too much panic in this town
111
+ TXT
112
+ ```
113
+
114
+ ### Copying fixture files
115
+
116
+ It's ideal to create pertinent files inline within a spec, to provide context
117
+ for the reader, but sometimes a large or non-readable file is best copied
118
+ across.
119
+
120
+ First create a fixture directory in your project, such as
121
+ `spec/fixtures/black_box`. Then configure the fixture path in
122
+ `spec/support/jet_black.rb`:
123
+
124
+ ```ruby
125
+ require "jet_black"
126
+
127
+ JetBlack.configure do |config|
128
+ config.fixture_directory = File.expand_path("../fixtures/black_box", __dir__)
129
+ end
130
+ ```
131
+
132
+ Now you can copy fixtures across into a session's temporary directory:
133
+
134
+ ```ruby
135
+ session = JetBlack::Session.new
136
+ session.copy_fixture("src-config.json", "config.json")
137
+
138
+ # Destination subdirectories are created for you:
139
+ session.copy_fixture("src-config.json", "config/config.json")
140
+ ```
141
+
142
+ ### Environment variable overrides
143
+
144
+ ```ruby
145
+ session = JetBlack::Session.new
146
+ result = subject.run("echo $FOO", env: { FOO: "bar" })
147
+
148
+ result.stdout # => "bar"
149
+ ```
150
+
151
+ ### Clean Bundler environment
152
+
153
+ If your project's test suite is invoked with Bundler (e.g. `bundle exec rspec`)
154
+ but you want to run commands like `bundle install` and `bundle exec` with a
155
+ different Gemfile in a given spec, you can configure the session or individual
156
+ commands to run with a clean Bundler environment.
157
+
158
+ ```ruby
159
+ # Per command
160
+ session = JetBlack::Session.new
161
+ subject.run("bundle install", options: { clean_bundler_env: true })
162
+
163
+ # Per session
164
+ session = JetBlack::Session.new(options: { clean_bundler_env: true })
165
+ subject.run("bundle install")
166
+ subject.run("bundle exec rake")
167
+ ```
168
+
169
+ ### `$PATH` prefix
170
+
171
+ Given the root of your project contains a `bin` directory containing
172
+ `my_awesome_bin`.
173
+
174
+ Configure the `path_prefix` to the directory containing with your executable(s):
175
+
176
+ ```ruby
177
+ # spec/support/jet_black.rb
178
+
179
+ require "jet_black"
180
+
181
+ JetBlack.configure do |config|
182
+ config.path_prefix = File.expand("../../bin", __dir__)
183
+ end
184
+ ```
185
+
186
+ Then the `$PATH` of each session will include the configured directory, and your
187
+ executable should be invokable:
188
+
189
+ ```ruby
190
+ JetBlack::Session.new.run("my_awesome_bin")
191
+ ```
192
+
193
+ ### RSpec matchers
194
+
195
+ Given the [RSpec setup](#rspec-setup) is configured, you'll have access to the
196
+ following matchers:
197
+
198
+ - `have_stdout` which accepts a string or regular expression
199
+ - `have_stderr` which accepts a string or regular expression
200
+ - `have_no_stdout` which asserts the `stdout` is empty
201
+ - `have_no_stderr` which asserts the `stderr` is empty
202
+
203
+ And the following predicate matchers:
204
+
205
+ - `be_a_success` / `be_success` asserts the exit status was zero
206
+ - `be_a_failure` / `be_failure` asserts the exit status was not zero
207
+
208
+ #### Example assertions
209
+
210
+ ```ruby
211
+ # spec/black_box/cli_spec.rb
212
+
213
+ RSpec.describe "my command line tool" do
214
+ let(:session) { JetBlack::Session.new }
215
+
216
+ it "does the work" do
217
+ expect(session.run("my_tool --good")).
218
+ to be_a_success.and have_stdout(/It worked/)
219
+ end
220
+
221
+ it "explodes with incorrect arguments" do
222
+ expect(session.run("my_tool --bad")).
223
+ to be_a_failure.and have_stderr("Oh no!")
224
+ end
225
+ end
226
+ ```
227
+
228
+ However these assertions can be made with built-in matchers too:
229
+
230
+ ```ruby
231
+ RSpec.describe "my command line tool" do
232
+ let(:session) { JetBlack::Session.new }
233
+
234
+ it "does the work" do
235
+ result = session.run("my_tool --good")
236
+
237
+ expect(result.stdout).to match(/It worked/)
238
+ expect(result.exit_status).to eq 0
239
+ end
240
+
241
+ it "explodes with incorrect arguments" do
242
+ result = session.run("my_tool --bad")
243
+
244
+ expect(result.stderr).to match("Oh no!")
245
+ expect(result.exit_status).to eq 1
246
+ end
247
+ end
248
+ ```
@@ -19,8 +19,10 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.16"
22
+ spec.add_development_dependency "coveralls"
22
23
  spec.add_development_dependency "pry"
23
24
  spec.add_development_dependency "rake", "~> 10.0"
24
25
  spec.add_development_dependency "rspec_junit_formatter"
25
26
  spec.add_development_dependency "rspec", "~> 3.0"
27
+ spec.add_development_dependency "simplecov"
26
28
  end
@@ -0,0 +1,66 @@
1
+ require "fileutils"
2
+ require_relative "errors"
3
+
4
+ module JetBlack
5
+ class FileHelper
6
+ def initialize(working_directory)
7
+ @working_directory = working_directory
8
+ end
9
+
10
+ def create_file(file_path, file_content)
11
+ resolved_file_path = resolve_working_path(file_path)
12
+ resolved_dir = File.dirname(resolved_file_path)
13
+
14
+ FileUtils.mkdir_p(resolved_dir)
15
+ File.write(resolved_file_path, file_content)
16
+ end
17
+
18
+ def create_executable(file_path, file_content)
19
+ resolved_file_path = resolve_working_path(file_path)
20
+
21
+ create_file(file_path, file_content)
22
+ FileUtils.chmod("+x", resolved_file_path)
23
+ end
24
+
25
+ def append_to_file(file_path, append_content)
26
+ resolved_file_path = resolve_working_path(file_path)
27
+
28
+ unless File.exist?(resolved_file_path)
29
+ raise JetBlack::NonExistentFileError.new(file_path, resolved_file_path)
30
+ end
31
+
32
+ File.open(resolved_file_path, "a") do |file|
33
+ file.write(append_content)
34
+ end
35
+ end
36
+
37
+ def copy_fixture(source_path, destination_path)
38
+ source_fixture_dir = JetBlack.configuration.fixture_directory
39
+ resolved_source_path = File.expand_path(source_path, source_fixture_dir)
40
+
41
+ resolved_destination_path = resolve_working_path(destination_path)
42
+ resolved_destination_dir = File.dirname(resolved_destination_path)
43
+
44
+ if source_fixture_dir.nil?
45
+ raise Error.new("Please configure the fixture_directory")
46
+ end
47
+
48
+ FileUtils.mkdir_p(resolved_destination_dir)
49
+ FileUtils.cp(resolved_source_path, resolved_destination_path)
50
+ end
51
+
52
+ private
53
+
54
+ attr_reader :working_directory
55
+
56
+ def resolve_working_path(file_path)
57
+ resolved_file_path = File.expand_path(file_path, working_directory)
58
+
59
+ unless resolved_file_path.start_with?(working_directory)
60
+ raise JetBlack::InvalidPathError.new(file_path, resolved_file_path)
61
+ end
62
+
63
+ resolved_file_path
64
+ end
65
+ end
66
+ end
@@ -1,86 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
+ require "forwardable"
4
5
  require "open3"
5
6
  require "tmpdir"
6
7
  require_relative "environment"
7
8
  require_relative "errors"
8
9
  require_relative "executed_command"
10
+ require_relative "file_helper"
9
11
 
10
12
  module JetBlack
11
13
  class Session
12
- attr_reader :commands
14
+ extend Forwardable
15
+
16
+ def_delegators :file_helper, :create_file, :create_executable,
17
+ :append_to_file, :copy_fixture
18
+
19
+ attr_reader :commands, :directory
13
20
 
14
21
  def initialize(options: {})
15
22
  @commands = []
16
23
  @session_options = options
24
+ @directory = File.realpath(Dir.mktmpdir("jet_black"))
25
+ @file_helper = FileHelper.new(directory)
17
26
  end
18
27
 
19
- def run(command, env: {}, options: {})
28
+ def run(command, stdin: nil, env: {}, options: {})
20
29
  combined_options = session_options.merge(options)
21
- executed_command = exec_command(command, env, combined_options)
30
+ executed_command = exec_command(command, stdin, env, combined_options)
22
31
  commands << executed_command
23
32
  executed_command
24
33
  end
25
34
 
26
- def directory
27
- @_directory ||= File.realpath(Dir.mktmpdir("jet_black"))
28
- end
29
-
30
- def create_file(file_path, file_content)
31
- expanded_file_path = File.expand_path(file_path, directory)
32
- expanded_dir = File.dirname(expanded_file_path)
33
-
34
- unless expanded_file_path.start_with?(directory)
35
- raise JetBlack::InvalidPathError.new(file_path, expanded_file_path)
36
- end
37
-
38
- FileUtils.mkdir_p(expanded_dir)
39
- File.write(expanded_file_path, file_content)
40
- end
41
-
42
- def append_to_file(file_path, append_content)
43
- expanded_file_path = File.expand_path(file_path, directory)
44
-
45
- unless File.exist?(expanded_file_path)
46
- raise JetBlack::NonExistentFileError.new(file_path, expanded_file_path)
47
- end
48
-
49
- File.open(expanded_file_path, "a") do |file|
50
- file.write(append_content)
51
- end
52
- end
53
-
54
- def copy_fixture(source_path, destination_path)
55
- src_fixture_dir = JetBlack.configuration.fixture_directory
56
- expanded_source_path = File.expand_path(source_path, src_fixture_dir)
57
- expanded_destination_path = File.expand_path(destination_path, directory)
58
- expanded_destination_dir = File.dirname(expanded_destination_path)
59
-
60
- if src_fixture_dir.nil?
61
- raise Error.new("Please configure the fixture_directory")
62
- end
63
-
64
- unless expanded_destination_path.start_with?(directory)
65
- raise JetBlack::InvalidPathError.new(
66
- destination_path, expanded_destination_path
67
- )
68
- end
69
-
70
- FileUtils.mkdir_p(expanded_destination_dir)
71
- FileUtils.cp(expanded_source_path, expanded_destination_path)
72
- end
73
-
74
35
  private
75
36
 
76
- attr_reader :session_options
37
+ attr_reader :session_options, :file_helper
77
38
 
78
- def exec_command(raw_command, raw_env, options)
39
+ def exec_command(raw_command, stdin, raw_env, options)
79
40
  env = Environment.new(raw_env).to_h
80
41
 
81
42
  command_context(options) do
82
43
  stdout, stderr, exit_status =
83
- Open3.capture3(env, raw_command, chdir: directory)
44
+ Open3.capture3(env, raw_command, chdir: directory, stdin_data: stdin)
84
45
 
85
46
  ExecutedCommand.new(
86
47
  raw_command: raw_command,
@@ -1,3 +1,3 @@
1
1
  module JetBlack
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jet_black
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oli Peate
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-04-27 00:00:00.000000000 Z
11
+ date: 2018-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: coveralls
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: pry
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,20 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  description:
84
112
  email:
85
113
  executables: []
@@ -89,7 +117,6 @@ files:
89
117
  - ".circleci/config.yml"
90
118
  - ".gitignore"
91
119
  - ".rspec"
92
- - ".travis.yml"
93
120
  - CHANGELOG.md
94
121
  - Gemfile
95
122
  - Gemfile.lock
@@ -104,6 +131,7 @@ files:
104
131
  - lib/jet_black/environment.rb
105
132
  - lib/jet_black/errors.rb
106
133
  - lib/jet_black/executed_command.rb
134
+ - lib/jet_black/file_helper.rb
107
135
  - lib/jet_black/rspec.rb
108
136
  - lib/jet_black/rspec/matchers.rb
109
137
  - lib/jet_black/session.rb
@@ -1,5 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.4.3
5
- before_install: gem install bundler -v 1.16.1