parallel_tests 2.21.3 → 4.2.1

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.
@@ -1,34 +1,32 @@
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
- exe = executable # expensive, so we cache
12
- cmd = [exe, options[:test_options], color, spec_opts, *test_files].compact.join(" ")
10
+ cmd = [*executable, *options[:test_options], *color, *spec_opts, *test_files]
13
11
  execute_command(cmd, process_number, num_processes, options)
14
12
  end
15
13
 
16
14
  def determine_executable
17
- cmd = case
18
- when File.exist?("bin/rspec")
15
+ if File.exist?("bin/rspec")
19
16
  ParallelTests.with_ruby_binary("bin/rspec")
20
- when ParallelTests.bundler_enabled?
21
- cmd = (run("bundle show rspec-core") =~ %r{Could not find gem.*} ? "spec" : "rspec")
22
- "bundle exec #{cmd}"
17
+ elsif ParallelTests.bundler_enabled?
18
+ ["bundle", "exec", "rspec"]
23
19
  else
24
- %w[spec rspec].detect{|cmd| system "#{cmd} --version > #{DEV_NULL} 2>&1" }
20
+ ["rspec"]
25
21
  end
26
-
27
- cmd or raise("Can't find executables rspec or spec")
28
22
  end
29
23
 
30
24
  def runtime_log
31
- 'tmp/parallel_runtime_rspec.log'
25
+ "tmp/parallel_runtime_rspec.log"
26
+ end
27
+
28
+ def default_test_folder
29
+ "spec"
32
30
  end
33
31
 
34
32
  def test_file_name
@@ -49,26 +47,36 @@ module ParallelTests
49
47
  # --order rand:1234
50
48
  # --order random:1234
51
49
  def command_with_seed(cmd, seed)
52
- clean = cmd.sub(/\s--(seed\s+\d+|order\s+rand(om)?(:\d+)?)\b/, '')
53
- "#{clean} --seed #{seed}"
50
+ clean = remove_command_arguments(cmd, '--seed', '--order')
51
+ [*clean, '--seed', seed]
54
52
  end
55
53
 
54
+ # Summarize results from threads and colorize results based on failure and pending counts.
55
+ #
56
+ def summarize_results(results)
57
+ text = super
58
+ return text unless $stdout.tty?
59
+ sums = sum_up_results(results)
60
+ color =
61
+ if sums['failure'] > 0
62
+ 31 # red
63
+ elsif sums['pending'] > 0
64
+ 33 # yellow
65
+ else
66
+ 32 # green
67
+ end
68
+ "\e[#{color}m#{text}\e[0m"
69
+ end
56
70
 
57
71
  private
58
72
 
59
- # so it can be stubbed....
60
- def run(cmd)
61
- `#{cmd}`
62
- end
63
-
64
73
  def color
65
- '--color --tty' if $stdout.tty?
74
+ ['--color', '--tty'] if $stdout.tty?
66
75
  end
67
76
 
68
77
  def spec_opts
69
- options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect{|f| File.file?(f) }
70
- return unless options_file
71
- "-O #{options_file}"
78
+ options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect { |f| File.file?(f) }
79
+ ["-O", options_file] if options_file
72
80
  end
73
81
  end
74
82
  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,17 +26,19 @@ class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
27
26
  super if defined?(super)
28
27
  end
29
28
 
30
- def dump_summary(*args);end
31
- def dump_failures(*args);end
32
- def dump_failure(*args);end
33
- def dump_pending(*args);end
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(*args)
36
- return unless ENV['TEST_ENV_NUMBER'] #only record when running in parallel
37
- # TODO: Figure out why sometimes time can be less than 0
37
+ def start_dump(*)
38
+ return unless ENV['TEST_ENV_NUMBER'] # only record when running in parallel
38
39
  lock_output do
39
40
  @example_times.each do |file, time|
40
- relative_path = file.sub(/^#{Regexp.escape Dir.pwd}\//,'').sub(/^\.\//, "")
41
+ relative_path = file.sub(%r{^#{Regexp.escape Dir.pwd}/}, '').sub(%r{^\./}, "")
41
42
  @output.puts "#{relative_path}:#{time > 0 ? time : 0}"
42
43
  end
43
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
@@ -1,30 +1,36 @@
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
- ENV['RAILS_ENV'] || 'test'
9
+ 'test'
8
10
  end
9
11
 
10
12
  def load_lib
11
- $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
13
+ $LOAD_PATH << File.expand_path('..', __dir__)
12
14
  require "parallel_tests"
13
15
  end
14
16
 
15
17
  def purge_before_load
16
18
  if Gem::Version.new(Rails.version) > Gem::Version.new('4.2.0')
17
- Rake::Task.task_defined?('db:test:purge') ? 'db:test:purge' : 'app:db:test:purge'
19
+ Rake::Task.task_defined?('db:purge') ? 'db:purge' : 'app:db:purge'
18
20
  end
19
21
  end
20
22
 
21
- def run_in_parallel(cmd, options={})
23
+ def run_in_parallel(cmd, options = {})
22
24
  load_lib
23
- count = " -n #{options[:count]}" unless options[:count].to_s.empty?
25
+
24
26
  # Using the relative path to find the binary allow to run a specific version of it
25
- executable = File.expand_path("../../../bin/parallel_test", __FILE__)
26
- command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} --exec '#{cmd}'#{count}#{' --non-parallel' if options[:non_parallel]}"
27
- abort unless system(command)
27
+ executable = File.expand_path('../../bin/parallel_test', __dir__)
28
+ command = ParallelTests.with_ruby_binary(executable)
29
+ command += ['--exec', Shellwords.join(cmd)]
30
+ command += ['-n', options[:count]] unless options[:count].to_s.empty?
31
+ command << '--non-parallel' if options[:non_parallel]
32
+
33
+ abort unless system(*command)
28
34
  end
29
35
 
30
36
  # this is a crazy-complex solution for a very simple problem:
@@ -37,16 +43,14 @@ module ParallelTests
37
43
  # - pipefail makes pipe fail with exitstatus of first failed command
38
44
  # - pipefail is not supported in (zsh)
39
45
  # - defining a new rake task like silence_schema would force users to load parallel_tests in test env
40
- # - do not use ' since run_in_parallel uses them to quote stuff
41
46
  # - simple system "set -o pipefail" returns nil even though set -o pipefail exists with 0
42
47
  def suppress_output(command, ignore_regex)
43
48
  activate_pipefail = "set -o pipefail"
44
- remove_ignored_lines = %Q{(grep -v "#{ignore_regex}" || test 1)}
49
+ remove_ignored_lines = %{(grep -v #{Shellwords.escape(ignore_regex)} || true)}
45
50
 
46
- if File.executable?('/bin/bash') && system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null && test 1")
47
- # We need to shell escape single quotes (' becomes '"'"') because
48
- # run_in_parallel wraps command in single quotes
49
- %Q{/bin/bash -c '"'"'#{activate_pipefail} && (#{command}) | #{remove_ignored_lines}'"'"'}
51
+ if system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null")
52
+ shell_command = "#{activate_pipefail} && (#{Shellwords.shelljoin(command)}) | #{remove_ignored_lines}"
53
+ ['/bin/bash', '-c', shell_command]
50
54
  else
51
55
  command
52
56
  end
@@ -65,20 +69,70 @@ module ParallelTests
65
69
  end
66
70
  end
67
71
 
68
- # parallel:spec[:count, :pattern, :options]
72
+ # parallel:spec[:count, :pattern, :options, :pass_through]
69
73
  def parse_args(args)
70
74
  # order as given by user
71
- args = [args[:count], args[:pattern], args[:options]]
75
+ args = [args[:count], args[:pattern], args[:options], args[:pass_through]]
72
76
 
73
77
  # count given or empty ?
74
78
  # parallel:spec[2,models,options]
75
79
  # parallel:spec[,models,options]
76
80
  count = args.shift if args.first.to_s =~ /^\d*$/
77
- num_processes = count.to_i unless count.to_s.empty?
81
+ num_processes = (count.to_s.empty? ? nil : Integer(count))
78
82
  pattern = args.shift
79
83
  options = args.shift
84
+ pass_through = args.shift
85
+
86
+ [num_processes, pattern, options, pass_through]
87
+ end
88
+
89
+ def schema_format_based_on_rails_version
90
+ if rails_7_or_greater?
91
+ ActiveRecord.schema_format
92
+ else
93
+ ActiveRecord::Base.schema_format
94
+ end
95
+ end
80
96
 
81
- [num_processes, pattern.to_s, options.to_s]
97
+ def schema_type_based_on_rails_version
98
+ if rails_61_or_greater? || schema_format_based_on_rails_version == :ruby
99
+ "schema"
100
+ else
101
+ "structure"
102
+ end
103
+ end
104
+
105
+ def build_run_command(type, args)
106
+ count, pattern, options, pass_through = ParallelTests::Tasks.parse_args(args)
107
+ test_framework = {
108
+ 'spec' => 'rspec',
109
+ 'test' => 'test',
110
+ 'features' => 'cucumber',
111
+ 'features-spinach' => 'spinach'
112
+ }.fetch(type)
113
+
114
+ type = 'features' if test_framework == 'spinach'
115
+
116
+ # Using the relative path to find the binary allow to run a specific version of it
117
+ executable = File.expand_path('../../bin/parallel_test', __dir__)
118
+ executable = ParallelTests.with_ruby_binary(executable)
119
+
120
+ command = [*executable, type, '--type', test_framework]
121
+ command += ['-n', count.to_s] if count
122
+ command += ['--pattern', pattern] if pattern
123
+ command += ['--test-options', options] if options
124
+ command += Shellwords.shellsplit pass_through if pass_through
125
+ command
126
+ end
127
+
128
+ private
129
+
130
+ def rails_7_or_greater?
131
+ Gem::Version.new(Rails.version) >= Gem::Version.new('7.0')
132
+ end
133
+
134
+ def rails_61_or_greater?
135
+ Gem::Version.new(Rails.version) >= Gem::Version.new('6.1.0')
82
136
  end
83
137
  end
84
138
  end
@@ -86,95 +140,129 @@ end
86
140
 
87
141
  namespace :parallel do
88
142
  desc "Setup test databases via db:setup --> parallel:setup[num_cpus]"
89
- task :setup, :count do |_,args|
90
- command = "rake db:setup RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
143
+ task :setup, :count do |_, args|
144
+ command = [$0, "db:setup", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"]
91
145
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
92
146
  end
93
147
 
94
148
  desc "Create test databases via db:create --> parallel:create[num_cpus]"
95
- task :create, :count do |_,args|
96
- ParallelTests::Tasks.run_in_parallel("rake db:create RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
149
+ task :create, :count do |_, args|
150
+ ParallelTests::Tasks.run_in_parallel(
151
+ [$0, "db:create", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
152
+ args
153
+ )
97
154
  end
98
155
 
99
156
  desc "Drop test databases via db:drop --> parallel:drop[num_cpus]"
100
- task :drop, :count do |_,args|
101
- ParallelTests::Tasks.run_in_parallel("rake db:drop RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args)
157
+ task :drop, :count do |_, args|
158
+ ParallelTests::Tasks.run_in_parallel(
159
+ [
160
+ $0,
161
+ "db:drop",
162
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
163
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
164
+ ],
165
+ args
166
+ )
102
167
  end
103
168
 
104
169
  desc "Update test databases by dumping and loading --> parallel:prepare[num_cpus]"
105
- task(:prepare, [:count]) do |_,args|
170
+ task(:prepare, [:count]) do |_, args|
106
171
  ParallelTests::Tasks.check_for_pending_migrations
107
- if defined?(ActiveRecord) && ActiveRecord::Base.schema_format == :ruby
108
- # dump then load in parallel
109
- Rake::Task['db:schema:dump'].invoke
110
- Rake::Task['parallel:load_schema'].invoke(args[:count])
172
+
173
+ if defined?(ActiveRecord) && [:ruby, :sql].include?(ParallelTests::Tasks.schema_format_based_on_rails_version)
174
+ # fast: dump once, load in parallel
175
+ type = ParallelTests::Tasks.schema_type_based_on_rails_version
176
+
177
+ Rake::Task["db:#{type}:dump"].invoke
178
+
179
+ # remove database connection to prevent "database is being accessed by other users"
180
+ ActiveRecord::Base.remove_connection if ActiveRecord::Base.configurations.any?
181
+
182
+ Rake::Task["parallel:load_#{type}"].invoke(args[:count])
111
183
  else
112
- # there is no separate dump / load for schema_format :sql -> do it safe and slow
113
- args = args.to_hash.merge(:non_parallel => true) # normal merge returns nil
114
- taskname = Rake::Task.task_defined?('db:test:prepare') ? 'db:test:prepare' : 'app:db:test:prepare'
115
- ParallelTests::Tasks.run_in_parallel("rake #{taskname}", args)
184
+ # slow: dump and load in in serial
185
+ args = args.to_hash.merge(non_parallel: true) # normal merge returns nil
186
+ task_name = Rake::Task.task_defined?('db:test:prepare') ? 'db:test:prepare' : 'app:db:test:prepare'
187
+ ParallelTests::Tasks.run_in_parallel([$0, task_name], args)
188
+ next
116
189
  end
117
190
  end
118
191
 
119
192
  # when dumping/resetting takes too long
120
193
  desc "Update test databases via db:migrate --> parallel:migrate[num_cpus]"
121
- task :migrate, :count do |_,args|
122
- ParallelTests::Tasks.run_in_parallel("rake db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
194
+ task :migrate, :count do |_, args|
195
+ ParallelTests::Tasks.run_in_parallel(
196
+ [$0, "db:migrate", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
197
+ args
198
+ )
123
199
  end
124
200
 
125
201
  desc "Rollback test databases via db:rollback --> parallel:rollback[num_cpus]"
126
- task :rollback, :count do |_,args|
127
- ParallelTests::Tasks.run_in_parallel("rake db:rollback RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
202
+ task :rollback, :count do |_, args|
203
+ ParallelTests::Tasks.run_in_parallel(
204
+ [$0, "db:rollback", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
205
+ args
206
+ )
128
207
  end
129
208
 
130
209
  # just load the schema (good for integration server <-> no development db)
131
210
  desc "Load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
132
- task :load_schema, :count do |_,args|
133
- command = "rake #{ParallelTests::Tasks.purge_before_load} db:schema:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
211
+ task :load_schema, :count do |_, args|
212
+ command = [
213
+ $0,
214
+ ParallelTests::Tasks.purge_before_load,
215
+ "db:schema:load",
216
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
217
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
218
+ ]
134
219
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
135
220
  end
136
221
 
137
222
  # load the structure from the structure.sql file
138
- desc "Load structure for test databases via db:structure:load --> parallel:load_structure[num_cpus]"
139
- task :load_structure, :count do |_,args|
140
- ParallelTests::Tasks.run_in_parallel("rake #{ParallelTests::Tasks.purge_before_load} db:structure:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args)
223
+ # (faster for rails < 6.1, deprecated after and only configured by `ActiveRecord::Base.schema_format`)
224
+ desc "Load structure for test databases via db:schema:load --> parallel:load_structure[num_cpus]"
225
+ task :load_structure, :count do |_, args|
226
+ ParallelTests::Tasks.run_in_parallel(
227
+ [
228
+ $0,
229
+ ParallelTests::Tasks.purge_before_load,
230
+ "db:structure:load",
231
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
232
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
233
+ ],
234
+ args
235
+ )
141
236
  end
142
237
 
143
238
  desc "Load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
144
- task :seed, :count do |_,args|
145
- ParallelTests::Tasks.run_in_parallel("rake db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
239
+ task :seed, :count do |_, args|
240
+ ParallelTests::Tasks.run_in_parallel(
241
+ [
242
+ $0,
243
+ "db:seed",
244
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
245
+ ],
246
+ args
247
+ )
146
248
  end
147
249
 
148
250
  desc "Launch given rake command in parallel"
149
251
  task :rake, :command, :count do |_, args|
150
- ParallelTests::Tasks.run_in_parallel("RAILS_ENV=#{ParallelTests::Tasks.rails_env} rake #{args.command}", args)
252
+ ParallelTests::Tasks.run_in_parallel(
253
+ [$0, args.command, "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
254
+ args
255
+ )
151
256
  end
152
257
 
153
258
  ['test', 'spec', 'features', 'features-spinach'].each do |type|
154
259
  desc "Run #{type} in parallel with parallel:#{type}[num_cpus]"
155
- task type, [:count, :pattern, :options] do |t, args|
260
+ task type, [:count, :pattern, :options, :pass_through] do |_t, args|
156
261
  ParallelTests::Tasks.check_for_pending_migrations
157
262
  ParallelTests::Tasks.load_lib
263
+ command = ParallelTests::Tasks.build_run_command(type, args)
158
264
 
159
- count, pattern, options = ParallelTests::Tasks.parse_args(args)
160
- test_framework = {
161
- 'spec' => 'rspec',
162
- 'test' => 'test',
163
- 'features' => 'cucumber',
164
- 'features-spinach' => 'spinach',
165
- }[type]
166
-
167
- if test_framework == 'spinach'
168
- type = 'features'
169
- end
170
- # Using the relative path to find the binary allow to run a specific version of it
171
- executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
172
-
173
- command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} #{type} --type #{test_framework} " \
174
- "-n #{count} " \
175
- "--pattern '#{pattern}' " \
176
- "--test-options '#{options}'"
177
- abort unless system(command) # allow to chain tasks e.g. rake parallel:spec parallel:features
265
+ abort unless system(*command) # allow to chain tasks e.g. rake parallel:spec parallel:features
178
266
  end
179
267
  end
180
268
  end