module RSpecCommand
An RSpec helper module for testing command-line tools.
@api public @since 1.0.0 @example Enable globally
RSpec.configure do |config| config.include RSpecCommand end
@example Enable for a single example group
describe 'myapp' do command 'myapp --version' its(:stdout) { it_expected.to include('1.0.0') } end
Copyright 2015, Noah Kantrowitz
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Copyright 2015, Noah Kantrowitz
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Constants
- VERSION
RSpec-command gem version.
Public Instance Methods
Run a local block with $stdout and $stderr redirected to a strings. Useful for running CLI code in unit tests. The returned string has `#stdout`, `#stderr` and `#exitstatus` attributes to emulate the output from {.command}.
@param block [Proc] Code to run. @return [String] @example
describe 'my rake task' do subject do capture_output do Rake::Task['mytask'].invoke end end end
# File lib/rspec_command.rb, line 141 def capture_output(&block) old_stdout = $stdout.dup old_stderr = $stderr.dup # Potential future improvement is to use IO.pipe instead of temp files, but # that would require threads or something to read contiuously since the # buffer is only 64k on the kernel side. Tempfile.open('capture_stdout') do |tmp_stdout| Tempfile.open('capture_stderr') do |tmp_stderr| $stdout.reopen(tmp_stdout) $stdout.sync = true $stderr.reopen(tmp_stderr) $stderr.sync = true output = nil begin # Inner block to make sure the ensure happens first. begin block.call ensure # Rewind. tmp_stdout.seek(0, 0) tmp_stderr.seek(0, 0) # Read in the output. output = OutputString.new(tmp_stdout.read, tmp_stderr.read) end rescue Exception => e if output # Try to add the output so far as an attribute on the exception via # a closure. e.define_singleton_method(:output_so_far) do output end end raise else output end end end ensure $stdout.reopen(old_stdout) $stderr.reopen(old_stderr) end
Run a command.
@see .command @param cmd [String, Array] Command to run. If passed as an array, no shell
expansion will be performed.
@param options [Hash<Symbol, Object>] Options to pass to
Mixlib::ShellOut.new.
@option options [Boolean] allow_error If true, don't raise an error on
failed commands.
@return [Mixlib::ShellOut] @example
before do command('git init') end
# File lib/rspec_command.rb, line 86 def command(cmd, options={}) # Try to find a Gemfile gemfile_path = ENV['BUNDLE_GEMFILE'] || find_file(self.class.file_path, 'Gemfile') gemfile_environment = gemfile_path ? {'BUNDLE_GEMFILE' => gemfile_path} : {} # Create the command options = options.dup allow_error = options.delete(:allow_error) full_cmd = if gemfile_path if cmd.is_a?(Array) %w{bundle exec} + cmd else "bundle exec #{cmd}" end else cmd end Mixlib::ShellOut.new( full_cmd, { cwd: temp_path, environment: gemfile_environment.merge(_environment), }.merge(options), ).tap do |cmd_out| # Run the command cmd_out.run_command cmd_out.error! unless allow_error end end
Matcher to compare files or folders from the temporary directory to a fixture.
@example
describe 'myapp' do command 'myapp write' it { is_expected.to match_fixture('write_data') } end
# File lib/rspec_command.rb, line 123 def match_fixture(fixture_path, local_path=nil) MatchFixture.new(find_fixture(self.class.file_path), temp_path, fixture_path, local_path) end
Private Instance Methods
Search backwards along the working directory looking for a file, a la .git. Either file or block must be given.
@param example_path [String] Path of the current example file. Find via
example.file_path.
@param file [String] Relative path to search for. @param backstop [String] Path to not search past. @param block [Proc] Block to use as a filter. @return [String, nil]
# File lib/rspec_command.rb, line 218 def find_file(example_path, file=nil, backstop=nil, &block) path = File.dirname(File.expand_path(example_path)) last_path = nil while path != last_path && path != backstop if block block_val = block.call(path) return block_val if block_val else file_path = File.join(path, file) return file_path if File.exists?(file_path) end last_path = path path = File.dirname(path) end nil end
Find a fixture file or the fixture base folder.
# File lib/rspec_command.rb, line 252 def find_fixture(example_path, path=nil) @fixture_base ||= find_file(example_path, fixture_root, find_gem_base(example_path)) path ? File.join(@fixture_base, path) : @fixture_base end
Find the base folder of the current gem.
# File lib/rspec_command.rb, line 236 def find_gem_base(example_path) @gem_base ||= begin paths = [] paths << find_file(example_path) do |path| spec_path = Dir.entries(path).find do |ent| ent.end_with?('.gemspec') end spec_path = File.join(path, spec_path) if spec_path spec_path end paths << find_file(example_path, 'Gemfile') File.dirname(paths.find {|v| v }) end end