mirror of
https://github.com/thoughtbot/shoulda-matchers.git
synced 2022-11-09 12:01:38 -05:00
ee1008de7a
- Layout/EmptyLines - Layout/EmptyLineAfterGuardClause - Layout/TrailingEmptyLines - Layout/EmptyLinesAroundBlockBody - Layout/EmptyLinesAroundModuleBody - Layout/EmptyLineAfterMagicComment - Layout/SpaceInsidePercentLiteralDelimiters - Layout/SpaceAroundEqualsInParameterDefault - Layout/SpaceInsideArrayLiteralBrackets - Layout/LeadingCommentSpace - Layout/SpaceBeforeComment - Layout/SpaceAroundOperators - Layout/SpaceInsideRangeLiteral - Layout/SpaceInsideReferenceBrackets - Layout/SpaceInLambdaLiteral - Layout/SpaceAfterComma - Layout/SpaceInsideHashLiteralBraces - Layout/TrailingWhitespace - Layout/ArgumentAlignment - Layout/HashAlignment - Layout/DotPosition - Layout/IndentationWidth - Layout/EndAlignment - Layout/MultilineOperationIndentation - Layout/IndentationConsistency - Layout/ClosingHeredocIndentation - Layout/MultilineMethodCallBrace- Layout - Layout/ClosingParenthesisIndentation
246 lines
4.9 KiB
Ruby
246 lines
4.9 KiB
Ruby
require 'timeout'
|
|
require 'shellwords'
|
|
|
|
module Tests
|
|
class CommandRunner
|
|
TimeoutError = Class.new(StandardError)
|
|
|
|
def self.run(*args)
|
|
new(*args).tap do |runner|
|
|
yield runner
|
|
runner.call
|
|
end
|
|
end
|
|
|
|
def self.run!(*args)
|
|
run(*args) do |runner|
|
|
runner.run_successfully = true
|
|
yield runner if block_given?
|
|
end
|
|
end
|
|
|
|
attr_reader :status, :options, :env
|
|
attr_accessor :command_prefix, :run_quickly, :run_successfully, :retries,
|
|
:timeout
|
|
|
|
def initialize(*args)
|
|
@reader, @writer = IO.pipe
|
|
@command_output = ''
|
|
options = (args.last.is_a?(Hash) ? args.pop : {})
|
|
@args = args
|
|
@options = options.merge(
|
|
err: [:child, :out],
|
|
out: writer,
|
|
)
|
|
@env = extract_env_from(@options)
|
|
|
|
@wrapper = -> (block) { block.call }
|
|
@command_prefix = ''
|
|
self.directory = Dir.pwd
|
|
@run_quickly = false
|
|
@run_successfully = false
|
|
@retries = 1
|
|
@num_times_run = 0
|
|
@timeout = 20
|
|
end
|
|
|
|
def around_command(&block)
|
|
@wrapper = block
|
|
end
|
|
|
|
def directory
|
|
@options[:chdir]
|
|
end
|
|
|
|
def directory=(directory)
|
|
@options[:chdir] = directory || Dir.pwd
|
|
end
|
|
|
|
def formatted_command
|
|
[formatted_env, Shellwords.join(command)].
|
|
select { |value| !value.empty? }.
|
|
join(' ')
|
|
end
|
|
|
|
def call
|
|
possibly_retrying do
|
|
possibly_running_quickly do
|
|
run_with_debugging
|
|
|
|
if run_successfully && !success?
|
|
fail!
|
|
end
|
|
end
|
|
end
|
|
|
|
self
|
|
end
|
|
|
|
def stop
|
|
unless writer.closed?
|
|
writer.close
|
|
end
|
|
end
|
|
|
|
def output
|
|
@_output ||= begin
|
|
stop
|
|
without_colors(command_output)
|
|
end
|
|
end
|
|
|
|
def elided_output
|
|
lines = output.split(/\n/)
|
|
new_lines = lines[0..4]
|
|
|
|
if lines.size > 10
|
|
new_lines << "(...#{lines.size - 10} more lines...)"
|
|
end
|
|
|
|
new_lines << lines[-5..-1]
|
|
new_lines.join("\n")
|
|
end
|
|
|
|
def success?
|
|
status.success?
|
|
end
|
|
|
|
def exit_status
|
|
status.exitstatus
|
|
end
|
|
|
|
def fail!
|
|
raise <<-MESSAGE
|
|
Command #{formatted_command.inspect} exited with status #{exit_status}.
|
|
Output:
|
|
#{divider('START') + output + divider('END')}
|
|
MESSAGE
|
|
end
|
|
|
|
def has_output?(expected_output)
|
|
if expected_output.is_a?(Regexp)
|
|
output =~ expected_output
|
|
else
|
|
output.include?(expected_output)
|
|
end
|
|
end
|
|
|
|
protected
|
|
|
|
attr_reader :args, :command_output, :reader, :writer, :wrapper
|
|
|
|
private
|
|
|
|
def extract_env_from(options)
|
|
options.delete(:env) { {} }.inject({}) do |hash, (key, value)|
|
|
hash[key.to_s] = value
|
|
hash
|
|
end
|
|
end
|
|
|
|
def command
|
|
([command_prefix] + args).flatten.flat_map do |word|
|
|
Shellwords.split(word)
|
|
end
|
|
end
|
|
|
|
def formatted_env
|
|
env.map { |key, value| "#{key}=#{value.inspect}" }.join(' ')
|
|
end
|
|
|
|
def run
|
|
pid = spawn(env, *command, options)
|
|
t = Thread.new do
|
|
loop do
|
|
begin
|
|
@command_output += reader.read_nonblock(4096)
|
|
rescue IO::WaitReadable
|
|
IO.select([reader])
|
|
retry
|
|
rescue EOFError
|
|
break
|
|
end
|
|
end
|
|
end
|
|
Process.waitpid(pid)
|
|
@status = $?
|
|
ensure
|
|
writer.close unless writer.closed?
|
|
t.join
|
|
end
|
|
|
|
def run_with_wrapper
|
|
wrapper.call(method(:run))
|
|
end
|
|
|
|
def run_with_debugging
|
|
debug { "\n\e[33mChanging to directory:\e[0m #{directory}" }
|
|
debug { "\e[32mRunning command:\e[0m #{formatted_command}" }
|
|
|
|
run_with_wrapper
|
|
|
|
debug { "\n" + divider('START') + output + divider('END') }
|
|
end
|
|
|
|
def possibly_running_quickly(&block)
|
|
if run_quickly
|
|
begin
|
|
Timeout.timeout(timeout, &block)
|
|
rescue Timeout::Error
|
|
stop
|
|
|
|
message =
|
|
"Command timed out after #{timeout} seconds: #{formatted_command}\n" +
|
|
"Output:\n" +
|
|
output
|
|
|
|
raise TimeoutError, message
|
|
end
|
|
else
|
|
yield
|
|
end
|
|
end
|
|
|
|
def possibly_retrying
|
|
begin
|
|
@num_times_run += 1
|
|
yield
|
|
rescue => error
|
|
debug { "#{error.class}: #{error.message}" }
|
|
|
|
if @num_times_run < @retries
|
|
sleep @num_times_run
|
|
retry
|
|
else
|
|
raise error
|
|
end
|
|
end
|
|
end
|
|
|
|
def divider(title = '')
|
|
total_length = 72
|
|
start_length = 3
|
|
|
|
string = ''
|
|
string << ('-' * start_length)
|
|
string << title
|
|
string << '-' * (total_length - start_length - title.length)
|
|
string << "\n"
|
|
string
|
|
end
|
|
|
|
def without_colors(string)
|
|
string.gsub(/\e\[\d+(?:;\d+)?m(.+?)\e\[0m/, '\1')
|
|
end
|
|
|
|
def debugging_enabled?
|
|
ENV['DEBUG_COMMANDS'] == '1'
|
|
end
|
|
|
|
def debug(&block)
|
|
if debugging_enabled?
|
|
puts block.call
|
|
end
|
|
end
|
|
end
|
|
end
|