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

capture_output(&block) click to toggle source

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
command(cmd, options={}) click to toggle source

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
match_fixture(fixture_path, local_path=nil) click to toggle source

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

find_file(example_path, file=nil, backstop=nil, &block) click to toggle source

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_fixture(example_path, path=nil) click to toggle source

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_gem_base(example_path) click to toggle source

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