Skip to content

Commit

Permalink
Pass through RSpec arguments except files
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobthemyth committed Dec 6, 2020
1 parent 5038ca3 commit 6cbc1a0
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 2 deletions.
17 changes: 16 additions & 1 deletion bin/rspecq
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@ else
redis_opts[:host] = opts[:redis_host]
end

# Use the RSpec parser to parse any command line args intended for rspec such
# as `-- --format JUnit -o foo.xml` so that we can pass these args to rspec
# while removing the files_or_dirs_to_run since we want to pull those from the
# queue. OptionParser above mutates ARGV, so only options after `--` or
# non-flag arguments (such as files) will make it to this point.
files_or_dirs_to_run = RSpec::Core::Parser.new(ARGV).parse[:files_or_directories_to_run]
l = files_or_dirs_to_run.length
if l.zero?
opts[:rspec_args] = ARGV
else
opts[:rspec_args] = ARGV[0...-l]
opts[:files_or_dirs_to_run] = files_or_dirs_to_run
end

if opts[:report]
reporter = RSpecQ::Reporter.new(
build_id: opts[:build],
Expand All @@ -139,7 +153,8 @@ else
redis_opts: redis_opts
)

worker.files_or_dirs_to_run = ARGV[0] if ARGV[0]
worker.rspec_args = opts[:rspec_args]
worker.files_or_dirs_to_run = opts[:files_or_dirs_to_run] if opts[:files_or_dirs_to_run]
worker.populate_timings = opts[:timings]
worker.file_split_threshold = opts[:file_split_threshold]
worker.max_requeues = opts[:max_requeues]
Expand Down
147 changes: 147 additions & 0 deletions lib/rspecq/parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
require "optparse"

module RSpecQ
class Parser
DEFAULT_REDIS_HOST = "127.0.0.1".freeze
DEFAULT_REPORT_TIMEOUT = 3600 # 1 hour
DEFAULT_MAX_REQUEUES = 3
DEFAULT_FAIL_FAST = 0

def self.parse!(args)
new(args).parse!
end

attr_reader :args, :opts

def initialize(args)
@args = args
@opts = {}
end

# This method mutates `args` in order to allow both rspecq and
# rspec options to be passed to rspecq. ["--build", "foo",
# "--", "--pattern", "bar"] will set `build: "foo"` for rspecq
# options and leave ["--pattern", "bar"] to be passed to rspec
def parse!
parse_args!
parse_env

# rubocop:disable Style/RaiseArgs, Layout/EmptyLineAfterGuardClause
raise OptionParser::MissingArgument.new(:build) if opts[:build].nil?
raise OptionParser::MissingArgument.new(:worker) if !opts[:report] && opts[:worker].nil?
# rubocop:enable Style/RaiseArgs, Layout/EmptyLineAfterGuardClause

opts
end

private

def parse_args!
OptionParser.new do |o|
name = File.basename($PROGRAM_NAME)

o.banner = <<~BANNER
NAME:
#{name} - Optimally distribute and run RSpec suites among parallel workers
USAGE:
#{name} [<options>] [spec files or directories]
#{name} [<options>] -- [<rspec options>] [spec files or directories]
BANNER

o.separator ""
o.separator "OPTIONS:"

o.on("-b", "--build ID", "A unique identifier for the build. Should be " \
"common among workers participating in the same build.") do |v|
opts[:build] = v
end

o.on("-w", "--worker ID", "An identifier for the worker. Workers " \
"participating in the same build should have distinct IDs.") do |v|
opts[:worker] = v
end

o.on("-r", "--redis HOST", "Redis host to connect to " \
"(default: #{DEFAULT_REDIS_HOST}).") do |v|
puts "--redis is deprecated. Use --redis-host or --redis-url instead"
opts[:redis_host] = v
end

o.on("--redis-host HOST", "Redis host to connect to " \
"(default: #{DEFAULT_REDIS_HOST}).") do |v|
opts[:redis_host] = v
end

o.on("--redis-url URL", "The URL of the Redis host to connect to " \
"(e.g.: redis://127.0.0.1:6379/0).") do |v|
opts[:redis_url] = v
end

o.on("--update-timings", "Update the global job timings key with the " \
"timings of this build. Note: This key is used as the basis for job " \
"scheduling.") do |v|
opts[:timings] = v
end

o.on("--file-split-threshold N", Integer, "Split spec files slower than N " \
"seconds and schedule them as individual examples.") do |v|
opts[:file_split_threshold] = v
end

o.on("--report", "Enable reporter mode: do not pull tests off the queue; " \
"instead print build progress and exit when it's " \
"finished.\n#{o.summary_indent * 9} " \
"Exits with a non-zero status code if there were any " \
"failures.") do |v|
opts[:report] = v
end

o.on("--report-timeout N", Integer, "Fail if build is not finished after " \
"N seconds. Only applicable if --report is enabled " \
"(default: #{DEFAULT_REPORT_TIMEOUT}).") do |v|
opts[:report_timeout] = v
end

o.on("--max-requeues N", Integer, "Retry failed examples up to N times " \
"before considering them legit failures " \
"(default: #{DEFAULT_MAX_REQUEUES}).") do |v|
opts[:max_requeues] = v
end

o.on("--fail-fast N", Integer, "Abort build with a non-zero status code " \
"after N failed examples.") do |v|
opts[:fail_fast] = v
end

o.on_tail("-h", "--help", "Show this message.") do
puts o
exit
end

o.on_tail("-v", "--version", "Print the version and exit.") do
puts "#{name} #{RSpecQ::VERSION}"
exit
end
end.parse!(args)
end

def parse_env
opts[:report] = opts.fetch(:report, env_set?("RSPECQ_REPORT"))
opts[:timings] = opts.fetch(:timings, env_set?("RSPECQ_UPDATE_TIMINGS"))

opts[:build] ||= ENV["RSPECQ_BUILD"]
opts[:worker] ||= ENV["RSPECQ_WORKER"]
opts[:redis_host] ||= ENV["RSPECQ_REDIS"] || DEFAULT_REDIS_HOST
opts[:file_split_threshold] ||= Integer(ENV["RSPECQ_FILE_SPLIT_THRESHOLD"] || 9_999_999)
opts[:report_timeout] ||= Integer(ENV["RSPECQ_REPORT_TIMEOUT"] || DEFAULT_REPORT_TIMEOUT)
opts[:max_requeues] ||= Integer(ENV["RSPECQ_MAX_REQUEUES"] || DEFAULT_MAX_REQUEUES)
opts[:redis_url] ||= ENV["RSPECQ_REDIS_URL"]
opts[:fail_fast] ||= Integer(ENV["RSPECQ_FAIL_FAST"] || DEFAULT_FAIL_FAST)
end

def env_set?(var)
["1", "true"].include?(ENV[var])
end
end
end
8 changes: 7 additions & 1 deletion lib/rspecq/worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ class Worker
# Defaults to 0
attr_accessor :fail_fast

# Optional arguments to pass along to rspec.
#
# Defaults to nil
attr_accessor :rspec_args

attr_reader :queue

def initialize(build_id:, worker_id:, redis_opts:)
Expand Down Expand Up @@ -107,7 +112,8 @@ def work
RSpec.configuration.add_formatter(Formatters::JobTimingRecorder.new(queue, job))
end

opts = RSpec::Core::ConfigurationOptions.new(["--format", "progress", job])
args = [*rspec_args, "--format", "progress", job]
opts = RSpec::Core::ConfigurationOptions.new(args)
_result = RSpec::Core::Runner.new(opts).run($stderr, $stdout)

queue.acknowledge_job(job)
Expand Down
4 changes: 4 additions & 0 deletions test/sample_suites/tagged_suite/spec/tagged_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
RSpec.describe do
it("foo", :foo) { expect(true).to be true }
it("bar", :bar) { expect(true).to be true }
end
6 changes: 6 additions & 0 deletions test/test_e2e.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,10 @@ def test_suite_with_failures_and_fail_fast

assert_includes [2, 3], queue.processed_jobs.length
end

def test_suite_with_rspec_arguments
queue = exec_build("tagged_suite", "-- --tag foo")

assert_equal 1, queue.example_count
end
end

0 comments on commit 6cbc1a0

Please sign in to comment.