rspec-command 1.0.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.
@@ -0,0 +1,120 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'rake'
18
+ require 'rspec'
19
+
20
+ require 'rspec_command'
21
+
22
+
23
+ module RSpecCommand
24
+ # An RSpec helper module for testing Rake tasks without running them in a
25
+ # full subprocess. This improves test speed while still giving you most of
26
+ # the benefits of integration testing.
27
+ #
28
+ # @api public
29
+ # @since 1.0.0
30
+ # @example
31
+ # RSpec.configure do |config|
32
+ # config.include RSpecCommand::Rake
33
+ # end
34
+ # @example Enable for a single example group
35
+ # describe 'mytask' do
36
+ # rakefile <<-EOH
37
+ # ...
38
+ # EOH
39
+ # rake_task 'mytask'
40
+ # its(:stdout) { it_expected.to include('1.0.0') }
41
+ # end
42
+ module Rake
43
+ # @!classmethods
44
+ module ClassMethods
45
+ # Run a Rake task as the subject of this example group. The subject will
46
+ # be a string returned by {#capture_output}.
47
+ #
48
+ # @param name [String] Name of the task to execute.
49
+ # @param args [Array<Object>] Arguments to pass to the task.
50
+ # @return [void]
51
+ # @example
52
+ # describe 'mytask' do
53
+ # rakefile 'require "myapp/rake_tasks"'
54
+ # rake_task 'mytask'
55
+ # its(:stdout) { is_expected.to include 'Complete!' }
56
+ # end
57
+ def rake_task(name, *args)
58
+ metadata[:rake] = true
59
+ subject do
60
+ exitstatus = []
61
+ capture_output do
62
+ Process.waitpid fork {
63
+ # This has to be nocov because simplecov doesn't track across fork.
64
+ # :nocov:
65
+ # Defang SimpleCov so it doesn't print its stuff. Can be removed
66
+ # when https://github.com/colszowka/simplecov/pull/377 is in a
67
+ # released version.
68
+ if defined?(SimpleCov)
69
+ SimpleCov.at_exit { SimpleCov.instance_variable_set(:@result, nil) }
70
+ end
71
+ # Because #init reads from ARGV and will try to parse rspec's flags.
72
+ ARGV.replace([])
73
+ Dir.chdir(temp_path)
74
+ ENV.update(_environment)
75
+ rake = ::Rake::Application.new.tap do |rake|
76
+ ::Rake.application = rake
77
+ rake.init
78
+ rake.load_rakefile
79
+ end
80
+ rake[name].invoke(*args)
81
+ }
82
+ exitstatus << $?.exitstatus
83
+ # :nocov:
84
+ end.tap do |output|
85
+ output.define_singleton_method(:exitstatus) { exitstatus.first }
86
+ end
87
+ end
88
+ end
89
+
90
+ # Write out a Rakefile to the temporary directory for this example group.
91
+ # Content can be passed as either a string or a block.
92
+ #
93
+ # @param content [String] Rakefile content.
94
+ # @param block [Proc] Optional block to return the Rakefile content.
95
+ # @return [void]
96
+ # @example
97
+ # describe 'mytask' do
98
+ # rakefile <<-EOH
99
+ # task 'mytask' do
100
+ # ...
101
+ # end
102
+ # EOH
103
+ # rake_task 'mytask'
104
+ # its(:stdout) { is_expected.to include 'Complete!' }
105
+ # end
106
+ def rakefile(content=nil, &block)
107
+ file('Rakefile', content, &block)
108
+ end
109
+
110
+ def included(klass)
111
+ super
112
+ # Pull this in as a dependency.
113
+ klass.send(:include, RSpecCommand)
114
+ klass.extend ClassMethods
115
+ end
116
+ end
117
+
118
+ extend ClassMethods
119
+ end
120
+ end
@@ -0,0 +1,21 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+
18
+ module RSpecCommand
19
+ # RSpec-command gem version.
20
+ VERSION = '1.0.0'
21
+ end
@@ -0,0 +1,48 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ lib = File.expand_path('../lib', __FILE__)
18
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
19
+ require 'rspec_command/version'
20
+
21
+ Gem::Specification.new do |spec|
22
+ spec.name = 'rspec-command'
23
+ spec.version = RSpecCommand::VERSION
24
+ spec.authors = ['Noah Kantrowitz']
25
+ spec.email = %w{noah@coderanger.net}
26
+ spec.description = 'An RSpec helper module for testing command-line tools.'
27
+ spec.summary = spec.description
28
+ spec.homepage = 'https://github.com/coderanger/rspec-command'
29
+ spec.license = 'Apache 2.0'
30
+
31
+ spec.files = `git ls-files`.split($/)
32
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
33
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
34
+ spec.require_paths = %w{lib}
35
+
36
+ spec.add_dependency 'rspec', '~> 3.2'
37
+ spec.add_dependency 'rspec-its', '~> 1.2'
38
+ spec.add_dependency 'mixlib-shellout', '~> 2.0'
39
+
40
+ spec.add_development_dependency 'rake', '~> 10.4'
41
+ spec.add_development_dependency 'fuubar', '~> 2.0'
42
+ spec.add_development_dependency 'simplecov', '~> 0.9'
43
+ spec.add_development_dependency 'yard', '~> 0.8'
44
+ spec.add_development_dependency 'yard-classmethods', '~> 1.0'
45
+ spec.add_development_dependency 'codeclimate-test-reporter', '~> 0.4'
46
+ spec.add_development_dependency 'codecov', '~> 0.0', '>= 0.0.2'
47
+ spec.add_development_dependency 'pry'
48
+ end
@@ -0,0 +1,301 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'spec_helper'
18
+
19
+ describe RSpecCommand do
20
+ def read_temp(path)
21
+ IO.read(File.join(temp_path, path))
22
+ end
23
+
24
+ describe '#command' do
25
+ context 'true' do
26
+ command 'true'
27
+ its(:exitstatus) { is_expected.to eq 0 }
28
+ end # /context true
29
+
30
+ context 'false' do
31
+ command 'false'
32
+ it { expect { subject }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) }
33
+ end # /context false
34
+
35
+ context 'with a block' do
36
+ command { 'true' }
37
+ its(:exitstatus) { is_expected.to eq 0 }
38
+ end # /context with a block
39
+
40
+ context 'with allow_error' do
41
+ command 'false', allow_error: true
42
+ its(:exitstatus) { is_expected.to eq 1 }
43
+ end # /context with allow_error
44
+
45
+ context 'with input' do
46
+ command 'cat', input: "I'm a little teapot"
47
+ its(:stdout) { is_expected.to eq "I'm a little teapot" }
48
+ end # /context with input
49
+
50
+ context 'check gemfile' do
51
+ command 'env'
52
+ its(:stdout) { is_expected.to include("BUNDLE_GEMFILE=#{File.expand_path('../../Gemfile', __FILE__)}") }
53
+ end # /context check gemfile
54
+
55
+ context 'echo * with shell' do
56
+ command 'echo *'
57
+ before { IO.write(File.join(temp_path, 'file'), '') }
58
+ its(:stdout) { is_expected.to eq "file\n" }
59
+ end # /context echo * with shell
60
+
61
+ context 'echo * without shell' do
62
+ command %w{echo *}
63
+ before { IO.write(File.join(temp_path, 'file'), '') }
64
+ its(:stdout) { is_expected.to eq "*\n" }
65
+ end # /context echo * without shell
66
+
67
+ context 'without a Gemfile' do
68
+ command 'env'
69
+ before { allow(self).to receive(:find_file).and_return(nil) }
70
+ around do |example|
71
+ begin
72
+ old_gemfile = ENV.delete('BUNDLE_GEMFILE')
73
+ example.run
74
+ ensure
75
+ ENV['BUNDLE_GEMFILE'] = old_gemfile if old_gemfile
76
+ end
77
+ end
78
+ its(:stdout) { is_expected.to_not include('BUNDLE_GEMFILE') }
79
+ end # /context without a Gemfile
80
+ end # /describe #command
81
+
82
+ describe '#file' do
83
+ context 'with a simple file' do
84
+ file 'data', 'Short and stout'
85
+ subject { read_temp('data') }
86
+ it { is_expected.to eq 'Short and stout' }
87
+ end # /context with a simple file
88
+
89
+ context 'with a block' do
90
+ file 'data' do
91
+ 'Here is my handle'
92
+ end
93
+ subject { read_temp('data') }
94
+ it { is_expected.to eq 'Here is my handle' }
95
+ end # /context with a block
96
+
97
+ context 'with a subfolder' do
98
+ file 'sub/data', 'Here is my spout'
99
+ subject { read_temp('sub/data') }
100
+ it { is_expected.to eq 'Here is my spout' }
101
+ end # /context with a subfolder
102
+
103
+ context 'with an absolute path' do
104
+ it { expect { self.class.file('/data', '') }.to raise_error }
105
+ end # /context with an absolute path
106
+ end # /describe #file
107
+
108
+ describe '#fixture_file' do
109
+ context 'with a single file fixture' do
110
+ fixture_file 'data.txt'
111
+ subject { read_temp('data.txt') }
112
+ it { is_expected.to eq "Fixture data.\n" }
113
+ end # /context with a single file fixture
114
+
115
+ context 'with a directory fixture' do
116
+ fixture_file 'sub'
117
+ it { expect(read_temp('sub1.txt')).to eq "Subfixture 1.\n" }
118
+ it { expect(read_temp('sub2.txt')).to eq "Subfixture 2.\n" }
119
+ end # /context with a directory fixture
120
+
121
+ context 'with a different dest' do
122
+ fixture_file 'sub', 'other'
123
+ it { expect(read_temp('other/sub1.txt')).to eq "Subfixture 1.\n" }
124
+ it { expect(read_temp('other/sub2.txt')).to eq "Subfixture 2.\n" }
125
+ end # /context with a different dest
126
+
127
+ context 'with an absolute path' do
128
+ it { expect { self.class.fixture_file('/data', '') }.to raise_error }
129
+ end # /context with an absolute path
130
+
131
+ context 'with a nested directory fixture' do
132
+ fixture_file 'sub_nested'
133
+ it { expect(read_temp('sub_nested.txt')).to eq "Subfixture nested.\n" }
134
+ it { expect(read_temp('sub_inner/sub_inner1.txt')).to eq "Subfixture inner 1.\n" }
135
+ it { expect(read_temp('sub_inner/sub_inner2.txt')).to eq "Subfixture inner 2.\n" }
136
+ end # /context with a nested directory fixture
137
+ end # /describe #fixture_file
138
+
139
+ describe '#environment' do
140
+ context 'with a single variable' do
141
+ environment MY_KEY: 'true'
142
+ command 'env'
143
+ its(:stdout) { is_expected.to include("MY_KEY=true") }
144
+ end # /context with a single variable
145
+
146
+ context 'with a two variables' do
147
+ environment MY_KEY: 'true'
148
+ environment OTHER_KEY: '1'
149
+ command 'env'
150
+ its(:stdout) { is_expected.to include("MY_KEY=true") }
151
+ its(:stdout) { is_expected.to include("OTHER_KEY=1") }
152
+ end # /context with a two variables
153
+ end # /describe #environment
154
+
155
+ describe '#temp_path' do
156
+ subject { temp_path }
157
+ it { is_expected.to be_a(String) }
158
+ end # /describe #temp_path
159
+
160
+ describe '#fixture_root' do
161
+ let(:fixture_root) { 'fixtures/sub' }
162
+ fixture_file 'sub1.txt'
163
+ subject { read_temp('sub1.txt') }
164
+ it { is_expected.to eq "Subfixture 1.\n" }
165
+ end # /describe #fixture_root
166
+
167
+ describe '#find_file' do
168
+ context 'with Gemfile' do
169
+ subject { find_file(__FILE__, 'Gemfile') }
170
+ it { is_expected.to eq File.expand_path('../../Gemfile', __FILE__) }
171
+ end # /context with Gemfile
172
+
173
+ context 'with a block' do
174
+ subject { find_file(__FILE__) {|p| p } }
175
+ it { is_expected.to eq File.dirname(__FILE__) }
176
+ end # /context with a block
177
+
178
+ context 'with a non-existant file' do
179
+ subject { find_file(__FILE__, 'NOPE.GIF') }
180
+ it { is_expected.to be_nil }
181
+ end # /context with a non-existant file
182
+
183
+ # This is important to check because otherwise the backstop test might be a
184
+ # false negative.
185
+ context 'with the gem root' do
186
+ subject { find_file(__FILE__, 'rspec-command') }
187
+ it { is_expected.to eq File.expand_path('../..', __FILE__) }
188
+ end # /context with the gem root
189
+
190
+ context 'with a backstop' do
191
+ subject { find_file(__FILE__, 'rspec-command', File.expand_path('..', __FILE__)) }
192
+ it { is_expected.to be_nil }
193
+ end # /context with a backstop
194
+ end # /describe #find_file
195
+
196
+ describe '#find_gem_base' do
197
+ subject { find_gem_base(__FILE__) }
198
+ it { is_expected.to eq File.expand_path('../..', __FILE__) }
199
+ end # /describe #find_gem_base
200
+
201
+ describe '#find_fixture' do
202
+ context 'with a fixture file' do
203
+ subject { find_fixture(__FILE__, 'data.txt') }
204
+ it { is_expected.to eq File.expand_path('../fixtures/data.txt', __FILE__) }
205
+ end # /context with a fixture file
206
+
207
+ context 'with no fixture path' do
208
+ subject { find_fixture(__FILE__) }
209
+ it { is_expected.to eq File.expand_path('../fixtures', __FILE__) }
210
+ end # /context with no fixture path
211
+ end # /describe #find_fixture
212
+
213
+ describe '#capture_output' do
214
+ context 'with puts' do
215
+ subject do
216
+ capture_output { puts 'test' }
217
+ end
218
+ it { is_expected.to eq "test\n" }
219
+ its(:stdout) { is_expected.to eq "test\n" }
220
+ its(:stderr) { is_expected.to eq '' }
221
+ its(:exitstatus) { is_expected.to eq 0 }
222
+ end # /context with puts
223
+
224
+ context 'with STDERR.puts' do
225
+ subject do
226
+ capture_output { STDERR.puts 'test' }
227
+ end
228
+ it { is_expected.to eq '' }
229
+ its(:stdout) { is_expected.to eq '' }
230
+ its(:stderr) { is_expected.to eq "test\n" }
231
+ its(:exitstatus) { is_expected.to eq 0 }
232
+ end # /context with STDERR.puts
233
+
234
+ context 'with $stderr.puts' do
235
+ subject do
236
+ capture_output { $stderr.puts 'test' }
237
+ end
238
+ it { is_expected.to eq '' }
239
+ its(:stdout) { is_expected.to eq '' }
240
+ its(:stderr) { is_expected.to eq "test\n" }
241
+ its(:exitstatus) { is_expected.to eq 0 }
242
+ end # /context with $stderr.puts
243
+
244
+ context 'with a subproc' do
245
+ subject do
246
+ capture_output do
247
+ # Can't use `` because that already captures stdout
248
+ if pid = Process.fork
249
+ Process.waitpid(pid)
250
+ else
251
+ exec('echo test')
252
+ end
253
+ end
254
+ end
255
+ it { is_expected.to eq "test\n" }
256
+ its(:stdout) { is_expected.to eq "test\n" }
257
+ its(:stderr) { is_expected.to eq '' }
258
+ its(:exitstatus) { is_expected.to eq 0 }
259
+ end # /context with a subproc
260
+
261
+ context 'with a subproc to stderr' do
262
+ subject do
263
+ capture_output { `echo test >&2` }
264
+ end
265
+ it { is_expected.to eq '' }
266
+ its(:stdout) { is_expected.to eq '' }
267
+ its(:stderr) { is_expected.to eq "test\n" }
268
+ its(:exitstatus) { is_expected.to eq 0 }
269
+ end # /context with a subproc
270
+
271
+ context 'with a block that raises an exception' do
272
+ subject do
273
+ capture_output do
274
+ puts 'before'
275
+ raise 'OMG'
276
+ puts 'after'
277
+ end
278
+ end
279
+ it { expect { subject }.to raise_error }
280
+ it do
281
+ begin
282
+ subject
283
+ rescue Exception => e
284
+ expect(e).to respond_to(:output_so_far)
285
+ expect(e.output_so_far).to eq "before\n"
286
+ else
287
+ raise 'Subject did not raise exception'
288
+ end
289
+ end
290
+ end # /context with a block that raises an exception
291
+ end # /describe #capture_output
292
+
293
+ describe RSpecCommand::OutputString do
294
+ subject { described_class.new('testout', 'testerr') }
295
+ it { is_expected.to be_a String }
296
+ it { is_expected.to eq 'testout' }
297
+ its(:stdout) { is_expected.to eq 'testout' }
298
+ its(:stderr) { is_expected.to eq 'testerr' }
299
+ its(:exitstatus) { is_expected.to eq 0 }
300
+ end # /describe RSpecCommand::OutputString
301
+ end