rspec-command 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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