parallel_cucumber 0.2.18 → 0.2.23

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 910908ef4d933b4914d5ea9872946844feaf4755fde1e33e1739438ed2bca2d5
4
- data.tar.gz: 0cc6af7cd57aa6c29525514a497ecfdd82df69536782e383c36fbd47851c50fe
3
+ metadata.gz: 9bb333397a44aa5a42a0ab3fd0b2ff757727eb9fc63bb01960ad1a6d92f7d40e
4
+ data.tar.gz: 7615b9ececcb23e8e8ce863e1bc77a021a28f14cfadd710bbc9e5a7b7d68c329
5
5
  SHA512:
6
- metadata.gz: 4a5f7ea51c8407311ad96825e829e6a823cb6e7a6db37bc498c9f785485153d18f43dd616b0d351e69bdc869437603267edbc02de69ad90ddb26bec30422354f
7
- data.tar.gz: 77878547532ac93709d73e5eab115fadb56deb6709b0a13716fd020e04643e359eed868bc10ba3313c1893ffd94e6e7e6419040813ee420dba59a3cff72aeef1
6
+ metadata.gz: 969b2a5e2dafad3fd558ba5e0cfc9ea0cbb7f2510c8ecb3d968668490b9b86319c123447989998f3e4370a45153e8b13c1eabe6060457955d264dda81e3c5b74
7
+ data.tar.gz: 2065449df4c9997df9f9e19bbbedebe8b64679d307fe91b4e4ce4106e82b8270a51e13d8706976a3caaa1cb2b85f8aacf9eee178e7b31cc91f6fa059db2e135d
@@ -8,7 +8,6 @@ module ParallelCucumber
8
8
  batch_size: 1,
9
9
  batch_timeout: 600,
10
10
  setup_timeout: 30,
11
- precheck_timeout: 30,
12
11
  batch_error_timeout: 30,
13
12
  cucumber_options: '',
14
13
  debug: false,
@@ -90,10 +89,6 @@ module ParallelCucumber
90
89
  options[:test_command] = test_command
91
90
  end
92
91
 
93
- opts.on('--pre-batch-check COMMAND', 'Command causing worker to quit on exit failure') do |pre_check|
94
- options[:pre_check] = pre_check
95
- end
96
-
97
92
  opts.on('--log-dir DIR', 'Directory for worker logfiles') do |log_dir|
98
93
  options[:log_dir] = log_dir
99
94
  end
@@ -173,13 +168,6 @@ module ParallelCucumber
173
168
  options[:batch_timeout] = batch_timeout
174
169
  end
175
170
 
176
- help_message = <<-TEXT.gsub(/\s+/, ' ').strip
177
- Timeout for each test precheck. Default is #{DEFAULTS[:batch_timeout]}
178
- TEXT
179
- opts.on('--precheck-timeout SECONDS', Float, help_message) do |timeout|
180
- options[:precheck_timeout] = timeout
181
- end
182
-
183
171
  help_message = <<-TEXT.gsub(/\s+/, ' ').strip
184
172
  Timeout for each batch_error script. Default is #{DEFAULTS[:batch_error_timeout]}
185
173
  TEXT
@@ -23,6 +23,10 @@ module ParallelCucumber
23
23
  Hooks.register_after_batch(proc)
24
24
  end
25
25
 
26
+ def worker_health_check(&proc)
27
+ Hooks.register_worker_health_check(proc)
28
+ end
29
+
26
30
  def before_workers(&proc)
27
31
  Hooks.register_before_workers(proc)
28
32
  end
@@ -34,6 +38,10 @@ module ParallelCucumber
34
38
  def on_batch_error(&proc)
35
39
  Hooks.register_on_batch_error(proc)
36
40
  end
41
+
42
+ def on_dry_run_error(&proc)
43
+ Hooks.register_on_dry_run_error(proc)
44
+ end
37
45
  end
38
46
  end
39
47
  end
@@ -15,8 +15,13 @@ module ParallelCucumber
15
15
  end
16
16
 
17
17
  ONE_SECOND = 1
18
+ STACKTRACE_COLLECTION_TIMEOUT = 10
18
19
 
19
- def exec_command(env, desc, script, logger, log_decoration = {}, timeout: 30, capture: false, return_script_error: false) # rubocop:disable Metrics/ParameterLists, Metrics/LineLength
20
+ # rubocop:disable Metrics/ParameterLists, Metrics/LineLength
21
+ def exec_command(env, desc, script, logger, log_decoration = {},
22
+ timeout: 30, capture: false, return_script_error: false,
23
+ return_on_timeout: false, collect_stacktrace: false
24
+ )
20
25
  block_name = ''
21
26
  if log_decoration['worker_block']
22
27
  if log_decoration['start'] || log_decoration['end']
@@ -28,42 +33,48 @@ module ParallelCucumber
28
33
  full_script = "#{script} 2>&1"
29
34
  env_string = env.map { |k, v| "#{k}=#{v}" }.sort.join(' ')
30
35
  logger << "== Running command `#{full_script}` at #{Time.now}\n== with environment variables: #{env_string}\n"
31
- pstat = nil
36
+ wait_thread = nil
32
37
  pout = nil
33
38
  capture &&= [''] # Pass by reference
34
39
  exception = nil
40
+ command_pid = nil
35
41
 
36
42
  begin
37
43
  completed = begin
38
- pin, pout, pstat = Open3.popen2e(env, full_script)
39
- logger << "Command has pid #{pstat[:pid]}\n"
44
+ pin, pout, wait_thread = Open3.popen2e(env, full_script)
45
+ command_pid = wait_thread[:pid].to_s
46
+ logger << "Command has pid #{command_pid}\n"
40
47
  pin.close
41
48
  out_reader = Thread.new do
42
- output_reader(pout, pstat, logger, capture)
49
+ output_reader(pout, wait_thread, logger, capture)
43
50
  end
44
51
 
45
52
  unless out_reader.join(timeout)
46
53
  raise TimedOutError
47
54
  end
48
55
 
49
- graceful_process_shutdown(out_reader, pstat, pout, logger)
56
+ graceful_process_shutdown(out_reader, wait_thread, pout, logger)
50
57
 
51
- pstat.value # reap already-terminated child.
52
- "Command completed #{pstat.value} at #{Time.now}"
58
+ wait_thread.value # reap already-terminated child.
59
+ "Command completed #{wait_thread.value} at #{Time.now}"
53
60
  end
54
61
 
55
62
  logger << "#{completed}\n"
56
63
 
57
- raise "Script returned #{pstat.value.exitstatus}" unless pstat.value.success? || return_script_error
64
+ raise "Script returned #{wait_thread.value.exitstatus}" unless wait_thread.value.success? || return_script_error
58
65
 
59
66
  capture_or_empty = capture ? capture.first : '' # Even '' is truthy
60
- return pstat.value.success? ? capture_or_empty : nil
67
+ return wait_thread.value.success? ? capture_or_empty : nil
61
68
  rescue TimedOutError => e
62
- force_kill_process_with_tree(out_reader, pstat, pout, full_script, logger, timeout)
69
+ process_tree = Helper::Processes.ps_tree
70
+ send_usr1_to_process_with_tree(command_pid, full_script, logger, process_tree) if collect_stacktrace
71
+ force_kill_process_with_tree(out_reader, wait_thread, pout, full_script, logger, timeout, process_tree, command_pid)
72
+
73
+ return capture.first if return_on_timeout
63
74
 
64
75
  exception = e
65
76
  rescue => e
66
- logger.debug("Exception #{pstat ? pstat[:pid] : "pstat=#{pstat}=nil"}")
77
+ logger.debug("Exception #{wait_thread ? wait_thread[:pid] : "wait_thread=#{wait_thread}=nil"}")
67
78
  trace = e.backtrace.join("\n\t").sub("\n\t", ": #{$ERROR_INFO}#{e.class ? " (#{e.class})" : ''}\n\t")
68
79
  logger.error("Threw for #{full_script}, caused #{trace}")
69
80
 
@@ -75,6 +86,7 @@ module ParallelCucumber
75
86
 
76
87
  raise exception
77
88
  end
89
+ # rubocop:enable Metrics/ParameterLists, Metrics/LineLength
78
90
 
79
91
  def log_until_incomplete_line(logger, out_string)
80
92
  loop do
@@ -87,13 +99,13 @@ module ParallelCucumber
87
99
 
88
100
  private
89
101
 
90
- def output_reader(pout, pstat, logger, capture)
102
+ def output_reader(pout, wait_thread, logger, capture)
91
103
  out_string = ''
92
104
 
93
105
  loop do
94
106
  io_select = IO.select([pout], [], [], ONE_SECOND)
95
- unless io_select || pstat.alive?
96
- logger << "\n== Terminating because io_select=#{io_select} when pstat.alive?=#{pstat.alive?}\n"
107
+ unless io_select || wait_thread.alive?
108
+ logger << "\n== Terminating because io_select=#{io_select} when wait_thread.alive?=#{wait_thread.alive?}\n"
97
109
  break
98
110
  end
99
111
  next unless io_select
@@ -103,44 +115,45 @@ module ParallelCucumber
103
115
  out_string = log_until_incomplete_line(logger, out_string + partial)
104
116
  end
105
117
  rescue EOFError
106
- logger << "\n== EOF is normal exit, #{pstat.inspect}\n"
118
+ logger << "\n== EOF is normal exit, #{wait_thread.inspect}\n"
107
119
  rescue => e
108
120
  logger << "\n== Exception in out_reader due to #{e.inspect} #{e.backtrace}\n"
109
121
  ensure
110
122
  logger << out_string
111
123
  logger << ["\n== Left out_reader at #{Time.now}; ",
112
- "pipe=#{pstat.status}+#{pstat.status ? '≤no value≥' : pstat.value}\n"].join
124
+ "pipe=#{wait_thread.status}+#{wait_thread.status ? '≤no value≥' : wait_thread.value}\n"].join
113
125
  end
114
126
 
115
- def graceful_process_shutdown(out_reader, pstat, pout, logger)
116
- out_reader.value # Should terminate with pstat
127
+ def graceful_process_shutdown(out_reader, wait_thread, pout, logger)
128
+ out_reader.value # Should terminate with wait_thread
117
129
  pout.close
118
- if pstat.status
119
- logger << "== Thread #{pstat.inspect} is not dead"
130
+ if wait_thread.status
131
+ logger << "== Thread #{wait_thread.inspect} is not dead"
120
132
 
121
- if pstat.join(3)
122
- logger << "== Thread #{pstat.inspect} joined late"
133
+ if wait_thread.join(3)
134
+ logger << "== Thread #{wait_thread.inspect} joined late"
123
135
  else
124
- pstat.terminate # Just in case
125
- logger << "== Thread #{pstat.inspect} terminated"
136
+ wait_thread.terminate # Just in case
137
+ logger << "== Thread #{wait_thread.inspect} terminated"
126
138
  end # Make an effort to reap
127
139
  end
128
140
 
129
- pstat.value # reap already-terminated child.
130
- "Command completed #{pstat.value} at #{Time.now}"
141
+ wait_thread.value # reap already-terminated child.
142
+ "Command completed #{wait_thread.value} at #{Time.now}"
131
143
  end
132
144
 
133
- def force_kill_process_with_tree(out_reader, pstat, pout, full_script, logger, timeout) # rubocop:disable Metrics/ParameterLists, Metrics/LineLength
145
+ def send_usr1_to_process_with_tree(command_pid, full_script, logger, tree)
146
+ return if Helper::Processes.ms_windows?
147
+
148
+ logger << "Timeout, so trying SIGUSR1 to trigger watchdog stacktrace #{command_pid}=#{full_script}"
149
+ Helper::Processes.kill_tree('SIGUSR1', command_pid, logger, tree)
150
+ sleep(STACKTRACE_COLLECTION_TIMEOUT) # Wait enough time for child processes to act on SIGUSR1
151
+ end
152
+
153
+ def force_kill_process_with_tree(out_reader, wait_thread, pout, full_script, logger, timeout, tree, pid) # rubocop:disable Metrics/ParameterLists, Metrics/LineLength
134
154
  out_reader.exit
135
- tree = Helper::Processes.ps_tree
136
- pid = pstat[:pid].to_s
137
- unless Helper::Processes.ms_windows?
138
- logger << "Timeout, so trying SIGUSR1 to trigger watchdog stacktrace #{pstat[:pid]}=#{full_script}"
139
- Helper::Processes.kill_tree('SIGUSR1', pid, logger, tree)
140
- sleep 2
141
- end
142
155
 
143
- logger << "Timeout, so trying SIGINT at #{pstat[:pid]}=#{full_script}"
156
+ logger << "Timeout, so trying SIGINT at #{wait_thread[:pid]}=#{full_script}"
144
157
 
145
158
  log_copy = Thread.new do
146
159
  pout.each_line { |l| logger << l }
@@ -167,7 +180,7 @@ module ParallelCucumber
167
180
  end
168
181
 
169
182
  logger << "About to reap root #{pid}"
170
- pstat.value # reap root - everything else should be reaped by init.
183
+ wait_thread.value # reap root - everything else should be reaped by init.
171
184
  logger << "Reaped root #{pid}"
172
185
  end
173
186
  end
@@ -26,25 +26,32 @@ module ParallelCucumber
26
26
 
27
27
  def parse_json_report(json_report)
28
28
  report = JSON.parse(json_report, symbolize_names: true)
29
- report.map do |scenario, cucumber_status|
30
- status = case cucumber_status
31
- when 'failed'
32
- Status::FAILED
33
- when 'passed'
34
- Status::PASSED
35
- when 'pending'
36
- Status::PENDING
37
- when 'skipped'
38
- Status::SKIPPED
39
- when 'undefined'
40
- Status::UNDEFINED
41
- when 'unknown'
42
- Status::UNKNOWN
43
- else
44
- Status::UNKNOWN
45
- end
46
- [scenario, status]
47
- end.to_h
29
+ report.each do |scenario, details|
30
+ report[scenario][:status] = case details[:status]
31
+ when 'failed'
32
+ Status::FAILED
33
+ when 'passed'
34
+ Status::PASSED
35
+ when 'pending'
36
+ Status::PENDING
37
+ when 'skipped'
38
+ Status::SKIPPED
39
+ when 'undefined'
40
+ Status::UNDEFINED
41
+ when 'unknown'
42
+ Status::UNKNOWN
43
+ else
44
+ Status::UNKNOWN
45
+ end
46
+ end
47
+ report
48
+ end
49
+
50
+ def unknown_result(tests)
51
+ res = tests.map do |test|
52
+ [test.to_sym, {status: ::ParallelCucumber::Status::UNKNOWN}]
53
+ end
54
+ res.to_h
48
55
  end
49
56
 
50
57
  private
@@ -57,7 +64,7 @@ module ParallelCucumber
57
64
  options = remove_strict_flag(options)
58
65
  content = nil
59
66
 
60
- Tempfile.open(%w(dry-run .json)) do |f|
67
+ Tempfile.open(%w[dry-run .json]) do |f|
61
68
  dry_run_options = "--dry-run --format ParallelCucumber::Helper::Cucumber::JsonStatusFormatter --out #{f.path}"
62
69
 
63
70
  cmd = "cucumber #{options} #{dry_run_options} #{args_string}"
@@ -15,7 +15,13 @@ module ParallelCucumber
15
15
  end
16
16
 
17
17
  def on_after_test_case(event)
18
- @result[event.test_case.location.to_s] = event.result.to_sym
18
+ details = {status: event.result.to_sym}
19
+ if event.result.respond_to?(:exception)
20
+ details[:exception_classname] = event.result.exception.class.to_s
21
+ details[:exception_message] = event.result.exception.message
22
+ end
23
+ details[:finish_time] = Time.now.to_i
24
+ @result[event.test_case.location.to_s] = details
19
25
  end
20
26
 
21
27
  def on_finished_testing(*)
@@ -35,7 +35,7 @@ module ParallelCucumber
35
35
  else
36
36
  descendants(root, logger, tree, old_tree, 'kill') do |pid, node|
37
37
  begin
38
- logger.warn "Killing #{node}"
38
+ logger.warn "Sending signal #{sig} to #{node}"
39
39
  Process.kill(sig, pid.to_i)
40
40
  rescue Errno::ESRCH
41
41
  nil # It's gone already? Hurrah!
@@ -44,7 +44,7 @@ module ParallelCucumber
44
44
  end
45
45
  # Let's kill pid unconditionally: descendants will go astray once reparented.
46
46
  begin
47
- logger.warn "Killing #{root} just in case"
47
+ logger.warn "Sending signal #{sig} to root process #{root} just in case"
48
48
  Process.kill(sig, root.to_i)
49
49
  rescue Errno::ESRCH
50
50
  nil # It's gone already? Hurrah!
@@ -1,12 +1,19 @@
1
1
  module ParallelCucumber
2
2
  class Hooks
3
- @before_batch_hooks ||= []
4
- @after_batch_hooks ||= []
5
- @before_workers ||= []
6
- @after_workers ||= []
7
- @on_batch_error ||= []
3
+ @worker_health_check ||= []
4
+ @before_batch_hooks ||= []
5
+ @after_batch_hooks ||= []
6
+ @before_workers ||= []
7
+ @after_workers ||= []
8
+ @on_batch_error ||= []
9
+ @on_dry_run_error ||= []
8
10
 
9
11
  class << self
12
+ def register_worker_health_check(proc)
13
+ raise(ArgumentError, 'Please provide a valid callback') unless proc.respond_to?(:call)
14
+ @worker_health_check << proc
15
+ end
16
+
10
17
  def register_before_batch(proc)
11
18
  raise(ArgumentError, 'Please provide a valid callback') unless proc.respond_to?(:call)
12
19
  @before_batch_hooks << proc
@@ -32,6 +39,17 @@ module ParallelCucumber
32
39
  @on_batch_error << proc
33
40
  end
34
41
 
42
+ def register_on_dry_run_error(proc)
43
+ raise(ArgumentError, 'Please provide a valid callback') unless proc.respond_to?(:call)
44
+ @on_dry_run_error << proc
45
+ end
46
+
47
+ def fire_worker_health_check(*args)
48
+ @worker_health_check.each do |hook|
49
+ hook.call(*args)
50
+ end
51
+ end
52
+
35
53
  def fire_before_batch_hooks(*args)
36
54
  @before_batch_hooks.each do |hook|
37
55
  hook.call(*args)
@@ -61,6 +79,12 @@ module ParallelCucumber
61
79
  hook.call(*args)
62
80
  end
63
81
  end
82
+
83
+ def fire_on_dry_run_error(error)
84
+ @on_dry_run_error.each do |hook|
85
+ hook.call(error)
86
+ end
87
+ end
64
88
  end
65
89
  end
66
90
  end
@@ -30,8 +30,12 @@ module ParallelCucumber
30
30
  exit(1)
31
31
  end
32
32
 
33
- all_tests = Helper::Cucumber.selected_tests(@options[:cucumber_options], @options[:cucumber_args])
34
-
33
+ begin
34
+ all_tests = Helper::Cucumber.selected_tests(@options[:cucumber_options], @options[:cucumber_args])
35
+ rescue StandardError => error
36
+ Hooks.fire_on_dry_run_error(error)
37
+ raise error
38
+ end
35
39
  if all_tests.empty?
36
40
  @logger.info('There is no tests to run, exiting...')
37
41
  exit(0)
@@ -98,7 +102,7 @@ module ParallelCucumber
98
102
 
99
103
  status_totals = Status.constants.map do |status|
100
104
  status = Status.const_get(status)
101
- tests_with_status = results.select { |_t, s| s == status }.keys
105
+ tests_with_status = results.select { |_t, s| s[:status] == status }.keys
102
106
  [status, tests_with_status]
103
107
  end.to_h
104
108
 
@@ -1,3 +1,3 @@
1
1
  module ParallelCucumber
2
- VERSION = '0.2.18'.freeze
2
+ VERSION = '0.2.23'.freeze
3
3
  end
@@ -10,11 +10,9 @@ module ParallelCucumber
10
10
  @group_by = options[:group_by]
11
11
  @batch_timeout = options[:batch_timeout]
12
12
  @batch_error_timeout = options[:batch_error_timeout]
13
- @precheck_timeout = options[:precheck_timeout]
14
13
  @setup_timeout = options[:setup_timeout]
15
14
  @cucumber_options = options[:cucumber_options]
16
15
  @test_command = options[:test_command]
17
- @pre_check = options[:pre_check]
18
16
  @index = index
19
17
  @name = "W#{@index}"
20
18
  @setup_worker = options[:setup_worker]
@@ -99,12 +97,7 @@ module ParallelCucumber
99
97
  job = @jobs_queue.pop(false)
100
98
  case job.type
101
99
  when Job::PRECHECK
102
- precmd = precheck(env)
103
- if (m = precmd.match(/precmd:retry-after-(\d+)-seconds/))
104
- @manager.inform_idle(@name)
105
- sleep(1 + m[1].to_i)
106
- next
107
- end
100
+ Hooks.fire_worker_health_check(env)
108
101
  @manager.inform_healthy(@name)
109
102
  when Job::RUN_TESTS
110
103
  run_batch(env, results, running_total, job.details)
@@ -161,22 +154,10 @@ module ParallelCucumber
161
154
  @logger.update_into(@stdout_logger)
162
155
  end
163
156
 
164
- def precheck(env)
165
- return 'default no-op pre_check' unless @pre_check
166
- begin
167
- return Helper::Command.exec_command(
168
- env, 'precheck', @pre_check, @logger, @log_decoration, timeout: @precheck_timeout, capture: true
169
- )
170
- rescue
171
- @logger.error('Pre-check failed: quitting immediately')
172
- raise 'Pre-check failed: quitting immediately'
173
- end
174
- end
175
-
176
157
  def running_totals(batch_results, running_total)
177
158
  batch_info = Status.constants.map do |status|
178
159
  status = Status.const_get(status)
179
- [status, batch_results.select { |_t, s| s == status }.keys]
160
+ [status, batch_results.select { |_t, s| s[:status] == status }.keys]
180
161
  end.to_h
181
162
  batch_info.each do |s, tt|
182
163
  @logger.info("#{s.to_s.upcase} #{tt.count} tests: #{tt.join(' ')}") unless tt.empty?
@@ -191,7 +172,7 @@ module ParallelCucumber
191
172
  test_syms = tests.map(&:to_sym)
192
173
  unrun = test_syms - batch_keys
193
174
  surfeit = batch_keys - test_syms
194
- unrun.each { |test| batch_results[test] = Status::UNKNOWN }
175
+ unrun.each { |test| batch_results[test][:status] = Status::UNKNOWN }
195
176
  surfeit.each { |test| batch_results.delete(test) }
196
177
  @logger.error("Did not run #{unrun.count}/#{tests.count}: #{unrun.join(' ')}") unless unrun.empty?
197
178
  @logger.error("Extraneous runs (#{surfeit.count}): #{surfeit.join(' ')}") unless surfeit.empty?
@@ -220,7 +201,8 @@ module ParallelCucumber
220
201
  begin
221
202
  ParallelCucumber::Helper::Command.exec_command(
222
203
  batch_env, 'batch', mapped_batch_cmd, @logger, @log_decoration,
223
- timeout: @batch_timeout, return_script_error: true
204
+ timeout: @batch_timeout, capture: true, return_script_error: true,
205
+ return_on_timeout: true, collect_stacktrace: true
224
206
  )
225
207
  rescue => e
226
208
  @logger << "ERROR #{e} #{e.backtrace.first(5)}"
@@ -232,7 +214,7 @@ module ParallelCucumber
232
214
  @logger.warn("There was exception in on_batch_error hook #{exc.message} \n #{trace}")
233
215
  end
234
216
 
235
- return tests.map { |t| [t, ::ParallelCucumber::Status::UNKNOWN] }.to_h
217
+ return Helper::Cucumber.unknown_result(tests)
236
218
  end
237
219
  parse_results(test_state, tests)
238
220
  ensure
@@ -291,18 +273,18 @@ module ParallelCucumber
291
273
  def parse_results(f, tests)
292
274
  unless File.file?(f)
293
275
  @logger.error("Results file does not exist: #{f}")
294
- return tests.map { |t| [t, ::ParallelCucumber::Status::UNKNOWN] }.to_h
276
+ return Helper::Cucumber.unknown_result(tests)
295
277
  end
296
278
  json_report = File.read(f)
297
279
  if json_report.empty?
298
280
  @logger.error("Results file is empty: #{f}")
299
- return tests.map { |t| [t, ::ParallelCucumber::Status::UNKNOWN] }.to_h
281
+ return Helper::Cucumber.unknown_result(tests)
300
282
  end
301
283
  Helper::Cucumber.parse_json_report(json_report)
302
284
  rescue => e
303
285
  trace = e.backtrace.join("\n\t").sub("\n\t", ": #{$ERROR_INFO}#{e.class ? " (#{e.class})" : ''}\n\t")
304
286
  @logger.error("Threw: JSON parse of results caused #{trace}")
305
- tests.map { |t| [t, ::ParallelCucumber::Status::UNKNOWN] }.to_h
287
+ Helper::Cucumber.unknown_result(tests)
306
288
  end
307
289
  end
308
290
  end
@@ -39,7 +39,7 @@ module ParallelCucumber
39
39
  def create_workers(number_of_workers)
40
40
  number_of_workers.times do |index|
41
41
  @workers["W#{index}"] =
42
- ParallelCucumber::Worker.new(options: @options, index: index, stdout_logger: @logger, manager: self)
42
+ ParallelCucumber::Worker.new(options: @options, index: index, stdout_logger: @logger, manager: self)
43
43
  end
44
44
  end
45
45
 
@@ -67,11 +67,15 @@ module ParallelCucumber
67
67
  puts "Starting W#{index}"
68
68
  @workers["W#{index}"].start(env_for_worker(@options[:env_variables], index))
69
69
  end
70
- @results.inject(:merge) # Returns hash of file:line to statuses + :worker-index to summary.
70
+ @results.inject do |seed, result|
71
+ seed.merge(result) do |_key, oldval, newval|
72
+ (newval[:finish_time] > oldval[:finish_time]) ? newval : oldval
73
+ end
74
+ end
71
75
  end
72
76
 
73
77
  def kill_all_workers
74
- @logger.info("=== Killing All Workers")
78
+ @logger.info('=== Killing All Workers')
75
79
  @workers.values.each { |w| w.assign_job(Job.new(Job::DIE)) }
76
80
  end
77
81
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallel_cucumber
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.18
4
+ version: 0.2.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Bayandin
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-29 00:00:00.000000000 Z
11
+ date: 2020-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cucumber
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - '='
74
74
  - !ruby/object:Gem::Version
75
- version: 0.59.2
75
+ version: 0.73.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - '='
81
81
  - !ruby/object:Gem::Version
82
- version: 0.59.2
82
+ version: 0.73.0
83
83
  description: Our own parallel cucumber with queue and workers
84
84
  email: a.bayandin@gmail.com
85
85
  executables:
@@ -112,7 +112,7 @@ homepage: https://github.com/badoo/parallel_cucumber
112
112
  licenses:
113
113
  - MIT
114
114
  metadata: {}
115
- post_install_message:
115
+ post_install_message:
116
116
  rdoc_options: []
117
117
  require_paths:
118
118
  - lib
@@ -127,8 +127,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
127
  - !ruby/object:Gem::Version
128
128
  version: '0'
129
129
  requirements: []
130
- rubygems_version: 3.0.4
131
- signing_key:
130
+ rubygems_version: 3.0.8
131
+ signing_key:
132
132
  specification_version: 4
133
133
  summary: Run cucumber in parallel
134
134
  test_files: []