parallel_tests 3.3.0 → 4.2.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.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require "parallel_tests/test/runner"
2
3
 
3
4
  module ParallelTests
@@ -6,24 +7,26 @@ module ParallelTests
6
7
  DEV_NULL = (WINDOWS ? "NUL" : "/dev/null")
7
8
  class << self
8
9
  def run_tests(test_files, process_number, num_processes, options)
9
- exe = executable # expensive, so we cache
10
- cmd = [exe, options[:test_options], color, spec_opts, *test_files].compact.join(" ")
10
+ cmd = [*executable, *options[:test_options], *color, *spec_opts, *test_files]
11
11
  execute_command(cmd, process_number, num_processes, options)
12
12
  end
13
13
 
14
14
  def determine_executable
15
- case
16
- when File.exist?("bin/rspec")
15
+ if File.exist?("bin/rspec")
17
16
  ParallelTests.with_ruby_binary("bin/rspec")
18
- when ParallelTests.bundler_enabled?
19
- "bundle exec rspec"
17
+ elsif ParallelTests.bundler_enabled?
18
+ ["bundle", "exec", "rspec"]
20
19
  else
21
- "rspec"
20
+ ["rspec"]
22
21
  end
23
22
  end
24
23
 
25
24
  def runtime_log
26
- 'tmp/parallel_runtime_rspec.log'
25
+ "tmp/parallel_runtime_rspec.log"
26
+ end
27
+
28
+ def default_test_folder
29
+ "spec"
27
30
  end
28
31
 
29
32
  def test_file_name
@@ -44,26 +47,36 @@ module ParallelTests
44
47
  # --order rand:1234
45
48
  # --order random:1234
46
49
  def command_with_seed(cmd, seed)
47
- clean = cmd.sub(/\s--(seed\s+\d+|order\s+rand(om)?(:\d+)?)\b/, '')
48
- "#{clean} --seed #{seed}"
50
+ clean = remove_command_arguments(cmd, '--seed', '--order')
51
+ [*clean, '--seed', seed]
49
52
  end
50
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
51
70
 
52
71
  private
53
72
 
54
- # so it can be stubbed....
55
- def run(cmd)
56
- `#{cmd}`
57
- end
58
-
59
73
  def color
60
- '--color --tty' if $stdout.tty?
74
+ ['--color', '--tty'] if $stdout.tty?
61
75
  end
62
76
 
63
77
  def spec_opts
64
- options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect{|f| File.file?(f) }
65
- return unless options_file
66
- "-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
67
80
  end
68
81
  end
69
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,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(*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
+ 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(/^#{Regexp.escape Dir.pwd}\//,'').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
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rake'
2
3
  require 'shellwords'
3
4
 
@@ -8,16 +9,8 @@ module ParallelTests
8
9
  'test'
9
10
  end
10
11
 
11
- def rake_bin
12
- # Prevent 'Exec format error' Errno::ENOEXEC on Windows
13
- return "rake" if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
14
- binstub_path = File.join('bin', 'rake')
15
- return binstub_path if File.exist?(binstub_path)
16
- "rake"
17
- end
18
-
19
12
  def load_lib
20
- $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
13
+ $LOAD_PATH << File.expand_path('..', __dir__)
21
14
  require "parallel_tests"
22
15
  end
23
16
 
@@ -27,13 +20,17 @@ module ParallelTests
27
20
  end
28
21
  end
29
22
 
30
- def run_in_parallel(cmd, options={})
23
+ def run_in_parallel(cmd, options = {})
31
24
  load_lib
32
- count = " -n #{options[:count]}" unless options[:count].to_s.empty?
25
+
33
26
  # Using the relative path to find the binary allow to run a specific version of it
34
- executable = File.expand_path("../../../bin/parallel_test", __FILE__)
35
- command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} --exec '#{cmd}'#{count}#{' --non-parallel' if options[:non_parallel]}"
36
- 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)
37
34
  end
38
35
 
39
36
  # this is a crazy-complex solution for a very simple problem:
@@ -46,16 +43,14 @@ module ParallelTests
46
43
  # - pipefail makes pipe fail with exitstatus of first failed command
47
44
  # - pipefail is not supported in (zsh)
48
45
  # - defining a new rake task like silence_schema would force users to load parallel_tests in test env
49
- # - do not use ' since run_in_parallel uses them to quote stuff
50
46
  # - simple system "set -o pipefail" returns nil even though set -o pipefail exists with 0
51
47
  def suppress_output(command, ignore_regex)
52
48
  activate_pipefail = "set -o pipefail"
53
- remove_ignored_lines = %Q{(grep -v "#{ignore_regex}" || test 1)}
49
+ remove_ignored_lines = %{(grep -v #{Shellwords.escape(ignore_regex)} || true)}
54
50
 
55
- if File.executable?('/bin/bash') && system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null && test 1")
56
- # We need to shell escape single quotes (' becomes '"'"') because
57
- # run_in_parallel wraps command in single quotes
58
- %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]
59
54
  else
60
55
  command
61
56
  end
@@ -83,12 +78,61 @@ module ParallelTests
83
78
  # parallel:spec[2,models,options]
84
79
  # parallel:spec[,models,options]
85
80
  count = args.shift if args.first.to_s =~ /^\d*$/
86
- num_processes = count.to_i unless count.to_s.empty?
81
+ num_processes = (count.to_s.empty? ? nil : Integer(count))
87
82
  pattern = args.shift
88
83
  options = args.shift
89
84
  pass_through = args.shift
90
85
 
91
- [num_processes, pattern.to_s, options.to_s, pass_through.to_s]
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
96
+
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')
92
136
  end
93
137
  end
94
138
  end
@@ -96,30 +140,40 @@ end
96
140
 
97
141
  namespace :parallel do
98
142
  desc "Setup test databases via db:setup --> parallel:setup[num_cpus]"
99
- task :setup, :count do |_,args|
100
- command = "#{ParallelTests::Tasks.rake_bin} 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}"]
101
145
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
102
146
  end
103
147
 
104
148
  desc "Create test databases via db:create --> parallel:create[num_cpus]"
105
- task :create, :count do |_,args|
149
+ task :create, :count do |_, args|
106
150
  ParallelTests::Tasks.run_in_parallel(
107
- "#{ParallelTests::Tasks.rake_bin} db:create RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
151
+ [$0, "db:create", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
152
+ args
153
+ )
108
154
  end
109
155
 
110
156
  desc "Drop test databases via db:drop --> parallel:drop[num_cpus]"
111
- task :drop, :count do |_,args|
157
+ task :drop, :count do |_, args|
112
158
  ParallelTests::Tasks.run_in_parallel(
113
- "#{ParallelTests::Tasks.rake_bin} db:drop RAILS_ENV=#{ParallelTests::Tasks.rails_env} " \
114
- "DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args)
159
+ [
160
+ $0,
161
+ "db:drop",
162
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
163
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
164
+ ],
165
+ args
166
+ )
115
167
  end
116
168
 
117
169
  desc "Update test databases by dumping and loading --> parallel:prepare[num_cpus]"
118
- task(:prepare, [:count]) do |_,args|
170
+ task(:prepare, [:count]) do |_, args|
119
171
  ParallelTests::Tasks.check_for_pending_migrations
120
- if defined?(ActiveRecord::Base) && [:ruby, :sql].include?(ActiveRecord::Base.schema_format)
172
+
173
+ if defined?(ActiveRecord) && [:ruby, :sql].include?(ParallelTests::Tasks.schema_format_based_on_rails_version)
121
174
  # fast: dump once, load in parallel
122
- type = (ActiveRecord::Base.schema_format == :ruby ? "schema" : "structure")
175
+ type = ParallelTests::Tasks.schema_type_based_on_rails_version
176
+
123
177
  Rake::Task["db:#{type}:dump"].invoke
124
178
 
125
179
  # remove database connection to prevent "database is being accessed by other users"
@@ -128,82 +182,87 @@ namespace :parallel do
128
182
  Rake::Task["parallel:load_#{type}"].invoke(args[:count])
129
183
  else
130
184
  # slow: dump and load in in serial
131
- args = args.to_hash.merge(:non_parallel => true) # normal merge returns nil
185
+ args = args.to_hash.merge(non_parallel: true) # normal merge returns nil
132
186
  task_name = Rake::Task.task_defined?('db:test:prepare') ? 'db:test:prepare' : 'app:db:test:prepare'
133
- ParallelTests::Tasks.run_in_parallel("#{ParallelTests::Tasks.rake_bin} #{task_name}", args)
187
+ ParallelTests::Tasks.run_in_parallel([$0, task_name], args)
134
188
  next
135
189
  end
136
190
  end
137
191
 
138
192
  # when dumping/resetting takes too long
139
193
  desc "Update test databases via db:migrate --> parallel:migrate[num_cpus]"
140
- task :migrate, :count do |_,args|
194
+ task :migrate, :count do |_, args|
141
195
  ParallelTests::Tasks.run_in_parallel(
142
- "#{ParallelTests::Tasks.rake_bin} db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
196
+ [$0, "db:migrate", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
197
+ args
198
+ )
143
199
  end
144
200
 
145
201
  desc "Rollback test databases via db:rollback --> parallel:rollback[num_cpus]"
146
- task :rollback, :count do |_,args|
202
+ task :rollback, :count do |_, args|
147
203
  ParallelTests::Tasks.run_in_parallel(
148
- "#{ParallelTests::Tasks.rake_bin} db:rollback RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
204
+ [$0, "db:rollback", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
205
+ args
206
+ )
149
207
  end
150
208
 
151
209
  # just load the schema (good for integration server <-> no development db)
152
210
  desc "Load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
153
- task :load_schema, :count do |_,args|
154
- command = "#{ParallelTests::Tasks.rake_bin} #{ParallelTests::Tasks.purge_before_load} " \
155
- "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
+ ]
156
219
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
157
220
  end
158
221
 
159
222
  # load the structure from the structure.sql file
160
- desc "Load structure for test databases via db:structure:load --> parallel:load_structure[num_cpus]"
161
- task :load_structure, :count do |_,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|
162
226
  ParallelTests::Tasks.run_in_parallel(
163
- "#{ParallelTests::Tasks.rake_bin} #{ParallelTests::Tasks.purge_before_load} " \
164
- "db:structure:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args)
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
+ )
165
236
  end
166
237
 
167
238
  desc "Load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
168
- task :seed, :count do |_,args|
239
+ task :seed, :count do |_, args|
169
240
  ParallelTests::Tasks.run_in_parallel(
170
- "#{ParallelTests::Tasks.rake_bin} db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
241
+ [
242
+ $0,
243
+ "db:seed",
244
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
245
+ ],
246
+ args
247
+ )
171
248
  end
172
249
 
173
250
  desc "Launch given rake command in parallel"
174
251
  task :rake, :command, :count do |_, args|
175
252
  ParallelTests::Tasks.run_in_parallel(
176
- "RAILS_ENV=#{ParallelTests::Tasks.rails_env} #{ParallelTests::Tasks.rake_bin} " \
177
- "#{args.command}", args)
253
+ [$0, args.command, "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
254
+ args
255
+ )
178
256
  end
179
257
 
180
258
  ['test', 'spec', 'features', 'features-spinach'].each do |type|
181
259
  desc "Run #{type} in parallel with parallel:#{type}[num_cpus]"
182
- task type, [:count, :pattern, :options, :pass_through] do |t, args|
260
+ task type, [:count, :pattern, :options, :pass_through] do |_t, args|
183
261
  ParallelTests::Tasks.check_for_pending_migrations
184
262
  ParallelTests::Tasks.load_lib
263
+ command = ParallelTests::Tasks.build_run_command(type, args)
185
264
 
186
- count, pattern, options, pass_through = ParallelTests::Tasks.parse_args(args)
187
- test_framework = {
188
- 'spec' => 'rspec',
189
- 'test' => 'test',
190
- 'features' => 'cucumber',
191
- 'features-spinach' => 'spinach',
192
- }[type]
193
-
194
- if test_framework == 'spinach'
195
- type = 'features'
196
- end
197
- # Using the relative path to find the binary allow to run a specific version of it
198
- executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
199
-
200
- command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} #{type} " \
201
- "--type #{test_framework} " \
202
- "-n #{count} " \
203
- "--pattern '#{pattern}' " \
204
- "--test-options '#{options}' " \
205
- "#{pass_through}"
206
- 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
207
266
  end
208
267
  end
209
268
  end