parallel_specs 0.9.0 → 0.9.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.
- checksums.yaml +4 -4
- data/README.md +89 -3
- data/lib/parallel_specs/cli/dashboard.rb +92 -112
- data/lib/parallel_specs/cli.rb +140 -57
- data/lib/parallel_specs/grouper.rb +2 -2
- data/lib/parallel_specs/pids.rb +1 -1
- data/lib/parallel_specs/railtie.rb +11 -0
- data/lib/parallel_specs/rspec/dashboard_logger.rb +12 -12
- data/lib/parallel_specs/rspec/logger_base.rb +6 -6
- data/lib/parallel_specs/rspec/runner.rb +16 -16
- data/lib/parallel_specs/rspec/runtime_logger.rb +18 -9
- data/lib/parallel_specs/tasks.rb +264 -0
- data/lib/parallel_specs/test/runner.rb +31 -25
- data/lib/parallel_specs/version.rb +1 -1
- data/lib/parallel_specs.rb +30 -23
- metadata +4 -2
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "parallel_specs"
|
|
4
|
+
require "rake"
|
|
5
|
+
require "shellwords"
|
|
6
|
+
|
|
7
|
+
module ParallelSpecs
|
|
8
|
+
module Tasks
|
|
9
|
+
class << self
|
|
10
|
+
def rails_env
|
|
11
|
+
ENV["PARALLEL_SPECS_RAILS_ENV"] || "test"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run_in_parallel(command, options = {})
|
|
15
|
+
command = command.compact
|
|
16
|
+
num_processes = task_process_count(options[:count])
|
|
17
|
+
|
|
18
|
+
thread_count = options[:non_parallel] ? 1 : num_processes
|
|
19
|
+
results = Parallel.map(0...num_processes, in_threads: thread_count) do |process_number|
|
|
20
|
+
env = worker_env(process_number, num_processes)
|
|
21
|
+
expanded_command = expand_worker_env(command, env)
|
|
22
|
+
run_command(env, expanded_command)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
abort unless results.all?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def run_command(env, command)
|
|
29
|
+
system(env, *command)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def worker_env(process_number, num_processes)
|
|
33
|
+
{
|
|
34
|
+
"TEST_ENV_NUMBER" => test_env_number(process_number),
|
|
35
|
+
"PARALLEL_SPECS_GROUPS" => num_processes.to_s,
|
|
36
|
+
"DISABLE_SPRING" => "1"
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_env_number(process_number)
|
|
41
|
+
process_number.zero? ? "" : (process_number + 1).to_s
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def suppress_output(command, ignore_regex)
|
|
45
|
+
command.compact!
|
|
46
|
+
activate_pipefail = "set -o pipefail"
|
|
47
|
+
remove_ignored_lines = %{(grep -v #{Shellwords.escape(ignore_regex)} || true)}
|
|
48
|
+
|
|
49
|
+
if system("/bin/bash", "-c", "#{activate_pipefail} 2>/dev/null")
|
|
50
|
+
shell_command = "#{activate_pipefail} && (#{Shellwords.shelljoin(command)}) | #{remove_ignored_lines}"
|
|
51
|
+
["/bin/bash", "-c", shell_command]
|
|
52
|
+
else
|
|
53
|
+
command
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def suppress_schema_load_output(command)
|
|
58
|
+
suppress_output(command, '^ ->\\|^-- ')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def check_for_pending_migrations
|
|
62
|
+
%w[db:abort_if_pending_migrations app:db:abort_if_pending_migrations].each do |task_name|
|
|
63
|
+
if Rake::Task.task_defined?(task_name)
|
|
64
|
+
Rake::Task[task_name].invoke
|
|
65
|
+
break
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def purge_before_load
|
|
71
|
+
return unless defined?(ActiveRecord) && ActiveRecord.version > Gem::Version.new("4.2.0")
|
|
72
|
+
|
|
73
|
+
Rake::Task.task_defined?("db:purge") ? "db:purge" : "app:db:purge"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def schema_format_based_on_rails_version
|
|
77
|
+
if active_record_7_or_greater?
|
|
78
|
+
ActiveRecord.schema_format
|
|
79
|
+
else
|
|
80
|
+
ActiveRecord::Base.schema_format
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def schema_type_based_on_rails_version
|
|
85
|
+
if active_record_61_or_greater? || schema_format_based_on_rails_version == :ruby
|
|
86
|
+
"schema"
|
|
87
|
+
else
|
|
88
|
+
"structure"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def configured_databases
|
|
93
|
+
return [] unless defined?(ActiveRecord) && active_record_61_or_greater?
|
|
94
|
+
|
|
95
|
+
@configured_databases ||= ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def for_each_database(&block)
|
|
99
|
+
block&.call(nil)
|
|
100
|
+
return unless defined?(ActiveRecord::Tasks::DatabaseTasks)
|
|
101
|
+
return unless ActiveRecord::Tasks::DatabaseTasks.respond_to?(:for_each)
|
|
102
|
+
|
|
103
|
+
ActiveRecord::Tasks::DatabaseTasks.for_each(configured_databases) do |name|
|
|
104
|
+
block&.call(name)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def define_task_unless_defined(task_name, *args, &block)
|
|
109
|
+
return if Rake::Task.task_defined?("parallel:#{task_name}")
|
|
110
|
+
|
|
111
|
+
Rake::Task.define_task(task_name.to_sym, *args, &block)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def task_process_count(count)
|
|
117
|
+
num_processes = ParallelSpecs.determine_number_of_processes(count)
|
|
118
|
+
abort "Process count must be greater than 0" unless num_processes.positive?
|
|
119
|
+
|
|
120
|
+
num_processes
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def expand_worker_env(command, env)
|
|
124
|
+
command.map do |part|
|
|
125
|
+
part.gsub("$TEST_ENV_NUMBER", env["TEST_ENV_NUMBER"])
|
|
126
|
+
.gsub("${TEST_ENV_NUMBER}", env["TEST_ENV_NUMBER"])
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def active_record_7_or_greater?
|
|
131
|
+
ActiveRecord.version >= Gem::Version.new("7.0")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def active_record_61_or_greater?
|
|
135
|
+
ActiveRecord.version >= Gem::Version.new("6.1.0")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
namespace :parallel do
|
|
142
|
+
desc "Setup test databases via db:setup --> parallel:setup[num_cpus]"
|
|
143
|
+
ParallelSpecs::Tasks.define_task_unless_defined(:setup, :count) do |_, args|
|
|
144
|
+
command = [$PROGRAM_NAME, "db:setup", "RAILS_ENV=#{ParallelSpecs::Tasks.rails_env}"]
|
|
145
|
+
ParallelSpecs::Tasks.run_in_parallel(ParallelSpecs::Tasks.suppress_schema_load_output(command), args)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
ParallelSpecs::Tasks.for_each_database do |name|
|
|
149
|
+
task_name = "create"
|
|
150
|
+
task_name += ":#{name}" if name
|
|
151
|
+
|
|
152
|
+
desc "Create test#{" #{name}" if name} database via db:#{task_name} --> parallel:#{task_name}[num_cpus]"
|
|
153
|
+
ParallelSpecs::Tasks.define_task_unless_defined(task_name, :count) do |_, args|
|
|
154
|
+
ParallelSpecs::Tasks.run_in_parallel(
|
|
155
|
+
[$PROGRAM_NAME, "db:#{task_name}", "RAILS_ENV=#{ParallelSpecs::Tasks.rails_env}"],
|
|
156
|
+
args
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
ParallelSpecs::Tasks.for_each_database do |name|
|
|
162
|
+
task_name = "drop"
|
|
163
|
+
task_name += ":#{name}" if name
|
|
164
|
+
|
|
165
|
+
desc "Drop test#{" #{name}" if name} database via db:#{task_name} --> parallel:#{task_name}[num_cpus]"
|
|
166
|
+
ParallelSpecs::Tasks.define_task_unless_defined(task_name, :count) do |_, args|
|
|
167
|
+
ParallelSpecs::Tasks.run_in_parallel(
|
|
168
|
+
[
|
|
169
|
+
$PROGRAM_NAME,
|
|
170
|
+
"db:#{task_name}",
|
|
171
|
+
"RAILS_ENV=#{ParallelSpecs::Tasks.rails_env}",
|
|
172
|
+
"DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
|
|
173
|
+
],
|
|
174
|
+
args
|
|
175
|
+
)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
desc "Update test databases by dumping and loading --> parallel:prepare[num_cpus]"
|
|
180
|
+
ParallelSpecs::Tasks.define_task_unless_defined(:prepare, :count) do |_, args|
|
|
181
|
+
ParallelSpecs::Tasks.check_for_pending_migrations
|
|
182
|
+
|
|
183
|
+
if defined?(ActiveRecord) && [:ruby, :sql].include?(ParallelSpecs::Tasks.schema_format_based_on_rails_version)
|
|
184
|
+
type = ParallelSpecs::Tasks.schema_type_based_on_rails_version
|
|
185
|
+
Rake::Task["db:#{type}:dump"].invoke
|
|
186
|
+
ActiveRecord::Base.remove_connection if ActiveRecord::Base.configurations.any?
|
|
187
|
+
Rake::Task["parallel:load_#{type}"].invoke(args[:count])
|
|
188
|
+
else
|
|
189
|
+
task_name = Rake::Task.task_defined?("db:test:prepare") ? "db:test:prepare" : "app:db:test:prepare"
|
|
190
|
+
ParallelSpecs::Tasks.run_in_parallel([$PROGRAM_NAME, task_name], args.to_hash.merge(non_parallel: true))
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
ParallelSpecs::Tasks.for_each_database do |name|
|
|
195
|
+
task_name = "migrate"
|
|
196
|
+
task_name += ":#{name}" if name
|
|
197
|
+
|
|
198
|
+
desc "Update test#{" #{name}" if name} database via db:#{task_name} --> parallel:#{task_name}[num_cpus]"
|
|
199
|
+
ParallelSpecs::Tasks.define_task_unless_defined(task_name, :count) do |_, args|
|
|
200
|
+
ParallelSpecs::Tasks.run_in_parallel(
|
|
201
|
+
[$PROGRAM_NAME, "db:#{task_name}", "RAILS_ENV=#{ParallelSpecs::Tasks.rails_env}"],
|
|
202
|
+
args
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
desc "Rollback test databases via db:rollback --> parallel:rollback[num_cpus]"
|
|
208
|
+
ParallelSpecs::Tasks.define_task_unless_defined(:rollback, :count) do |_, args|
|
|
209
|
+
ParallelSpecs::Tasks.run_in_parallel(
|
|
210
|
+
[$PROGRAM_NAME, "db:rollback", "RAILS_ENV=#{ParallelSpecs::Tasks.rails_env}"],
|
|
211
|
+
args
|
|
212
|
+
)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
ParallelSpecs::Tasks.for_each_database do |name|
|
|
216
|
+
rails_task = "db:schema:load"
|
|
217
|
+
rails_task += ":#{name}" if name
|
|
218
|
+
|
|
219
|
+
task_name = "load_schema"
|
|
220
|
+
task_name += ":#{name}" if name
|
|
221
|
+
|
|
222
|
+
desc "Load dumped schema for test#{" #{name}" if name} database via #{rails_task} --> parallel:#{task_name}[num_cpus]"
|
|
223
|
+
ParallelSpecs::Tasks.define_task_unless_defined(task_name, :count) do |_, args|
|
|
224
|
+
command = [
|
|
225
|
+
$PROGRAM_NAME,
|
|
226
|
+
ParallelSpecs::Tasks.purge_before_load,
|
|
227
|
+
rails_task,
|
|
228
|
+
"RAILS_ENV=#{ParallelSpecs::Tasks.rails_env}",
|
|
229
|
+
"DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
|
|
230
|
+
]
|
|
231
|
+
ParallelSpecs::Tasks.run_in_parallel(ParallelSpecs::Tasks.suppress_schema_load_output(command), args)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
desc "Load structure for test databases via db:structure:load --> parallel:load_structure[num_cpus]"
|
|
236
|
+
ParallelSpecs::Tasks.define_task_unless_defined(:load_structure, :count) do |_, args|
|
|
237
|
+
ParallelSpecs::Tasks.run_in_parallel(
|
|
238
|
+
[
|
|
239
|
+
$PROGRAM_NAME,
|
|
240
|
+
ParallelSpecs::Tasks.purge_before_load,
|
|
241
|
+
"db:structure:load",
|
|
242
|
+
"RAILS_ENV=#{ParallelSpecs::Tasks.rails_env}",
|
|
243
|
+
"DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
|
|
244
|
+
],
|
|
245
|
+
args
|
|
246
|
+
)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
desc "Load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
|
|
250
|
+
ParallelSpecs::Tasks.define_task_unless_defined(:seed, :count) do |_, args|
|
|
251
|
+
ParallelSpecs::Tasks.run_in_parallel(
|
|
252
|
+
[$PROGRAM_NAME, "db:seed", "RAILS_ENV=#{ParallelSpecs::Tasks.rails_env}"],
|
|
253
|
+
args
|
|
254
|
+
)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
desc "Launch given rake command in parallel"
|
|
258
|
+
ParallelSpecs::Tasks.define_task_unless_defined(:rake, :command, :count) do |_, args|
|
|
259
|
+
ParallelSpecs::Tasks.run_in_parallel(
|
|
260
|
+
[$PROGRAM_NAME, args.command, "RAILS_ENV=#{ParallelSpecs::Tasks.rails_env}"],
|
|
261
|
+
args
|
|
262
|
+
)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "parallel_specs"
|
|
4
|
+
require "shellwords"
|
|
5
5
|
|
|
6
6
|
module ParallelSpecs
|
|
7
7
|
module Test
|
|
8
8
|
class Runner
|
|
9
9
|
RuntimeLogTooSmallError = Class.new(StandardError)
|
|
10
10
|
RuntimeLogParseError = Class.new(StandardError)
|
|
11
|
+
MissingTestFileError = Class.new(ArgumentError)
|
|
12
|
+
OUTPUT_MUTEX = Mutex.new
|
|
11
13
|
|
|
12
14
|
class << self
|
|
13
15
|
def tests_in_groups(tests, num_groups, options = {})
|
|
@@ -37,13 +39,13 @@ module ParallelSpecs
|
|
|
37
39
|
rescue RuntimeLogParseError => e
|
|
38
40
|
warn "parallel_specs: unable to use runtime log #{runtime_log_path(options)}: #{e.message}; falling back to filesize grouping"
|
|
39
41
|
known_runtimes = {}
|
|
40
|
-
rescue
|
|
42
|
+
rescue => e
|
|
41
43
|
warn "parallel_specs: unable to load runtime log #{runtime_log_path(options)}: #{e.class}: #{e.message}"
|
|
42
44
|
raise
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
if known_runtimes.size * 1.5 > tests.size
|
|
46
|
-
puts
|
|
48
|
+
puts "Using recorded test runtime"
|
|
47
49
|
sort_by_runtime(tests, known_runtimes)
|
|
48
50
|
else
|
|
49
51
|
sort_by_filesize(tests)
|
|
@@ -57,13 +59,13 @@ module ParallelSpecs
|
|
|
57
59
|
|
|
58
60
|
def execute_command(cmd, process_number, num_processes, options)
|
|
59
61
|
env = {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
"TEST_ENV_NUMBER" => test_env_number(process_number),
|
|
63
|
+
"PARALLEL_SPECS_GROUPS" => num_processes.to_s,
|
|
64
|
+
"PARALLEL_SPECS_PID_FILE" => ParallelSpecs.pid_file_path
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
if (dashboard_event_files = options[:dashboard_event_files])
|
|
66
|
-
env[
|
|
68
|
+
env["PARALLEL_SPECS_DASHBOARD_EVENT_LOG"] = dashboard_event_files.fetch(process_number)
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
execute_command_and_capture_output(env, cmd, options)
|
|
@@ -88,13 +90,13 @@ module ParallelSpecs
|
|
|
88
90
|
1
|
|
89
91
|
end
|
|
90
92
|
|
|
91
|
-
{
|
|
93
|
+
{env: env, stdout: output, exit_status: exit_status, command: cmd, seed: seed_from(output)}
|
|
92
94
|
end
|
|
93
95
|
|
|
94
96
|
def print_command(command, env = {})
|
|
95
|
-
env_string = rerun_env(env).map { |key, value| "#{key}=#{Shellwords.escape(value)}" }.join(
|
|
97
|
+
env_string = rerun_env(env).map { |key, value| "#{key}=#{Shellwords.escape(value)}" }.join(" ")
|
|
96
98
|
command_string = Shellwords.shelljoin(command)
|
|
97
|
-
puts [env_string, command_string].reject(&:empty?).join(
|
|
99
|
+
puts [env_string, command_string].reject(&:empty?).join(" ")
|
|
98
100
|
end
|
|
99
101
|
|
|
100
102
|
def rerun_command(command, seed: nil)
|
|
@@ -102,37 +104,39 @@ module ParallelSpecs
|
|
|
102
104
|
end
|
|
103
105
|
|
|
104
106
|
def command_with_seed(command, seed)
|
|
105
|
-
[*remove_command_arguments(command,
|
|
107
|
+
[*remove_command_arguments(command, "--seed"), "--seed", seed]
|
|
106
108
|
end
|
|
107
109
|
|
|
108
110
|
def find_results(test_output)
|
|
109
111
|
test_output.lines.filter_map do |line|
|
|
110
|
-
line = line.chomp.gsub(/\e\[\d+m/,
|
|
112
|
+
line = line.chomp.gsub(/\e\[\d+m/, "")
|
|
111
113
|
line if line_is_result?(line)
|
|
112
114
|
end
|
|
113
115
|
end
|
|
114
116
|
|
|
115
117
|
def summarize_results(results)
|
|
116
|
-
sum_up_results(results).sort.map { |word, count| "#{count} #{word}#{
|
|
118
|
+
sum_up_results(results).sort.map { |word, count| "#{count} #{word}#{"s" if count != 1}" }.join(", ")
|
|
117
119
|
end
|
|
118
120
|
|
|
119
121
|
protected
|
|
120
122
|
|
|
121
123
|
def capture_output(out, dashboard)
|
|
122
|
-
result = +
|
|
124
|
+
result = +""
|
|
123
125
|
begin
|
|
124
126
|
loop do
|
|
125
127
|
chunk = out.readpartial(1_000_000)
|
|
126
128
|
chunk = chunk.force_encoding(Encoding.default_internal) if Encoding.default_internal
|
|
127
129
|
result << chunk
|
|
128
|
-
next if dashboard
|
|
129
|
-
|
|
130
|
-
$stdout.print(chunk)
|
|
131
|
-
$stdout.flush
|
|
132
130
|
end
|
|
133
131
|
rescue EOFError
|
|
134
132
|
nil
|
|
135
133
|
end
|
|
134
|
+
unless dashboard
|
|
135
|
+
OUTPUT_MUTEX.synchronize do
|
|
136
|
+
$stdout.print(result)
|
|
137
|
+
$stdout.flush
|
|
138
|
+
end
|
|
139
|
+
end
|
|
136
140
|
result
|
|
137
141
|
end
|
|
138
142
|
|
|
@@ -161,7 +165,7 @@ module ParallelSpecs
|
|
|
161
165
|
File.read(path).split("\n").each_with_index.each_with_object({}) do |(line, index), times|
|
|
162
166
|
next if line.empty?
|
|
163
167
|
|
|
164
|
-
test, separator, time = line.rpartition(
|
|
168
|
+
test, separator, time = line.rpartition(":")
|
|
165
169
|
raise RuntimeLogParseError, "Invalid runtime log line #{index + 1} in #{path}: #{line.inspect}" if separator.empty? || test.empty? || time.empty?
|
|
166
170
|
|
|
167
171
|
times[test] = Float(time) if tests.include?(test)
|
|
@@ -182,9 +186,11 @@ module ParallelSpecs
|
|
|
182
186
|
def find_tests(tests, options = {})
|
|
183
187
|
tests.flat_map do |file_or_folder|
|
|
184
188
|
if File.directory?(file_or_folder)
|
|
185
|
-
filter_files(Dir[File.join(file_or_folder,
|
|
186
|
-
|
|
189
|
+
filter_files(Dir[File.join(file_or_folder, "**/*_spec.rb")].uniq.sort, options)
|
|
190
|
+
elsif File.exist?(file_or_folder)
|
|
187
191
|
filter_files([file_or_folder], options)
|
|
192
|
+
else
|
|
193
|
+
raise MissingTestFileError, "No such spec file or directory: #{file_or_folder}"
|
|
188
194
|
end
|
|
189
195
|
end.uniq
|
|
190
196
|
end
|
|
@@ -213,15 +219,15 @@ module ParallelSpecs
|
|
|
213
219
|
end
|
|
214
220
|
|
|
215
221
|
def rerun_env(env)
|
|
216
|
-
env.slice(
|
|
222
|
+
env.slice("TEST_ENV_NUMBER", "PARALLEL_SPECS_GROUPS").reject { |_key, value| value.to_s.empty? }
|
|
217
223
|
end
|
|
218
224
|
|
|
219
225
|
def test_env_number(process_number)
|
|
220
|
-
process_number.zero? ?
|
|
226
|
+
process_number.zero? ? "" : (process_number + 1).to_s
|
|
221
227
|
end
|
|
222
228
|
|
|
223
229
|
def sum_up_results(results)
|
|
224
|
-
results.join(
|
|
230
|
+
results.join(" ").gsub(/s\b/, "").scan(/(\d+) (\w+)/).each_with_object(Hash.new(0)) do |(number, word), sum|
|
|
225
231
|
sum[word] += number.to_i
|
|
226
232
|
end
|
|
227
233
|
end
|
data/lib/parallel_specs.rb
CHANGED
|
@@ -1,35 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
3
|
+
require "parallel"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
require "tempfile"
|
|
6
6
|
|
|
7
7
|
module ParallelSpecs
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
ConfigurationError = Class.new(ArgumentError)
|
|
9
|
+
WINDOWS = (RbConfig::CONFIG["host_os"] =~ /cygwin|mswin|mingw|bccwin|wince|emx/)
|
|
10
|
+
RUBY_BINARY = File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["ruby_install_name"])
|
|
10
11
|
|
|
11
|
-
autoload :CLI,
|
|
12
|
-
autoload :VERSION,
|
|
13
|
-
autoload :Grouper,
|
|
14
|
-
autoload :Pids,
|
|
12
|
+
autoload :CLI, "parallel_specs/cli"
|
|
13
|
+
autoload :VERSION, "parallel_specs/version"
|
|
14
|
+
autoload :Grouper, "parallel_specs/grouper"
|
|
15
|
+
autoload :Pids, "parallel_specs/pids"
|
|
15
16
|
|
|
16
17
|
class << self
|
|
17
18
|
def determine_number_of_processes(count)
|
|
18
|
-
|
|
19
|
-
count,
|
|
20
|
-
ENV[
|
|
21
|
-
Parallel.processor_count
|
|
22
|
-
].detect { |
|
|
19
|
+
source, value = [
|
|
20
|
+
["process count", count],
|
|
21
|
+
["PARALLEL_SPECS_PROCESSORS", ENV["PARALLEL_SPECS_PROCESSORS"]],
|
|
22
|
+
["processor count", Parallel.processor_count]
|
|
23
|
+
].detect { |_source, raw_value| !raw_value.to_s.strip.empty? }
|
|
24
|
+
|
|
25
|
+
Integer(value)
|
|
26
|
+
rescue ArgumentError
|
|
27
|
+
raise ConfigurationError, "#{source} must be an integer"
|
|
23
28
|
end
|
|
24
29
|
|
|
25
30
|
def with_pid_file
|
|
26
|
-
previous_pid_file = ENV[
|
|
27
|
-
Tempfile.open(
|
|
28
|
-
ENV[
|
|
31
|
+
previous_pid_file = ENV["PARALLEL_SPECS_PID_FILE"]
|
|
32
|
+
Tempfile.open("parallel_specs-pidfile") do |file|
|
|
33
|
+
ENV["PARALLEL_SPECS_PID_FILE"] = file.path
|
|
29
34
|
@pids = pids
|
|
30
35
|
yield
|
|
31
36
|
ensure
|
|
32
|
-
ENV[
|
|
37
|
+
ENV["PARALLEL_SPECS_PID_FILE"] = previous_pid_file
|
|
33
38
|
@pids = nil
|
|
34
39
|
end
|
|
35
40
|
end
|
|
@@ -39,11 +44,11 @@ module ParallelSpecs
|
|
|
39
44
|
end
|
|
40
45
|
|
|
41
46
|
def pid_file_available?
|
|
42
|
-
!ENV[
|
|
47
|
+
!ENV["PARALLEL_SPECS_PID_FILE"].to_s.empty?
|
|
43
48
|
end
|
|
44
49
|
|
|
45
50
|
def pid_file_path
|
|
46
|
-
ENV.fetch(
|
|
51
|
+
ENV.fetch("PARALLEL_SPECS_PID_FILE")
|
|
47
52
|
end
|
|
48
53
|
|
|
49
54
|
def stop_all_processes
|
|
@@ -68,17 +73,17 @@ module ParallelSpecs
|
|
|
68
73
|
previous = nil
|
|
69
74
|
current = File.expand_path(Dir.pwd)
|
|
70
75
|
until !File.directory?(current) || current == previous
|
|
71
|
-
return true if File.exist?(File.join(current,
|
|
76
|
+
return true if File.exist?(File.join(current, "Gemfile"))
|
|
72
77
|
|
|
73
78
|
previous = current
|
|
74
|
-
current = File.expand_path(
|
|
79
|
+
current = File.expand_path("..", current)
|
|
75
80
|
end
|
|
76
81
|
|
|
77
82
|
false
|
|
78
83
|
end
|
|
79
84
|
|
|
80
85
|
def with_ruby_binary(command)
|
|
81
|
-
WINDOWS ? [RUBY_BINARY,
|
|
86
|
+
WINDOWS ? [RUBY_BINARY, "--", command] : [command]
|
|
82
87
|
end
|
|
83
88
|
|
|
84
89
|
def now
|
|
@@ -92,3 +97,5 @@ module ParallelSpecs
|
|
|
92
97
|
end
|
|
93
98
|
end
|
|
94
99
|
end
|
|
100
|
+
|
|
101
|
+
require "parallel_specs/railtie" if defined?(Rails::Railtie)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: parallel_specs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Scott Watermasysk
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: parallel
|
|
@@ -65,10 +65,12 @@ files:
|
|
|
65
65
|
- lib/parallel_specs/cli/dashboard.rb
|
|
66
66
|
- lib/parallel_specs/grouper.rb
|
|
67
67
|
- lib/parallel_specs/pids.rb
|
|
68
|
+
- lib/parallel_specs/railtie.rb
|
|
68
69
|
- lib/parallel_specs/rspec/dashboard_logger.rb
|
|
69
70
|
- lib/parallel_specs/rspec/logger_base.rb
|
|
70
71
|
- lib/parallel_specs/rspec/runner.rb
|
|
71
72
|
- lib/parallel_specs/rspec/runtime_logger.rb
|
|
73
|
+
- lib/parallel_specs/tasks.rb
|
|
72
74
|
- lib/parallel_specs/test/runner.rb
|
|
73
75
|
- lib/parallel_specs/version.rb
|
|
74
76
|
homepage: https://github.com/scottwater/parallel_specs
|