Skip to content

Commit

Permalink
Add support for more than one isolated process. (#779)
Browse files Browse the repository at this point in the history
  • Loading branch information
v-kumar committed Sep 16, 2020
1 parent c410a72 commit d95ea27
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Added support for multiple isolated processes.

### Breaking Changes

- None
Expand Down
4 changes: 3 additions & 1 deletion Readme.md
Expand Up @@ -205,7 +205,9 @@ Options are:
default - runtime when runtime log is filled otherwise filesize
-m, --multiply-processes [FLOAT] use given number as a multiplier of processes to run
-s, --single [PATTERN] Run all matching files in the same process
-i, --isolate Do not run any other tests in the group used by --single(-s)
-i, --isolate Do not run any other tests in the group used by --single(-s).
Automatically turned on if --isolate-n is set above 0.
--isolate-n Number of processes for isolated groups. Default to 1 when --isolate is on.
--only-group INT[, INT]
-e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUMBER']
-o, --test-options '[OPTIONS]' execute test commands with those options
Expand Down
6 changes: 6 additions & 0 deletions lib/parallel_tests/cli.rb
Expand Up @@ -192,6 +192,12 @@ def parse_options!(argv)
options[:isolate] = true
end

opts.on("--isolate-n [PROCESSES]",
Integer,
"Use 'isolate' singles with number of processes, default: 1.") do |n|
options[:isolate_count] = n
end

opts.on("--only-group INT[, INT]", Array) { |groups| options[:only_group] = groups.map(&:to_i) }

opts.on("-e", "--exec [COMMAND]", "execute this code parallel and with ENV['TEST_ENV_NUMBER']") { |path| options[:execute] = path }
Expand Down
37 changes: 32 additions & 5 deletions lib/parallel_tests/grouper.rb
Expand Up @@ -15,19 +15,46 @@ def in_even_groups_by_size(items, num_groups, options= {})
groups = Array.new(num_groups) { {:items => [], :size => 0} }

# add all files that should run in a single process to one group
(options[:single_process] || []).each do |pattern|
matched, items = items.partition { |item, _size| item =~ pattern }
matched.each { |item, size| add_to_group(groups.first, item, size) }
single_process_patterns = options[:single_process] || []

single_items, items = items.partition do |item, _size|
single_process_patterns.any? { |pattern| item =~ pattern }
end

groups_to_fill = (options[:isolate] ? groups[1..-1] : groups)
group_features_by_size(items_to_group(items), groups_to_fill)
isolate_count = isolate_count(options)

if isolate_count >= num_groups
raise 'Number of isolated processes must be less than total the number of processes'
end

if isolate_count >= 1
# add all files that should run in a multiple isolated processes to their own groups
group_features_by_size(items_to_group(single_items), groups[0..(isolate_count - 1)])
# group the non-isolated by size
group_features_by_size(items_to_group(items), groups[isolate_count..-1])
else
# add all files that should run in a single non-isolated process to first group
single_items.each { |item, size| add_to_group(groups.first, item, size) }

# group all by size
group_features_by_size(items_to_group(items), groups)
end

groups.map! { |g| g[:items].sort }
end

private

def isolate_count(options)
if options[:isolate_count] && options[:isolate_count] > 1
options[:isolate_count]
elsif options[:isolate]
1
else
0
end
end

def largest_first(files)
files.sort_by{|_item, size| size }.reverse
end
Expand Down
20 changes: 20 additions & 0 deletions spec/parallel_tests/cli_spec.rb
Expand Up @@ -107,6 +107,26 @@ def call(*args)
end
end

context "single and isolate" do
it "single_process should be an array of patterns" do
expect(call(["test", "--single", '1'])).to eq(defaults.merge(single_process: [/1/]))
end

it "single_process should be an array of patterns" do
expect(call(["test", "--single", '1', "--single", '2'])).to eq(defaults.merge(single_process: [/1/, /2/]))
end

it "isolate should set isolate_count defaults" do
expect(call(["test", "--single", '1', "--isolate"])).to eq(defaults.merge(single_process: [/1/], isolate: true))
end

it "isolate_n should set isolate_count and turn on isolate" do
expect(call(["test", "-n", "3", "--single", '1', "--isolate-n", "2"])).to eq(
defaults.merge(count: 3, single_process: [/1/], isolate_count: 2)
)
end
end

context "when the -- option separator is used" do
it "interprets arguments as files/directories" do
expect(call(%w(-- test))).to eq( files: %w(test))
Expand Down
13 changes: 13 additions & 0 deletions spec/parallel_tests/grouper_spec.rb
Expand Up @@ -54,9 +54,22 @@ def call(num_groups, options={})
expect(call(2, :single_process => [/1|2|3|4/])).to eq([["1", "2", "3", "4"], ["5"]])
end

it "groups single items into specified isolation groups" do
expect(call(3, :single_process => [/1|2|3|4/], :isolate_count => 2)).to eq([["1", "4"], ["2", "3"], ["5"]])
end

it "groups single items with others if there are too few" do
expect(call(2, :single_process => [/1/])).to eq([["1", "3", "4"], ["2", "5"]])
end

it "groups must abort when isolate_count is out of bounds" do
expect {
call(3, :single_process => [/1/], :isolate_count => 3)
}.to raise_error(
"Number of isolated processes must be less than total the number of processes"
)
end

end

describe '.by_scenarios' do
Expand Down
20 changes: 20 additions & 0 deletions spec/parallel_tests/test/runner_spec.rb
Expand Up @@ -146,6 +146,26 @@ def call(*args)

expect(valid_combinations).to include(actual)
end

it "groups by size and use specified number of isolation groups" do
skip if RUBY_PLATFORM == "java"
expect(ParallelTests::Test::Runner).to receive(:runtimes).
and_return({"aaa1" => 1, "aaa2" => 3, "aaa3" => 2, "bbb" => 3, "ccc" => 1, "ddd" => 2})
result = call(["aaa1", "aaa2", "aaa3", "bbb", "ccc", "ddd", "eee"], 4, isolate_count: 2, single_process: [/^aaa/], group_by: :runtime)

isolated_1, isolated_2, *groups = result
expect(isolated_1).to eq(["aaa2"])
expect(isolated_2).to eq(["aaa1", "aaa3"])
actual = groups.map(&:to_set).to_set

# both eee and ccs are the same size, so either can be in either group
valid_combinations = [
[["bbb", "eee"], ["ccc", "ddd"]].map(&:to_set).to_set,
[["bbb", "ccc"], ["eee", "ddd"]].map(&:to_set).to_set
]

expect(valid_combinations).to include(actual)
end
end
end

Expand Down

0 comments on commit d95ea27

Please sign in to comment.