diff --git a/lib/flatware/cli.rb b/lib/flatware/cli.rb index 8d3a1b9..066ca3f 100644 --- a/lib/flatware/cli.rb +++ b/lib/flatware/cli.rb @@ -76,6 +76,10 @@ def try_setpgrp def workers options[:workers] end + + def worker_spawn_count(jobs) + [workers, jobs.length].min + end end end diff --git a/lib/flatware/cucumber/cli.rb b/lib/flatware/cucumber/cli.rb index 98092d9..5ed872a 100644 --- a/lib/flatware/cucumber/cli.rb +++ b/lib/flatware/cucumber/cli.rb @@ -16,24 +16,22 @@ class CLI 'parallelizes cucumber with custom arguments' ) def cucumber(*args) - config = Cucumber.configure args + jobs = load_jobs(args) - ensure_jobs(config) + formatter = Flatware::Cucumber::Formatters::Console.new($stdout, $stderr) Flatware.verbose = options[:log] - sink = options['sink-endpoint'] - Worker.spawn(count: workers, runner: Cucumber, sink: sink) - start_sink( - jobs: config.jobs, - workers: workers, - formatter: Flatware::Cucumber::Formatters::Console.new($stdout, $stderr) - ) + + spawn_count = worker_spawn_count(jobs) + Worker.spawn(count: spawn_count, runner: Cucumber, sink: options['sink-endpoint']) + start_sink(jobs: jobs, workers: spawn_count, formatter: formatter) end private - def ensure_jobs(config) - return if config.jobs.any? + def load_jobs(args) + config = Cucumber.configure args + return config.jobs if config.jobs.any? abort( format( diff --git a/lib/flatware/rspec/cli.rb b/lib/flatware/rspec/cli.rb index c0417f7..d912438 100644 --- a/lib/flatware/rspec/cli.rb +++ b/lib/flatware/rspec/cli.rb @@ -23,8 +23,10 @@ def rspec(*rspec_args) ) Flatware.verbose = options[:log] - Worker.spawn count: workers, runner: RSpec, sink: options['sink-endpoint'] - start_sink(jobs: jobs, workers: workers, formatter: formatter) + + spawn_count = worker_spawn_count(jobs) + Worker.spawn(count: spawn_count, runner: RSpec, sink: options['sink-endpoint']) + start_sink(jobs: jobs, workers: spawn_count, formatter: formatter) end end end diff --git a/lib/flatware/rspec/job_builder.rb b/lib/flatware/rspec/job_builder.rb index 1ae9756..42250be 100644 --- a/lib/flatware/rspec/job_builder.rb +++ b/lib/flatware/rspec/job_builder.rb @@ -43,11 +43,17 @@ def jobs private def balance_jobs(bucket_count:, timed_files:, untimed_files:) - balance_by(bucket_count, timed_files, &:last) - .map { |bucket| bucket.map(&:first) } - .zip( - round_robin(bucket_count, untimed_files) - ).map(&:flatten) + timed_groups = balance_by(bucket_count, timed_files, &:last) + .map { |bucket| bucket.map(&:first) } + untimed_groups = round_robin(bucket_count, untimed_files) + + # When the files can't be evenly divided between groups, the first groups + # in each of timed_groups & untimed_groups will have more files. + # By reversing one of them before combining them, we can improve the final distribution. + timed_groups + .zip(untimed_groups.reverse) + .map(&:flatten) + .reject(&:empty?) .map { |files| Job.new(files, args) } end diff --git a/spec/flatware/rspec/job_builder_spec.rb b/spec/flatware/rspec/job_builder_spec.rb index 6e35d14..2cfee19 100644 --- a/spec/flatware/rspec/job_builder_spec.rb +++ b/spec/flatware/rspec/job_builder_spec.rb @@ -16,9 +16,10 @@ let(:persisted_examples) { [] } let(:files_to_run) { [] } + let(:worker_count) { 2 } subject do - described_class.new([], workers: 2).jobs + described_class.new([], workers: worker_count).jobs end context 'when this run includes persisted examples' do @@ -65,5 +66,26 @@ ) end end + + context 'and there are more workers than example-files' do + let(:files_to_run) do + %w[ + fast_1_spec.rb + slow_spec.rb + new_1_spec.rb + ] + end + let(:worker_count) { 5 } + + it "doesn't return empty job-groups" do + expect(subject).to match_array( + [ + have_attributes(id: include('./fast_1_spec.rb')), + have_attributes(id: include('./slow_spec.rb')), + have_attributes(id: include('./new_1_spec.rb')) + ] + ) + end + end end end