parallel_cucumber 0.2.18 → 0.2.23

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 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: []