rspecq 0.3.0 → 0.4.0

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: 89dbfa98d1eaceb06c39d41ab85e7fa6923d0c87e9a15b9cbfaf7399ff2aaff3
4
- data.tar.gz: b7cd028440e6eb03401dc623c7ee0fc0fe74f6ffa12a25ecc23d0cf54e6acd1e
3
+ metadata.gz: ae9ce385f55d300aa1c6d50b75ffe5bb91b28783e834009dc308d3e52856f786
4
+ data.tar.gz: 13cf6b430cb88a14040442a94f0df5d2831f263ae1205a6b0c6e228c5bc3aefe
5
5
  SHA512:
6
- metadata.gz: a43f0630e8a02a001132f45c9f68cacf7edae8e90487112e640eb611e7d1345f68ad0ab163ae01c91cb38ebc879e98cda9b54044c0ee676293c7aa3bf7c17942
7
- data.tar.gz: bf98027dc02ac56d02cc258700f5efa766c40d4d69c55c529d311083965d1fe4f76423b05fddb35a37496bb6eb3c11a8460a8e76f94897c4bb38a744b2fb40df
6
+ metadata.gz: a98eadc353fbc02963228503b92d88cdc04414c037d5728ebad471764b202967a5b535e34a9533abb8879ccfe20b88ff2561f8e122433b0f6deb1333ee9c22cf
7
+ data.tar.gz: 58af317b915a83bb8f0458dd25ab7a2b1aa31ee3835e3ad05082a6478f68d74681c0d040d9455b11135e8952bee2aadde70a344d1f25301af0a604a4e996b746
@@ -4,6 +4,14 @@ Breaking changes are prefixed with a "[BREAKING]" label.
4
4
 
5
5
  ## master (unreleased)
6
6
 
7
+ ## 0.4.0 (2020-10-07)
8
+
9
+ ### Added
10
+
11
+ - Builds can be configured to terminate after a specified number of failures,
12
+ using the `--fail-fast` option.
13
+
14
+
7
15
  ## 0.3.0 (2020-10-05)
8
16
 
9
17
  ### Added
data/README.md CHANGED
@@ -28,6 +28,7 @@ and [ci-queue](https://github.com/Shopify/ci-queue).
28
28
  - Sentry integration for monitoring build-level events. See [*Sentry integration*](#sentry-integration).
29
29
  - [PLANNED] StatsD integration for various build-level metrics and insights.
30
30
  See [#2](https://github.com/skroutz/rspecq/issues/2).
31
+ - Automatic termination of builds after a certain amount of failures. See [*Fail-fast*](#fail-fast).
31
32
 
32
33
  ## Usage
33
34
 
@@ -73,6 +74,7 @@ OPTIONS:
73
74
  Exits with a non-zero status code if there were any failures.
74
75
  --report-timeout N Fail if build is not finished after N seconds. Only applicable if --report is enabled (default: 3600).
75
76
  --max-requeues N Retry failed examples up to N times before considering them legit failures (default: 3).
77
+ --fail-fast N Abort build with a non-zero status code after N failed examples.
76
78
  -h, --help Show this message.
77
79
  -v, --version Print the version and exit.
78
80
  ```
@@ -133,6 +135,16 @@ final report.
133
135
  Flaky tests are also detected and printed as such in the final report. They are
134
136
  also emitted to Sentry (see [Sentry integration](#sentry-integration)).
135
137
 
138
+ ### Fail-fast
139
+
140
+ In order to prevent large suites running for a long time with a lot of
141
+ failures, a threshold can be set to control the number of failed examples that
142
+ will render the build unsuccessful. This is in par with RSpec's
143
+ [--fail-fast](https://relishapp.com/rspec/rspec-core/docs/command-line/fail-fast-option).
144
+
145
+ This feature is disabled by default, and can be controlled via the
146
+ `--fail-fast` command line option.
147
+
136
148
  ### Worker failures
137
149
 
138
150
  It's not uncommon for CI processes to encounter unrecoverable failures for
data/bin/rspecq CHANGED
@@ -5,6 +5,7 @@ require "rspecq"
5
5
  DEFAULT_REDIS_HOST = "127.0.0.1"
6
6
  DEFAULT_REPORT_TIMEOUT = 3600 # 1 hour
7
7
  DEFAULT_MAX_REQUEUES = 3
8
+ DEFAULT_FAIL_FAST = 0
8
9
 
9
10
  def env_set?(var)
10
11
  ["1", "true"].include?(ENV[var])
@@ -83,6 +84,11 @@ OptionParser.new do |o|
83
84
  opts[:max_requeues] = v
84
85
  end
85
86
 
87
+ o.on("--fail-fast N", Integer, "Abort build with a non-zero status code " \
88
+ "after N failed examples." ) do |v|
89
+ opts[:fail_fast] = v
90
+ end
91
+
86
92
  o.on_tail("-h", "--help", "Show this message.") do
87
93
  puts o
88
94
  exit
@@ -103,6 +109,7 @@ opts[:report] ||= env_set?("RSPECQ_REPORT")
103
109
  opts[:report_timeout] ||= Integer(ENV["RSPECQ_REPORT_TIMEOUT"] || DEFAULT_REPORT_TIMEOUT)
104
110
  opts[:max_requeues] ||= Integer(ENV["RSPECQ_MAX_REQUEUES"] || DEFAULT_MAX_REQUEUES)
105
111
  opts[:redis_url] ||= ENV["RSPECQ_REDIS_URL"]
112
+ opts[:fail_fast] ||= Integer(ENV["RSPECQ_FAIL_FAST"] || DEFAULT_FAIL_FAST)
106
113
 
107
114
  raise OptionParser::MissingArgument.new(:build) if opts[:build].nil?
108
115
  raise OptionParser::MissingArgument.new(:worker) if !opts[:report] && opts[:worker].nil?
@@ -134,5 +141,6 @@ else
134
141
  worker.populate_timings = opts[:timings]
135
142
  worker.file_split_threshold = opts[:file_split_threshold]
136
143
  worker.max_requeues = opts[:max_requeues]
144
+ worker.fail_fast = opts[:fail_fast]
137
145
  worker.work
138
146
  end
@@ -77,8 +77,9 @@ module RSpecQ
77
77
  end
78
78
 
79
79
  # NOTE: jobs will be processed from head to tail (lpop)
80
- def publish(jobs)
80
+ def publish(jobs, fail_fast = 0)
81
81
  @redis.multi do
82
+ @redis.hset(key_queue_config, 'fail_fast', fail_fast)
82
83
  @redis.rpush(key_queue_unprocessed, jobs)
83
84
  @redis.set(key_queue_status, STATUS_READY)
84
85
  end.first
@@ -232,7 +233,9 @@ module RSpecQ
232
233
  # after being retried). Must be called after the build is complete,
233
234
  # otherwise an exception will be raised.
234
235
  def flaky_jobs
235
- raise "Queue is not yet exhausted" if !exhausted?
236
+ if !exhausted? && !build_failed_fast?
237
+ raise "Queue is not yet exhausted"
238
+ end
236
239
 
237
240
  requeued = @redis.hkeys(key_requeues)
238
241
 
@@ -241,11 +244,38 @@ module RSpecQ
241
244
  requeued - @redis.hkeys(key_failures)
242
245
  end
243
246
 
247
+ # Returns the number of failures that will trigger the build to fail-fast.
248
+ # Returns 0 if this feature is disabled and nil if the Queue is not yet
249
+ # published
250
+ def fail_fast
251
+ return nil unless published?
252
+
253
+ @fail_fast ||= Integer(@redis.hget(key_queue_config, 'fail_fast'))
254
+ end
255
+
256
+ # Returns true if the number of failed tests, has surpassed the threshold
257
+ # to render the run unsuccessful and the build should be terminated.
258
+ def build_failed_fast?
259
+ if fail_fast.nil? || fail_fast.zero?
260
+ return false
261
+ end
262
+
263
+ @redis.multi do
264
+ @redis.hlen(key_failures)
265
+ @redis.hlen(key_errors)
266
+ end.inject(:+) >= fail_fast
267
+ end
268
+
244
269
  # redis: STRING [STATUS_INITIALIZING, STATUS_READY]
245
270
  def key_queue_status
246
271
  key("queue", "status")
247
272
  end
248
273
 
274
+ # redis: HASH<config_key => config_value>
275
+ def key_queue_config
276
+ key("queue", "config")
277
+ end
278
+
249
279
  # redis: LIST<job>
250
280
  def key_queue_unprocessed
251
281
  key("queue", "unprocessed")
@@ -41,7 +41,7 @@ module RSpecQ
41
41
  puts failure_formatted(rspec_output)
42
42
  end
43
43
 
44
- if !@queue.exhausted?
44
+ unless @queue.exhausted? || @queue.build_failed_fast?
45
45
  sleep 1
46
46
  next
47
47
  end
@@ -83,6 +83,13 @@ module RSpecQ
83
83
  end
84
84
 
85
85
  summary = ""
86
+ if @queue.build_failed_fast?
87
+ summary << "\n\n"
88
+ summary << "The limit of #{@queue.fail_fast} failures has been reached\n"
89
+ summary << "Aborting..."
90
+ summary << "\n"
91
+ end
92
+
86
93
  summary << failed_examples_section if !failures.empty?
87
94
 
88
95
  errors.each { |_job, msg| summary << msg }
@@ -1,3 +1,3 @@
1
1
  module RSpecQ
2
- VERSION = "0.3.0".freeze
2
+ VERSION = "0.4.0".freeze
3
3
  end
@@ -40,12 +40,19 @@ module RSpecQ
40
40
  # Defaults to 3
41
41
  attr_accessor :max_requeues
42
42
 
43
+ # Stop the execution after N failed tests. Do not stop at any point when
44
+ # set to 0.
45
+ #
46
+ # Defaults to 0
47
+ attr_accessor :fail_fast
48
+
43
49
  attr_reader :queue
44
50
 
45
51
  def initialize(build_id:, worker_id:, redis_opts:)
46
52
  @build_id = build_id
47
53
  @worker_id = worker_id
48
54
  @queue = Queue.new(build_id, worker_id, redis_opts)
55
+ @fail_fast = 0
49
56
  @files_or_dirs_to_run = "spec"
50
57
  @populate_timings = false
51
58
  @file_split_threshold = 999999
@@ -69,6 +76,8 @@ module RSpecQ
69
76
  # to `requeue_lost_job` inside the work loop
70
77
  update_heartbeat
71
78
 
79
+ return if queue.build_failed_fast?
80
+
72
81
  lost = queue.requeue_lost_job
73
82
  puts "Requeued lost job: #{lost}" if lost
74
83
 
@@ -121,7 +130,7 @@ module RSpecQ
121
130
 
122
131
  timings = queue.timings
123
132
  if timings.empty?
124
- q_size = queue.publish(files_to_run.shuffle)
133
+ q_size = queue.publish(files_to_run.shuffle, fail_fast)
125
134
  log_event(
126
135
  "No timings found! Published queue in random order (size=#{q_size})",
127
136
  "warning"
@@ -160,7 +169,7 @@ module RSpecQ
160
169
  # sort jobs based on their timings (slowest to be processed first)
161
170
  jobs = jobs.sort_by { |_j, t| -t }.map(&:first)
162
171
 
163
- puts "Published queue (size=#{queue.publish(jobs)})"
172
+ puts "Published queue (size=#{queue.publish(jobs, fail_fast)})"
164
173
  end
165
174
 
166
175
  private
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspecq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Agis Anastasopoulos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-05 00:00:00.000000000 Z
11
+ date: 2020-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec-core