specwrk 0.13.1 → 0.14.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3028afb58991e7fc5837333b22d842ba261c6fa5985809db231b21fb40fa904
4
- data.tar.gz: b3471eafa04b678db1b14cf0684296677dbe995fb2f3a5f920659479110e291f
3
+ metadata.gz: 7890636149b3658e663cfc5957522e9147f0ede2a6ff4e28c27b6966c1b45b40
4
+ data.tar.gz: a76c0bd696a11d4b1703dabd156466611678bcec30b35fd6994ced48444e9dcf
5
5
  SHA512:
6
- metadata.gz: 5ed970474e4a193778c33c113f374ffe8146dca152fb14e991f4a2b1970d1b71fee7050b78029ecd7fd3594b16dbd594d94cacd00e29e98ba7eeefc77c8bed90
7
- data.tar.gz: 6a22138e628cd93aa6db4ceaf06d04b702d540765c53c41a0cefe04192af218394a0c5df6aec0200c5e3fcaadae8d521d3b9839290422b562819b6c78701b005
6
+ metadata.gz: 9ad2c96e0111c5b523836f22a3eda9133f7ef838b5239c8355118a299c9ac6ea523bdb524955bf7b66caffea1397d5470cfd3554e877247268fce332ba34ac43
7
+ data.tar.gz: dec3475151008f2e4452ed4f5e96c413d63d61c4b2ebe38d315a3d795bed58247341c53c60b8dd7f15385860a1d381d77f60cdba61ce5738c24fb0f27a50b72f
data/.circleci/config.yml CHANGED
@@ -73,7 +73,8 @@ jobs:
73
73
  ## SPECWRK STEP ##
74
74
  - run:
75
75
  name: Run tests via specwrk start
76
- command: bundle exec specwrk start --count 2 spec/
76
+ # This run will output a single failure when healthy, but because of retries the exit code will be zero
77
+ command: bundle exec specwrk start --count 2 --max-retries 1 spec/
77
78
  ## /SPECWRK STEP ##
78
79
 
79
80
  ## SPECWRK STEP ##
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Specwrk
2
- Run your [RSpec](https://github.com/rspec/rspec) examples across many processors and many nodes for a single build. Or just many processes on a single node. Speeds up your *slow* (minutes/hours not seconds) test suite by running multiple examples in parallel.
2
+ Run your [RSpec](https://github.com/rspec/rspec) examples across many processors and many nodes for a single build. Or just many processes on a single node. Speeds up your *slow* (minutes/hours not seconds) test suite by running multiple examples in parallel. Optionally, retry failed examples up-to N-times to avoid breaking deployments.
3
3
 
4
4
  One CLI command to:
5
5
 
@@ -60,6 +60,7 @@ Options:
60
60
  --store-uri=VALUE # Directory where server state is stored. Required for multi-node or multi-process servers.
61
61
  --group-by=VALUE # How examples will be grouped for workers; fallback to file if no timings are found. Overrides SPECWERK_SRV_GROUP_BY: (file/timings), default: "timings"
62
62
  --[no-]verbose # Run in verbose mode, default: false
63
+ --max-retries=VALUE # Number of times an example will be re-run should it fail, default: 0
63
64
  --help, -h # Print this help
64
65
  ```
65
66
 
@@ -111,6 +112,7 @@ Options:
111
112
  --key=VALUE, -k VALUE # Authentication key clients must use for access. Overrides SPECWRK_SRV_KEY, default: ""
112
113
  --run=VALUE, -r VALUE # The run identifier for this job execution. Overrides SPECWRK_RUN, default: "main"
113
114
  --timeout=VALUE, -t VALUE # The amount of time to wait for the server to respond. Overrides SPECWRK_TIMEOUT, default: "5"
115
+ --max-retries=VALUE # Number of times an example will be re-run should it fail, default: 0
114
116
  --help, -h # Print this help
115
117
  ```
116
118
 
data/lib/specwrk/cli.rb CHANGED
@@ -49,16 +49,31 @@ module Specwrk
49
49
  end
50
50
 
51
51
  def start_workers
52
+ @final_outputs = []
52
53
  @worker_pids = worker_count.times.map do |i|
54
+ reader, writer = IO.pipe
55
+ @final_outputs << reader
56
+
53
57
  Process.fork do
54
58
  ENV["TEST_ENV_NUMBER"] = ENV["SPECWRK_FORKED"] = (i + 1).to_s
55
59
  ENV["SPECWRK_ID"] = ENV["SPECWRK_ID"] + "-#{i + 1}"
56
60
 
61
+ $final_output = writer # standard:disable Style/GlobalVars
62
+ $final_output.sync = true # standard:disable Style/GlobalVars
63
+ reader.close
64
+
57
65
  require "specwrk/worker"
58
66
 
59
67
  status = Specwrk::Worker.run!
68
+ $final_output.close # standard:disable Style/GlobalVars
60
69
  exit(status)
61
- end
70
+ end.tap { writer.close }
71
+ end
72
+ end
73
+
74
+ def drain_outputs
75
+ @final_outputs.each do |reader|
76
+ reader.each_line { |line| $stdout.print line }
62
77
  end
63
78
  end
64
79
 
@@ -104,10 +119,10 @@ module Specwrk
104
119
  include Clientable
105
120
 
106
121
  desc "Seed the server with a list of specs for the run"
107
-
122
+ option :max_retries, default: 0, desc: "Number of times an example will be re-run should it fail"
108
123
  argument :dir, required: false, default: "spec", desc: "Relative spec directory to run against"
109
124
 
110
- def call(dir:, **args)
125
+ def call(max_retries:, dir:, **args)
111
126
  self.class.setup(**args)
112
127
 
113
128
  require "specwrk/list_examples"
@@ -117,7 +132,7 @@ module Specwrk
117
132
  examples = ListExamples.new(dir).examples
118
133
 
119
134
  Client.wait_for_server!
120
- Client.new.seed(examples)
135
+ Client.new.seed(examples, max_retries: max_retries)
121
136
  file_count = examples.group_by { |e| e[:file_path] }.keys.size
122
137
  puts "🌱 Seeded #{examples.size} examples across #{file_count} files"
123
138
  rescue Errno::ECONNREFUSED
@@ -140,6 +155,7 @@ module Specwrk
140
155
 
141
156
  start_workers
142
157
  wait_for_workers_exit
158
+ drain_outputs
143
159
 
144
160
  require "specwrk/cli_reporter"
145
161
  Specwrk::CLIReporter.new.report
@@ -180,9 +196,10 @@ module Specwrk
180
196
  include Servable
181
197
 
182
198
  desc "Start a server and workers, monitor until complete"
199
+ option :max_retries, default: 0, desc: "Number of times an example will be re-run should it fail"
183
200
  argument :dir, required: false, default: "spec", desc: "Relative spec directory to run against"
184
201
 
185
- def call(dir:, **args)
202
+ def call(max_retries:, dir:, **args)
186
203
  self.class.setup(**args)
187
204
  $stdout.sync = true
188
205
 
@@ -204,6 +221,7 @@ module Specwrk
204
221
  require "specwrk/list_examples"
205
222
  require "specwrk/client"
206
223
 
224
+ ENV["SPECWRK_FORKED"] = "1"
207
225
  ENV["SPECWRK_SEED"] = "1"
208
226
  examples = ListExamples.new(dir).examples
209
227
 
@@ -211,7 +229,7 @@ module Specwrk
211
229
  Client.wait_for_server!
212
230
  status "Server responding ✓"
213
231
  status "Seeding #{examples.length} examples..."
214
- Client.new.seed(examples)
232
+ Client.new.seed(examples, max_retries)
215
233
  file_count = examples.group_by { |e| e[:file_path] }.keys.size
216
234
  status "🌱 Seeded #{examples.size} examples across #{file_count} files"
217
235
  end
@@ -228,6 +246,7 @@ module Specwrk
228
246
  status "#{worker_count} workers started ✓\n"
229
247
  Specwrk.wait_for_pids_exit(@worker_pids)
230
248
 
249
+ drain_outputs
231
250
  return if Specwrk.force_quit
232
251
 
233
252
  require "specwrk/cli_reporter"
@@ -127,8 +127,8 @@ module Specwrk
127
127
  retry
128
128
  end
129
129
 
130
- def seed(examples)
131
- response = post "/seed", body: examples.to_json
130
+ def seed(examples, max_retries)
131
+ response = post "/seed", body: {max_retries: max_retries, examples: examples}.to_json
132
132
 
133
133
  (response.code == "200") ? true : raise(UnhandledResponseError.new("#{response.code}: #{response.body}"))
134
134
  end
data/lib/specwrk/store.rb CHANGED
@@ -99,6 +99,7 @@ module Specwrk
99
99
  class PendingStore < Store
100
100
  RUN_TIME_BUCKET_MAXIMUM_KEY = :____run_time_bucket_maximum
101
101
  ORDER_KEY = :____order
102
+ MAX_RETRIES_KEY = :____max_retries
102
103
 
103
104
  def run_time_bucket_maximum=(val)
104
105
  @run_time_bucket_maximum = self[RUN_TIME_BUCKET_MAXIMUM_KEY] = val
@@ -122,6 +123,14 @@ module Specwrk
122
123
  @order ||= self[ORDER_KEY] || []
123
124
  end
124
125
 
126
+ def max_retries=(val)
127
+ @max_retries = self[MAX_RETRIES_KEY] = val
128
+ end
129
+
130
+ def max_retries
131
+ @max_retries ||= self[MAX_RETRIES_KEY] || 0
132
+ end
133
+
125
134
  def keys
126
135
  return super if order.length.zero?
127
136
 
@@ -130,7 +139,8 @@ module Specwrk
130
139
 
131
140
  def merge!(hash)
132
141
  super
133
- self.order = hash.keys
142
+
143
+ self.order = order + (hash.keys - order)
134
144
  end
135
145
 
136
146
  def clear
@@ -140,6 +150,7 @@ module Specwrk
140
150
 
141
151
  def reload
142
152
  @order = nil
153
+ @max_retries = nil
143
154
  super
144
155
  end
145
156
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Specwrk
4
- VERSION = "0.13.1"
4
+ VERSION = "0.14.0"
5
5
  end
@@ -92,6 +92,10 @@ module Specwrk
92
92
  @completed ||= CompletedStore.new(ENV.fetch("SPECWRK_SRV_STORE_URI", "memory:///"), File.join(run_id, "completed"))
93
93
  end
94
94
 
95
+ def failure_counts
96
+ @failure_counts ||= Store.new(ENV.fetch("SPECWRK_SRV_STORE_URI", "memory:///"), File.join(run_id, "failure_counts"))
97
+ end
98
+
95
99
  def metadata
96
100
  @metadata ||= Store.new(ENV.fetch("SPECWRK_SRV_STORE_URI", "memory:///"), File.join(run_id, "metadata"))
97
101
  end
@@ -141,6 +145,10 @@ module Specwrk
141
145
 
142
146
  def with_response
143
147
  pending.clear
148
+ failure_counts.clear
149
+
150
+ pending.max_retries = payload.fetch(:max_retries, "0").to_i
151
+
144
152
  new_run_time_bucket_maximums = [pending.run_time_bucket_maximum, @seeds_run_time_bucket_maximum.to_f].compact
145
153
  pending.run_time_bucket_maximum = new_run_time_bucket_maximums.sum.to_f / new_run_time_bucket_maximums.length.to_f
146
154
 
@@ -154,10 +162,10 @@ module Specwrk
154
162
  def examples_with_run_times
155
163
  @examples_with_run_times ||= begin
156
164
  unsorted_examples_with_run_times = []
157
- all_ids = payload.map { |example| example[:id] }
165
+ all_ids = payload[:examples].map { |example| example[:id] }
158
166
  all_run_times = run_times.multi_read(*all_ids)
159
167
 
160
- payload.each do |example|
168
+ payload[:examples].each do |example|
161
169
  run_time = all_run_times[example[:id]]
162
170
 
163
171
  unsorted_examples_with_run_times << [example[:id], example.merge(expected_run_time: run_time)]
@@ -250,15 +258,52 @@ module Specwrk
250
258
 
251
259
  def with_response
252
260
  completed.merge!(completed_examples)
253
- processing.delete(*completed_examples.keys)
261
+ processing.delete(*(completed_examples.keys + retry_examples.keys))
262
+ pending.merge!(retry_examples)
263
+ failure_counts.merge!(retry_examples_new_failure_counts)
254
264
 
255
265
  with_pop_response
256
266
  end
257
267
 
258
268
  private
259
269
 
270
+ def all_examples
271
+ @all_examples ||= payload.map { |example| [example[:id], example] if processing[example[:id]] }.compact.to_h
272
+ end
273
+
260
274
  def completed_examples
261
- @completed_data ||= payload.map { |example| [example[:id], example] if processing[example[:id]] }.compact.to_h
275
+ @completed_examples ||= all_examples.map do |id, example|
276
+ next if retry_example?(example)
277
+
278
+ [id, example]
279
+ end.compact.to_h
280
+ end
281
+
282
+ def retry_examples
283
+ @retry_examples ||= all_examples.map do |id, example|
284
+ next unless retry_example?(example)
285
+
286
+ [id, example]
287
+ end.compact.to_h
288
+ end
289
+
290
+ def retry_examples_new_failure_counts
291
+ @retry_examples_new_failure_counts ||= retry_examples.map do |id, _example|
292
+ [id, all_example_failure_counts.fetch(id, 0) + 1]
293
+ end.to_h
294
+ end
295
+
296
+ def retry_example?(example)
297
+ return false unless example[:status] == "failed"
298
+ return false unless pending.max_retries.positive?
299
+
300
+ example_failure_count = all_example_failure_counts.fetch(example[:id], 0)
301
+
302
+ example_failure_count < pending.max_retries
303
+ end
304
+
305
+ def all_example_failure_counts
306
+ @all_example_failure_counts ||= failure_counts.multi_read(*all_examples.keys)
262
307
  end
263
308
 
264
309
  def completed_examples_status_counts
@@ -54,7 +54,7 @@ module Specwrk
54
54
  end
55
55
  end
56
56
 
57
- executor.final_output.tap(&:rewind).each_line { |line| $stdout.write line }
57
+ executor.final_output.tap(&:rewind).each_line { |line| final_output.write line }
58
58
 
59
59
  @heartbeat_thread.kill
60
60
  client.close
@@ -109,6 +109,10 @@ module Specwrk
109
109
 
110
110
  attr_reader :running, :client, :executor
111
111
 
112
+ def final_output
113
+ $final_output || $stdout # standard:disable Style/GlobalVars
114
+ end
115
+
112
116
  def status
113
117
  return 0 if @all_examples_completed && client.worker_status.zero?
114
118
  return 1 if Specwrk.force_quit
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: specwrk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.1
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Westendorf