module RSpec::DescribeExecutable

describe_executable method and some pretty expectations regarding streams.

Public Instance Methods

describe_executable(cmd, &block) click to toggle source

Outmost describe block. @param [String] cmd executable name @yields block like ordinary describe @example

describe_executable 'ls' do
  describe_stdout do
    it { is_expected.to be_a String }
  end
end
# File lib/rspec/launchbox/describe_executable.rb, line 76
def describe_executable(cmd, &block)
  RSpec.describe "#{$_command_line} executable" do
    $_command_line = cmd
    $_flags = []
    $_process_stdout = ''
    $_process_stderr = ''

    before(:each) do
      $_command_line = cmd
      $_process_stdout = ''
      $_process_pid = nil
      $_piper_o, $_pipew_o = IO.pipe
      $_piper_e, $_pipew_e = IO.pipe
      $_process_pid = fork do
        $_piper_o.close
        $_piper_e.close
        $stdout.reopen $_pipew_o
        $stderr.reopen $_pipew_e
        exec($_command_line + ' ' + $_flags.join(' '))
      end
      $_pipew_o.close
      $_pipew_e.close
      Thread.abort_on_exception = true
      Thread.new do
        maybe_read = [$_piper_o, $_piper_e]
        can_read = []
        loop do
          can_read = select([$_piper_o, $_piper_e]).first.map!(&:fileno)
          $_process_stdout << ($_piper_o.read(16) || '') if can_read.include? $_piper_o.fileno
          $_process_stderr << ($_piper_e.read(16) || '') if can_read.include? $_piper_e.fileno
          maybe_read.delete_if { |io| io.eof? }
          break if maybe_read.empty?
        end
      end.join
    end

    after(:each) do
      `kill -INT #{$_process_pid} &>/dev/null`
      sleep 0.1
      `kill -KILL #{$_process_pid} &>/dev/null`
    end

    class_eval &block
  end
end
describe_stderr(&block) click to toggle source

Same as describe_stdout for stderr

# File lib/rspec/launchbox/describe_executable.rb, line 139
def describe_stderr(&block)
  describe "it's stderr" do
    let(:_watch_stream) { $_process_stderr.dup }
    extend Stream

    subject { $_process_stderr }

    class_eval &block
  end
end
describe_stdout(&block) click to toggle source

Opens new 'describe', sets it's subject to process stdout @yields block like ordinary describe @see describe_executable for an example

# File lib/rspec/launchbox/describe_executable.rb, line 126
def describe_stdout(&block)
  describe "it's stdout" do
    let(:_watch_stream) { $_process_stdout.dup }
    extend Stream

    subject { $_process_stdout }

    class_eval &block
  end
end
given_option(flag, &block) click to toggle source

Opens new 'context', where options are appended to command line before execution. @param [String] flag e.g. `-f` @param [String] parameter @example

describe_executable 'ls' do
  given_option '-a' do
    describe_stdout do
      #you need rspec-its gem for `its`
      its(:lines) { is_expected.to include ".\n" }
      # much prettier:
      has_line '.'
    end
  end
end
# File lib/rspec/launchbox/describe_executable.rb, line 166
def given_option(flag, &block)
  context "given option #{flag}" do
    before(:context) do
      $_flags << flag
    end

    after(:context) do
      $_flags.pop
    end

    class_eval &block
  end
end
running_for(timeout, options = {}, &block) click to toggle source

Waits for `timeout` seconds, then sends signal to the process. @param [Integer] timeout @param [Hash] options @option options [Symbol | String] signal @example

describe_executable 'some_long_running_executable' do
  running_for(10, signal: :INT) do
    describe_stdout do
      #...
    end
  end
end
# File lib/rspec/launchbox/describe_executable.rb, line 193
def running_for(timeout, options = {}, &block)
  $_signal = options[:signal] && options[:signal].to_s.upcase.prepend('-')
  context "when sent #{$_signal} after #{timeout} seconds" do
    before(:each) do
      Thread.new do
        sleep timeout
        `kill #{$_signal} #{$_process_pid} &>/dev/null`
      end.join
    end

    class_eval &block
  end
end