parallel_tests 2.28.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Readme.md +46 -28
- data/bin/parallel_cucumber +2 -1
- data/bin/parallel_rspec +2 -1
- data/bin/parallel_spinach +2 -1
- data/bin/parallel_test +2 -1
- data/lib/parallel_tests.rb +12 -12
- data/lib/parallel_tests/cli.rb +133 -68
- data/lib/parallel_tests/cucumber/failures_logger.rb +1 -1
- data/lib/parallel_tests/cucumber/features_with_steps.rb +32 -0
- data/lib/parallel_tests/cucumber/runner.rb +8 -5
- data/lib/parallel_tests/cucumber/scenario_line_logger.rb +18 -16
- data/lib/parallel_tests/cucumber/scenarios.rb +20 -30
- data/lib/parallel_tests/gherkin/io.rb +2 -3
- data/lib/parallel_tests/gherkin/listener.rb +10 -12
- data/lib/parallel_tests/gherkin/runner.rb +20 -21
- data/lib/parallel_tests/gherkin/runtime_logger.rb +3 -2
- data/lib/parallel_tests/grouper.rb +92 -28
- data/lib/parallel_tests/pids.rb +4 -3
- data/lib/parallel_tests/railtie.rb +1 -0
- data/lib/parallel_tests/rspec/failures_logger.rb +2 -2
- data/lib/parallel_tests/rspec/logger_base.rb +9 -7
- data/lib/parallel_tests/rspec/runner.rb +27 -12
- data/lib/parallel_tests/rspec/runtime_logger.rb +12 -10
- data/lib/parallel_tests/rspec/summary_logger.rb +2 -3
- data/lib/parallel_tests/spinach/runner.rb +6 -2
- data/lib/parallel_tests/tasks.rb +81 -40
- data/lib/parallel_tests/test/runner.rb +54 -37
- data/lib/parallel_tests/test/runtime_logger.rb +19 -14
- data/lib/parallel_tests/version.rb +2 -1
- metadata +11 -7
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'parallel_tests/gherkin/io'
|
2
3
|
|
3
4
|
module ParallelTests
|
@@ -14,12 +15,12 @@ module ParallelTests
|
|
14
15
|
end
|
15
16
|
|
16
17
|
config.on_event :test_case_finished do |event|
|
17
|
-
@example_times[event.test_case.
|
18
|
+
@example_times[event.test_case.location.file] += ParallelTests.now.to_f - @start_at
|
18
19
|
end
|
19
20
|
|
20
21
|
config.on_event :test_run_finished do |_|
|
21
22
|
lock_output do
|
22
|
-
@io.puts
|
23
|
+
@io.puts(@example_times.map { |file, time| "#{file}:#{time}" })
|
23
24
|
end
|
24
25
|
end
|
25
26
|
end
|
@@ -1,35 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module ParallelTests
|
2
3
|
class Grouper
|
3
4
|
class << self
|
4
5
|
def by_steps(tests, num_groups, options)
|
5
|
-
features_with_steps =
|
6
|
+
features_with_steps = group_by_features_with_steps(tests, options)
|
6
7
|
in_even_groups_by_size(features_with_steps, num_groups)
|
7
8
|
end
|
8
9
|
|
9
|
-
def by_scenarios(tests, num_groups, options={})
|
10
|
+
def by_scenarios(tests, num_groups, options = {})
|
10
11
|
scenarios = group_by_scenarios(tests, options)
|
11
12
|
in_even_groups_by_size(scenarios, num_groups)
|
12
13
|
end
|
13
14
|
|
14
|
-
def in_even_groups_by_size(items, num_groups, options= {})
|
15
|
-
groups = Array.new(num_groups) { {:
|
15
|
+
def in_even_groups_by_size(items, num_groups, options = {})
|
16
|
+
groups = Array.new(num_groups) { { items: [], size: 0 } }
|
17
|
+
|
18
|
+
return specify_groups(items, num_groups, options, groups) if options[:specify_groups]
|
16
19
|
|
17
20
|
# add all files that should run in a single process to one group
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
single_process_patterns = options[:single_process] || []
|
22
|
+
|
23
|
+
single_items, items = items.partition do |item, _size|
|
24
|
+
single_process_patterns.any? { |pattern| item =~ pattern }
|
25
|
+
end
|
26
|
+
|
27
|
+
isolate_count = isolate_count(options)
|
28
|
+
|
29
|
+
if isolate_count >= num_groups
|
30
|
+
raise 'Number of isolated processes must be less than total the number of processes'
|
31
|
+
end
|
32
|
+
|
33
|
+
if isolate_count >= num_groups
|
34
|
+
raise 'Number of isolated processes must be >= total number of processes'
|
21
35
|
end
|
22
36
|
|
23
|
-
|
24
|
-
|
37
|
+
if isolate_count >= 1
|
38
|
+
# add all files that should run in a multiple isolated processes to their own groups
|
39
|
+
group_features_by_size(items_to_group(single_items), groups[0..(isolate_count - 1)])
|
40
|
+
# group the non-isolated by size
|
41
|
+
group_features_by_size(items_to_group(items), groups[isolate_count..-1])
|
42
|
+
else
|
43
|
+
# add all files that should run in a single non-isolated process to first group
|
44
|
+
single_items.each { |item, size| add_to_group(groups.first, item, size) }
|
45
|
+
|
46
|
+
# group all by size
|
47
|
+
group_features_by_size(items_to_group(items), groups)
|
48
|
+
end
|
25
49
|
|
26
50
|
groups.map! { |g| g[:items].sort }
|
27
51
|
end
|
28
52
|
|
29
53
|
private
|
30
54
|
|
55
|
+
def specify_groups(items, num_groups, options, groups)
|
56
|
+
specify_test_process_groups = options[:specify_groups].split('|')
|
57
|
+
if specify_test_process_groups.count > num_groups
|
58
|
+
raise 'Number of processes separated by pipe must be less than or equal to the total number of processes'
|
59
|
+
end
|
60
|
+
|
61
|
+
all_specified_tests = specify_test_process_groups.map { |group| group.split(',') }.flatten
|
62
|
+
specified_items_found, items = items.partition { |item, _size| all_specified_tests.include?(item) }
|
63
|
+
|
64
|
+
specified_specs_not_found = all_specified_tests - specified_items_found.map(&:first)
|
65
|
+
if specified_specs_not_found.any?
|
66
|
+
raise "Could not find #{specified_specs_not_found} from --specify-groups in the selected files & folders"
|
67
|
+
end
|
68
|
+
|
69
|
+
if specify_test_process_groups.count == num_groups && items.flatten.any?
|
70
|
+
raise(
|
71
|
+
<<~ERROR
|
72
|
+
The number of groups in --specify-groups matches the number of groups from -n but there were other specs
|
73
|
+
found in the selected files & folders not specified in --specify-groups. Make sure -n is larger than the
|
74
|
+
number of processes in --specify-groups if there are other specs that need to be run. The specs that aren't run:
|
75
|
+
#{items.map(&:first)}
|
76
|
+
ERROR
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
# First order the specify_groups into the main groups array
|
81
|
+
specify_test_process_groups.each_with_index do |specify_test_process, i|
|
82
|
+
groups[i] = specify_test_process.split(',')
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return early when processed specify_groups tests exactly match the items passed in
|
86
|
+
return groups if specify_test_process_groups.count == num_groups
|
87
|
+
|
88
|
+
# Now sort the rest of the items into the main groups array
|
89
|
+
specified_range = specify_test_process_groups.count..-1
|
90
|
+
remaining_groups = groups[specified_range]
|
91
|
+
group_features_by_size(items_to_group(items), remaining_groups)
|
92
|
+
# Don't sort all the groups, only sort the ones not specified in specify_groups
|
93
|
+
sorted_groups = remaining_groups.map { |g| g[:items].sort }
|
94
|
+
groups[specified_range] = sorted_groups
|
95
|
+
|
96
|
+
groups
|
97
|
+
end
|
98
|
+
|
99
|
+
def isolate_count(options)
|
100
|
+
if options[:isolate_count] && options[:isolate_count] > 1
|
101
|
+
options[:isolate_count]
|
102
|
+
elsif options[:isolate]
|
103
|
+
1
|
104
|
+
else
|
105
|
+
0
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
31
109
|
def largest_first(files)
|
32
|
-
files.sort_by{|_item, size| size }.reverse
|
110
|
+
files.sort_by { |_item, size| size }.reverse
|
33
111
|
end
|
34
112
|
|
35
113
|
def smallest_group(groups)
|
@@ -41,26 +119,12 @@ module ParallelTests
|
|
41
119
|
group[:size] += size
|
42
120
|
end
|
43
121
|
|
44
|
-
def
|
45
|
-
require '
|
46
|
-
|
47
|
-
parser = ::Gherkin::Parser.new
|
48
|
-
# format of hash will be FILENAME => NUM_STEPS
|
49
|
-
steps_per_file = tests.each_with_object({}) do |file,steps|
|
50
|
-
feature = parser.parse(File.read(file)).fetch(:feature)
|
51
|
-
|
52
|
-
# skip feature if it matches tag regex
|
53
|
-
next if feature[:tags].grep(ignore_tag_pattern).any?
|
54
|
-
|
55
|
-
# count the number of steps in the file
|
56
|
-
# will only include a feature if the regex does not match
|
57
|
-
all_steps = feature[:children].map{|a| a[:steps].count if a[:tags].grep(ignore_tag_pattern).empty? }.compact
|
58
|
-
steps[file] = all_steps.inject(0,:+)
|
59
|
-
end
|
60
|
-
steps_per_file.sort_by { |_, value| -value }
|
122
|
+
def group_by_features_with_steps(tests, options)
|
123
|
+
require 'parallel_tests/cucumber/features_with_steps'
|
124
|
+
ParallelTests::Cucumber::FeaturesWithSteps.all(tests, options)
|
61
125
|
end
|
62
126
|
|
63
|
-
def group_by_scenarios(tests, options={})
|
127
|
+
def group_by_scenarios(tests, options = {})
|
64
128
|
require 'parallel_tests/cucumber/scenarios'
|
65
129
|
ParallelTests::Cucumber::Scenarios.all(tests, options)
|
66
130
|
end
|
data/lib/parallel_tests/pids.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'json'
|
2
3
|
|
3
4
|
module ParallelTests
|
4
5
|
class Pids
|
5
|
-
attr_reader :
|
6
|
+
attr_reader :file_path, :mutex
|
6
7
|
|
7
8
|
def initialize(file_path)
|
8
9
|
@file_path = file_path
|
@@ -52,8 +53,8 @@ module ParallelTests
|
|
52
53
|
sync { IO.write(file_path, pids.to_json) }
|
53
54
|
end
|
54
55
|
|
55
|
-
def sync
|
56
|
-
mutex.synchronize
|
56
|
+
def sync(&block)
|
57
|
+
mutex.synchronize(&block)
|
57
58
|
end
|
58
59
|
end
|
59
60
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'parallel_tests/rspec/logger_base'
|
2
3
|
require 'parallel_tests/rspec/runner'
|
3
4
|
|
4
5
|
class ParallelTests::RSpec::FailuresLogger < ParallelTests::RSpec::LoggerBase
|
5
6
|
if RSPEC_2
|
6
|
-
def dump_failures(*args)
|
7
|
-
end
|
7
|
+
def dump_failures(*args); end
|
8
8
|
else
|
9
9
|
RSpec::Core::Formatters.register self, :dump_summary
|
10
10
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module ParallelTests
|
2
3
|
module RSpec
|
3
4
|
end
|
@@ -13,26 +14,27 @@ class ParallelTests::RSpec::LoggerBase < RSpec::Core::Formatters::BaseTextFormat
|
|
13
14
|
|
14
15
|
@output ||= args[0]
|
15
16
|
|
16
|
-
|
17
|
+
case @output
|
18
|
+
when String # a path ?
|
17
19
|
FileUtils.mkdir_p(File.dirname(@output))
|
18
|
-
File.open(@output, 'w'){} # overwrite previous results
|
20
|
+
File.open(@output, 'w') {} # overwrite previous results
|
19
21
|
@output = File.open(@output, 'a')
|
20
|
-
|
22
|
+
when File # close and restart in append mode
|
21
23
|
@output.close
|
22
24
|
@output = File.open(@output.path, 'a')
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
|
-
#stolen from Rspec
|
27
|
-
def close(*
|
28
|
-
@output.close
|
28
|
+
# stolen from Rspec
|
29
|
+
def close(*)
|
30
|
+
@output.close if (IO === @output) & (@output != $stdout)
|
29
31
|
end
|
30
32
|
|
31
33
|
protected
|
32
34
|
|
33
35
|
# do not let multiple processes get in each others way
|
34
36
|
def lock_output
|
35
|
-
if
|
37
|
+
if @output.is_a?(File)
|
36
38
|
begin
|
37
39
|
@output.flock File::LOCK_EX
|
38
40
|
yield
|
@@ -1,11 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "parallel_tests/test/runner"
|
2
3
|
|
3
4
|
module ParallelTests
|
4
5
|
module RSpec
|
5
6
|
class Runner < ParallelTests::Test::Runner
|
6
7
|
DEV_NULL = (WINDOWS ? "NUL" : "/dev/null")
|
7
|
-
NAME = 'RSpec'
|
8
|
-
|
9
8
|
class << self
|
10
9
|
def run_tests(test_files, process_number, num_processes, options)
|
11
10
|
exe = executable # expensive, so we cache
|
@@ -14,21 +13,21 @@ module ParallelTests
|
|
14
13
|
end
|
15
14
|
|
16
15
|
def determine_executable
|
17
|
-
|
18
|
-
when File.exist?("bin/rspec")
|
16
|
+
if File.exist?("bin/rspec")
|
19
17
|
ParallelTests.with_ruby_binary("bin/rspec")
|
20
|
-
|
21
|
-
|
22
|
-
"bundle exec #{cmd}"
|
18
|
+
elsif ParallelTests.bundler_enabled?
|
19
|
+
"bundle exec rspec"
|
23
20
|
else
|
24
|
-
|
21
|
+
"rspec"
|
25
22
|
end
|
26
|
-
|
27
|
-
cmd or raise("Can't find executables rspec or spec")
|
28
23
|
end
|
29
24
|
|
30
25
|
def runtime_log
|
31
|
-
|
26
|
+
"tmp/parallel_runtime_rspec.log"
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_test_folder
|
30
|
+
"spec"
|
32
31
|
end
|
33
32
|
|
34
33
|
def test_file_name
|
@@ -53,6 +52,22 @@ module ParallelTests
|
|
53
52
|
"#{clean} --seed #{seed}"
|
54
53
|
end
|
55
54
|
|
55
|
+
# Summarize results from threads and colorize results based on failure and pending counts.
|
56
|
+
#
|
57
|
+
def summarize_results(results)
|
58
|
+
text = super
|
59
|
+
return text unless $stdout.tty?
|
60
|
+
sums = sum_up_results(results)
|
61
|
+
color =
|
62
|
+
if sums['failure'] > 0
|
63
|
+
31 # red
|
64
|
+
elsif sums['pending'] > 0
|
65
|
+
33 # yellow
|
66
|
+
else
|
67
|
+
32 # green
|
68
|
+
end
|
69
|
+
"\e[#{color}m#{text}\e[0m"
|
70
|
+
end
|
56
71
|
|
57
72
|
private
|
58
73
|
|
@@ -66,7 +81,7 @@ module ParallelTests
|
|
66
81
|
end
|
67
82
|
|
68
83
|
def spec_opts
|
69
|
-
options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect{|f| File.file?(f) }
|
84
|
+
options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect { |f| File.file?(f) }
|
70
85
|
return unless options_file
|
71
86
|
"-O #{options_file}"
|
72
87
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'parallel_tests'
|
2
3
|
require 'parallel_tests/rspec/logger_base'
|
3
4
|
|
@@ -8,9 +9,7 @@ class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
|
|
8
9
|
@group_nesting = 0
|
9
10
|
end
|
10
11
|
|
11
|
-
unless RSPEC_2
|
12
|
-
RSpec::Core::Formatters.register self, :example_group_started, :example_group_finished, :start_dump
|
13
|
-
end
|
12
|
+
RSpec::Core::Formatters.register self, :example_group_started, :example_group_finished, :start_dump unless RSPEC_2
|
14
13
|
|
15
14
|
def example_group_started(example_group)
|
16
15
|
@time = ParallelTests.now if @group_nesting == 0
|
@@ -27,16 +26,19 @@ class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
|
|
27
26
|
super if defined?(super)
|
28
27
|
end
|
29
28
|
|
30
|
-
def dump_summary(*
|
31
|
-
|
32
|
-
def
|
33
|
-
|
29
|
+
def dump_summary(*); end
|
30
|
+
|
31
|
+
def dump_failures(*); end
|
32
|
+
|
33
|
+
def dump_failure(*); end
|
34
|
+
|
35
|
+
def dump_pending(*); end
|
34
36
|
|
35
|
-
def start_dump(*
|
36
|
-
return unless ENV['TEST_ENV_NUMBER'] #only record when running in parallel
|
37
|
+
def start_dump(*)
|
38
|
+
return unless ENV['TEST_ENV_NUMBER'] # only record when running in parallel
|
37
39
|
lock_output do
|
38
40
|
@example_times.each do |file, time|
|
39
|
-
relative_path = file.sub(
|
41
|
+
relative_path = file.sub(%r{^#{Regexp.escape Dir.pwd}/}, '').sub(%r{^\./}, "")
|
40
42
|
@output.puts "#{relative_path}:#{time > 0 ? time : 0}"
|
41
43
|
end
|
42
44
|
end
|
@@ -1,9 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'parallel_tests/rspec/failures_logger'
|
2
3
|
|
3
4
|
class ParallelTests::RSpec::SummaryLogger < ParallelTests::RSpec::LoggerBase
|
4
|
-
unless RSPEC_2
|
5
|
-
RSpec::Core::Formatters.register self, :dump_failures
|
6
|
-
end
|
5
|
+
RSpec::Core::Formatters.register self, :dump_failures unless RSPEC_2
|
7
6
|
|
8
7
|
def dump_failures(*args)
|
9
8
|
lock_output { super }
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "parallel_tests/gherkin/runner"
|
2
3
|
|
3
4
|
module ParallelTests
|
@@ -8,11 +9,14 @@ module ParallelTests
|
|
8
9
|
'spinach'
|
9
10
|
end
|
10
11
|
|
12
|
+
def default_test_folder
|
13
|
+
'features'
|
14
|
+
end
|
15
|
+
|
11
16
|
def runtime_logging
|
12
|
-
#Not Yet Supported
|
17
|
+
# Not Yet Supported
|
13
18
|
""
|
14
19
|
end
|
15
|
-
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
data/lib/parallel_tests/tasks.rb
CHANGED
@@ -1,10 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'rake'
|
3
|
+
require 'shellwords'
|
2
4
|
|
3
5
|
module ParallelTests
|
4
6
|
module Tasks
|
5
7
|
class << self
|
6
8
|
def rails_env
|
7
|
-
|
9
|
+
'test'
|
10
|
+
end
|
11
|
+
|
12
|
+
def rake_bin
|
13
|
+
# Prevent 'Exec format error' Errno::ENOEXEC on Windows
|
14
|
+
return "rake" if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
15
|
+
binstub_path = File.join('bin', 'rake')
|
16
|
+
return binstub_path if File.exist?(binstub_path)
|
17
|
+
"rake"
|
8
18
|
end
|
9
19
|
|
10
20
|
def load_lib
|
@@ -18,12 +28,13 @@ module ParallelTests
|
|
18
28
|
end
|
19
29
|
end
|
20
30
|
|
21
|
-
def run_in_parallel(cmd, options={})
|
31
|
+
def run_in_parallel(cmd, options = {})
|
22
32
|
load_lib
|
23
33
|
count = " -n #{options[:count]}" unless options[:count].to_s.empty?
|
24
34
|
# Using the relative path to find the binary allow to run a specific version of it
|
25
|
-
executable = File.expand_path(
|
26
|
-
|
35
|
+
executable = File.expand_path('../../bin/parallel_test', __dir__)
|
36
|
+
non_parallel = (options[:non_parallel] ? ' --non-parallel' : '')
|
37
|
+
command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} --exec '#{cmd}'#{count}#{non_parallel}"
|
27
38
|
abort unless system(command)
|
28
39
|
end
|
29
40
|
|
@@ -41,12 +52,12 @@ module ParallelTests
|
|
41
52
|
# - simple system "set -o pipefail" returns nil even though set -o pipefail exists with 0
|
42
53
|
def suppress_output(command, ignore_regex)
|
43
54
|
activate_pipefail = "set -o pipefail"
|
44
|
-
remove_ignored_lines = %
|
55
|
+
remove_ignored_lines = %{(grep -v "#{ignore_regex}" || test 1)}
|
45
56
|
|
46
57
|
if File.executable?('/bin/bash') && system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null && test 1")
|
47
58
|
# We need to shell escape single quotes (' becomes '"'"') because
|
48
59
|
# run_in_parallel wraps command in single quotes
|
49
|
-
%
|
60
|
+
%{/bin/bash -c '"'"'#{activate_pipefail} && (#{command}) | #{remove_ignored_lines}'"'"'}
|
50
61
|
else
|
51
62
|
command
|
52
63
|
end
|
@@ -74,7 +85,7 @@ module ParallelTests
|
|
74
85
|
# parallel:spec[2,models,options]
|
75
86
|
# parallel:spec[,models,options]
|
76
87
|
count = args.shift if args.first.to_s =~ /^\d*$/
|
77
|
-
num_processes = count.
|
88
|
+
num_processes = (count.to_s.empty? ? nil : Integer(count))
|
78
89
|
pattern = args.shift
|
79
90
|
options = args.shift
|
80
91
|
pass_through = args.shift
|
@@ -87,73 +98,104 @@ end
|
|
87
98
|
|
88
99
|
namespace :parallel do
|
89
100
|
desc "Setup test databases via db:setup --> parallel:setup[num_cpus]"
|
90
|
-
task :setup, :count do |_,args|
|
91
|
-
command = "
|
101
|
+
task :setup, :count do |_, args|
|
102
|
+
command = "#{ParallelTests::Tasks.rake_bin} db:setup RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
|
92
103
|
ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
|
93
104
|
end
|
94
105
|
|
95
106
|
desc "Create test databases via db:create --> parallel:create[num_cpus]"
|
96
|
-
task :create, :count do |_,args|
|
97
|
-
ParallelTests::Tasks.run_in_parallel(
|
107
|
+
task :create, :count do |_, args|
|
108
|
+
ParallelTests::Tasks.run_in_parallel(
|
109
|
+
"#{ParallelTests::Tasks.rake_bin} db:create RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
|
110
|
+
)
|
98
111
|
end
|
99
112
|
|
100
113
|
desc "Drop test databases via db:drop --> parallel:drop[num_cpus]"
|
101
|
-
task :drop, :count do |_,args|
|
102
|
-
ParallelTests::Tasks.run_in_parallel(
|
114
|
+
task :drop, :count do |_, args|
|
115
|
+
ParallelTests::Tasks.run_in_parallel(
|
116
|
+
"#{ParallelTests::Tasks.rake_bin} db:drop RAILS_ENV=#{ParallelTests::Tasks.rails_env} " \
|
117
|
+
"DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args
|
118
|
+
)
|
103
119
|
end
|
104
120
|
|
105
121
|
desc "Update test databases by dumping and loading --> parallel:prepare[num_cpus]"
|
106
|
-
task(:prepare, [:count]) do |_,args|
|
122
|
+
task(:prepare, [:count]) do |_, args|
|
107
123
|
ParallelTests::Tasks.check_for_pending_migrations
|
108
|
-
if defined?(ActiveRecord) && ActiveRecord::Base.schema_format
|
109
|
-
# dump
|
110
|
-
|
111
|
-
|
124
|
+
if defined?(ActiveRecord::Base) && [:ruby, :sql].include?(ActiveRecord::Base.schema_format)
|
125
|
+
# fast: dump once, load in parallel
|
126
|
+
type =
|
127
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('6.1.0')
|
128
|
+
"schema"
|
129
|
+
else
|
130
|
+
ActiveRecord::Base.schema_format == :ruby ? "schema" : "structure"
|
131
|
+
end
|
132
|
+
|
133
|
+
Rake::Task["db:#{type}:dump"].invoke
|
134
|
+
|
135
|
+
# remove database connection to prevent "database is being accessed by other users"
|
136
|
+
ActiveRecord::Base.remove_connection if ActiveRecord::Base.configurations.any?
|
137
|
+
|
138
|
+
Rake::Task["parallel:load_#{type}"].invoke(args[:count])
|
112
139
|
else
|
113
|
-
#
|
114
|
-
args = args.to_hash.merge(:
|
115
|
-
|
116
|
-
ParallelTests::Tasks.run_in_parallel("
|
140
|
+
# slow: dump and load in in serial
|
141
|
+
args = args.to_hash.merge(non_parallel: true) # normal merge returns nil
|
142
|
+
task_name = Rake::Task.task_defined?('db:test:prepare') ? 'db:test:prepare' : 'app:db:test:prepare'
|
143
|
+
ParallelTests::Tasks.run_in_parallel("#{ParallelTests::Tasks.rake_bin} #{task_name}", args)
|
144
|
+
next
|
117
145
|
end
|
118
146
|
end
|
119
147
|
|
120
148
|
# when dumping/resetting takes too long
|
121
149
|
desc "Update test databases via db:migrate --> parallel:migrate[num_cpus]"
|
122
|
-
task :migrate, :count do |_,args|
|
123
|
-
ParallelTests::Tasks.run_in_parallel(
|
150
|
+
task :migrate, :count do |_, args|
|
151
|
+
ParallelTests::Tasks.run_in_parallel(
|
152
|
+
"#{ParallelTests::Tasks.rake_bin} db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
|
153
|
+
)
|
124
154
|
end
|
125
155
|
|
126
156
|
desc "Rollback test databases via db:rollback --> parallel:rollback[num_cpus]"
|
127
|
-
task :rollback, :count do |_,args|
|
128
|
-
ParallelTests::Tasks.run_in_parallel(
|
157
|
+
task :rollback, :count do |_, args|
|
158
|
+
ParallelTests::Tasks.run_in_parallel(
|
159
|
+
"#{ParallelTests::Tasks.rake_bin} db:rollback RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
|
160
|
+
)
|
129
161
|
end
|
130
162
|
|
131
163
|
# just load the schema (good for integration server <-> no development db)
|
132
164
|
desc "Load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
|
133
|
-
task :load_schema, :count do |_,args|
|
134
|
-
command = "
|
165
|
+
task :load_schema, :count do |_, args|
|
166
|
+
command = "#{ParallelTests::Tasks.rake_bin} #{ParallelTests::Tasks.purge_before_load} " \
|
167
|
+
"db:schema:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
|
135
168
|
ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
|
136
169
|
end
|
137
170
|
|
138
171
|
# load the structure from the structure.sql file
|
139
|
-
|
140
|
-
|
141
|
-
|
172
|
+
# (faster for rails < 6.1, deprecated after and only configured by `ActiveRecord::Base.schema_format`)
|
173
|
+
desc "Load structure for test databases via db:schema:load --> parallel:load_structure[num_cpus]"
|
174
|
+
task :load_structure, :count do |_, args|
|
175
|
+
ParallelTests::Tasks.run_in_parallel(
|
176
|
+
"#{ParallelTests::Tasks.rake_bin} #{ParallelTests::Tasks.purge_before_load} " \
|
177
|
+
"db:structure:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args
|
178
|
+
)
|
142
179
|
end
|
143
180
|
|
144
181
|
desc "Load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
|
145
|
-
task :seed, :count do |_,args|
|
146
|
-
ParallelTests::Tasks.run_in_parallel(
|
182
|
+
task :seed, :count do |_, args|
|
183
|
+
ParallelTests::Tasks.run_in_parallel(
|
184
|
+
"#{ParallelTests::Tasks.rake_bin} db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
|
185
|
+
)
|
147
186
|
end
|
148
187
|
|
149
188
|
desc "Launch given rake command in parallel"
|
150
189
|
task :rake, :command, :count do |_, args|
|
151
|
-
ParallelTests::Tasks.run_in_parallel(
|
190
|
+
ParallelTests::Tasks.run_in_parallel(
|
191
|
+
"RAILS_ENV=#{ParallelTests::Tasks.rails_env} #{ParallelTests::Tasks.rake_bin} " \
|
192
|
+
"#{args.command}", args
|
193
|
+
)
|
152
194
|
end
|
153
195
|
|
154
196
|
['test', 'spec', 'features', 'features-spinach'].each do |type|
|
155
197
|
desc "Run #{type} in parallel with parallel:#{type}[num_cpus]"
|
156
|
-
task type, [:count, :pattern, :options, :pass_through] do |
|
198
|
+
task type, [:count, :pattern, :options, :pass_through] do |_t, args|
|
157
199
|
ParallelTests::Tasks.check_for_pending_migrations
|
158
200
|
ParallelTests::Tasks.load_lib
|
159
201
|
|
@@ -162,16 +204,15 @@ namespace :parallel do
|
|
162
204
|
'spec' => 'rspec',
|
163
205
|
'test' => 'test',
|
164
206
|
'features' => 'cucumber',
|
165
|
-
'features-spinach' => 'spinach'
|
207
|
+
'features-spinach' => 'spinach'
|
166
208
|
}[type]
|
167
209
|
|
168
|
-
if test_framework == 'spinach'
|
169
|
-
type = 'features'
|
170
|
-
end
|
210
|
+
type = 'features' if test_framework == 'spinach'
|
171
211
|
# Using the relative path to find the binary allow to run a specific version of it
|
172
212
|
executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
|
173
213
|
|
174
|
-
command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} #{type}
|
214
|
+
command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} #{type} " \
|
215
|
+
"--type #{test_framework} " \
|
175
216
|
"-n #{count} " \
|
176
217
|
"--pattern '#{pattern}' " \
|
177
218
|
"--test-options '#{options}' " \
|