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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +12 -0
- data/bin/rspecq +8 -0
- data/lib/rspecq/queue.rb +32 -2
- data/lib/rspecq/reporter.rb +8 -1
- data/lib/rspecq/version.rb +1 -1
- data/lib/rspecq/worker.rb +11 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae9ce385f55d300aa1c6d50b75ffe5bb91b28783e834009dc308d3e52856f786
|
4
|
+
data.tar.gz: 13cf6b430cb88a14040442a94f0df5d2831f263ae1205a6b0c6e228c5bc3aefe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a98eadc353fbc02963228503b92d88cdc04414c037d5728ebad471764b202967a5b535e34a9533abb8879ccfe20b88ff2561f8e122433b0f6deb1333ee9c22cf
|
7
|
+
data.tar.gz: 58af317b915a83bb8f0458dd25ab7a2b1aa31ee3835e3ad05082a6478f68d74681c0d040d9455b11135e8952bee2aadde70a344d1f25301af0a604a4e996b746
|
data/CHANGELOG.md
CHANGED
@@ -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
|
data/lib/rspecq/queue.rb
CHANGED
@@ -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
|
-
|
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")
|
data/lib/rspecq/reporter.rb
CHANGED
@@ -41,7 +41,7 @@ module RSpecQ
|
|
41
41
|
puts failure_formatted(rspec_output)
|
42
42
|
end
|
43
43
|
|
44
|
-
|
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 }
|
data/lib/rspecq/version.rb
CHANGED
data/lib/rspecq/worker.rb
CHANGED
@@ -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.
|
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-
|
11
|
+
date: 2020-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec-core
|