parallel_cucumber 0.2.21 → 0.2.25

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: b2a5fcb6c187e21abd99851009a31c5015221e31118e5e16ccbb022467f64614
4
- data.tar.gz: f45853fb7908faa80978b52c7e8208b2cec406a4dad87c80949623e615870dcc
3
+ metadata.gz: c26b5025007a16790939a4b3d276cfd552f36f611b80554d7a597d477775b168
4
+ data.tar.gz: 9e3016e687ece3ff4e3ba714a598f489e3f8b8381f01a40004e59cecf7160985
5
5
  SHA512:
6
- metadata.gz: 21f7385b3c24476db0de9b9890d0e47166cd2ab3d4d5a5c52e8a73e39bc97e58502c50c0faaf4c82f0e250bda4ebe86a401a6b40c5f84b71959f058a9c6bfecb
7
- data.tar.gz: eaeb5edcda79f778eb6b3bf1c0af1bfdabe69137422724a79523e9f0288c5e3e8177cfb30b920c227173ffcdf4644272772fb112ac46a214a3d3423a2709d0e9
6
+ metadata.gz: 77722cf57a707fe7c650f7656b680418ce4e8b8a5411142afb1be201d09d426e07e9ce81eb3331e472c492c8993d02e349dc62385d495498b70d568c3e668f42
7
+ data.tar.gz: 9a8c49cbc3f35e05642aff922f4c6490035e334ff4e521d651a2938b5715ccd787db8a26c560ca24e564cc10f083fdad2beec3d6a66ddf177a7977b4e24e59b1
@@ -38,6 +38,10 @@ module ParallelCucumber
38
38
  def on_batch_error(&proc)
39
39
  Hooks.register_on_batch_error(proc)
40
40
  end
41
+
42
+ def on_dry_run_error(&proc)
43
+ Hooks.register_on_dry_run_error(proc)
44
+ end
41
45
  end
42
46
  end
43
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
@@ -20,6 +20,7 @@ module ParallelCucumber
20
20
  details[:exception_classname] = event.result.exception.class.to_s
21
21
  details[:exception_message] = event.result.exception.message
22
22
  end
23
+ details[:name] = "#{event.test_case.feature}: #{event.test_case.name}"
23
24
  details[:finish_time] = Time.now.to_i
24
25
  @result[event.test_case.location.to_s] = details
25
26
  end
@@ -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!
@@ -6,6 +6,7 @@ module ParallelCucumber
6
6
  @before_workers ||= []
7
7
  @after_workers ||= []
8
8
  @on_batch_error ||= []
9
+ @on_dry_run_error ||= []
9
10
 
10
11
  class << self
11
12
  def register_worker_health_check(proc)
@@ -38,6 +39,11 @@ module ParallelCucumber
38
39
  @on_batch_error << proc
39
40
  end
40
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
+
41
47
  def fire_worker_health_check(*args)
42
48
  @worker_health_check.each do |hook|
43
49
  hook.call(*args)
@@ -73,6 +79,12 @@ module ParallelCucumber
73
79
  hook.call(*args)
74
80
  end
75
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
76
88
  end
77
89
  end
78
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)
@@ -1,3 +1,3 @@
1
1
  module ParallelCucumber
2
- VERSION = '0.2.21'.freeze
2
+ VERSION = '0.2.25'.freeze
3
3
  end
@@ -201,7 +201,8 @@ module ParallelCucumber
201
201
  begin
202
202
  ParallelCucumber::Helper::Command.exec_command(
203
203
  batch_env, 'batch', mapped_batch_cmd, @logger, @log_decoration,
204
- 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
205
206
  )
206
207
  rescue => e
207
208
  @logger << "ERROR #{e} #{e.backtrace.first(5)}"
@@ -52,11 +52,15 @@ module ParallelCucumber
52
52
  elsif any_worker_busy?
53
53
  kill_surplus_workers
54
54
  else
55
- kill_all_workers
56
55
  break
57
56
  end
58
57
  sleep 0.5
59
58
  end
59
+ rescue StandardError => e
60
+ puts "There was a FATAL ERROR with worker manager. #{e}"
61
+ raise e
62
+ ensure
63
+ kill_all_workers
60
64
  end
61
65
  end
62
66
 
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.21
4
+ version: 0.2.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Bayandin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-17 00:00:00.000000000 Z
11
+ date: 2021-08-17 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:
@@ -127,7 +127,7 @@ 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.8
130
+ rubygems_version: 3.0.3
131
131
  signing_key:
132
132
  specification_version: 4
133
133
  summary: Run cucumber in parallel