rspecq 0.4.0 → 0.5.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: ae9ce385f55d300aa1c6d50b75ffe5bb91b28783e834009dc308d3e52856f786
4
- data.tar.gz: 13cf6b430cb88a14040442a94f0df5d2831f263ae1205a6b0c6e228c5bc3aefe
3
+ metadata.gz: 2abc0b960b2d28528c0d8a4f9fff7dc0708fd582c7377af021b6c01777350124
4
+ data.tar.gz: 23d432f09a68ace0932d3c82a112fd0e7058357fa4029c94401a75b84240d5b7
5
5
  SHA512:
6
- metadata.gz: a98eadc353fbc02963228503b92d88cdc04414c037d5728ebad471764b202967a5b535e34a9533abb8879ccfe20b88ff2561f8e122433b0f6deb1333ee9c22cf
7
- data.tar.gz: 58af317b915a83bb8f0458dd25ab7a2b1aa31ee3835e3ad05082a6478f68d74681c0d040d9455b11135e8952bee2aadde70a344d1f25301af0a604a4e996b746
6
+ metadata.gz: 9c499c1ded556a66e9547e71ebfc3f4cdefd6ec984269986b7ae2e1078a86e157ed9805d57afd615f7e9044869124b8f2359095c65febf2d39b3065191e450b7
7
+ data.tar.gz: 050a8b4eb4983234cbd2d42a24590c9d0914833231c16692f273020c297757c8afca453f09f9ff72a6ec10564809c81356f3ab23c9220644fbcba89f7f0a90a3
data/CHANGELOG.md CHANGED
@@ -1,8 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## master (unreleased)
4
+
3
5
  Breaking changes are prefixed with a "[BREAKING]" label.
4
6
 
5
- ## master (unreleased)
7
+ ## 0.5.0 (2021-02-05)
8
+
9
+ ### Added
10
+
11
+ - New cli parameter `queue_wait_timeout`.
12
+ It configured the time a queue can wait to be ready. The env equivalent
13
+ is `RSPECQ_QUEUE_WAIT_TIMEOUT`. [#51](https://github.com/skroutz/rspecq/pull/51)
6
14
 
7
15
  ## 0.4.0 (2020-10-07)
8
16
 
@@ -27,7 +35,7 @@ Breaking changes are prefixed with a "[BREAKING]" label.
27
35
  ## 0.2.2 (2020-09-10)
28
36
 
29
37
  ### Fixed
30
- - Worker would fail if application code was writing to stderr
38
+ - Worker would fail if application code was writing to stderr
31
39
  [[#35](https://github.com/skroutz/rspecq/pull/35)]
32
40
 
33
41
  ## 0.2.1 (2020-09-09)
data/README.md CHANGED
@@ -26,7 +26,6 @@ and [ci-queue](https://github.com/Shopify/ci-queue).
26
26
  - Handles intermittent worker failures (e.g. network hiccups, faulty hardware etc.)
27
27
  by detecting non-responsive workers and requeing their jobs. See [*Worker failures*](#worker-failures)
28
28
  - Sentry integration for monitoring build-level events. See [*Sentry integration*](#sentry-integration).
29
- - [PLANNED] StatsD integration for various build-level metrics and insights.
30
29
  See [#2](https://github.com/skroutz/rspecq/issues/2).
31
30
  - Automatic termination of builds after a certain amount of failures. See [*Fail-fast*](#fail-fast).
32
31
 
@@ -74,6 +73,7 @@ OPTIONS:
74
73
  Exits with a non-zero status code if there were any failures.
75
74
  --report-timeout N Fail if build is not finished after N seconds. Only applicable if --report is enabled (default: 3600).
76
75
  --max-requeues N Retry failed examples up to N times before considering them legit failures (default: 3).
76
+ --queue-wait-timeout N Time to wait for a queue to be ready before considering it failed (default: 30).
77
77
  --fail-fast N Abort build with a non-zero status code after N failed examples.
78
78
  -h, --help Show this message.
79
79
  -v, --version Print the version and exit.
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require "rake/testtask"
2
2
 
3
3
  Rake::TestTask.new do |t|
4
4
  t.libs << "test"
5
- t.test_files = FileList['test/test_*.rb']
5
+ t.test_files = FileList["test/test_*.rb"]
6
6
  t.verbose = true
7
7
  end
8
8
 
data/bin/rspecq CHANGED
@@ -2,9 +2,10 @@
2
2
  require "optparse"
3
3
  require "rspecq"
4
4
 
5
- DEFAULT_REDIS_HOST = "127.0.0.1"
5
+ DEFAULT_REDIS_HOST = "127.0.0.1".freeze
6
6
  DEFAULT_REPORT_TIMEOUT = 3600 # 1 hour
7
7
  DEFAULT_MAX_REQUEUES = 3
8
+ DEFAULT_QUEUE_WAIT_TIMEOUT = 30
8
9
  DEFAULT_FAIL_FAST = 0
9
10
 
10
11
  def env_set?(var)
@@ -66,7 +67,7 @@ OptionParser.new do |o|
66
67
 
67
68
  o.on("--report", "Enable reporter mode: do not pull tests off the queue; " \
68
69
  "instead print build progress and exit when it's " \
69
- "finished.\n#{o.summary_indent*9} " \
70
+ "finished.\n#{o.summary_indent * 9} " \
70
71
  "Exits with a non-zero status code if there were any " \
71
72
  "failures.") do |v|
72
73
  opts[:report] = v
@@ -84,8 +85,14 @@ OptionParser.new do |o|
84
85
  opts[:max_requeues] = v
85
86
  end
86
87
 
88
+ o.on("--queue-wait-timeout N", Integer, "Time to wait for a queue to be " \
89
+ "ready before considering it failed " \
90
+ "(default: #{DEFAULT_QUEUE_WAIT_TIMEOUT}).") do |v|
91
+ opts[:queue_wait_timeout] = v
92
+ end
93
+
87
94
  o.on("--fail-fast N", Integer, "Abort build with a non-zero status code " \
88
- "after N failed examples." ) do |v|
95
+ "after N failed examples.") do |v|
89
96
  opts[:fail_fast] = v
90
97
  end
91
98
 
@@ -104,15 +111,18 @@ opts[:build] ||= ENV["RSPECQ_BUILD"]
104
111
  opts[:worker] ||= ENV["RSPECQ_WORKER"]
105
112
  opts[:redis_host] ||= ENV["RSPECQ_REDIS"] || DEFAULT_REDIS_HOST
106
113
  opts[:timings] ||= env_set?("RSPECQ_UPDATE_TIMINGS")
107
- opts[:file_split_threshold] ||= Integer(ENV["RSPECQ_FILE_SPLIT_THRESHOLD"] || 9999999)
114
+ opts[:file_split_threshold] ||= Integer(ENV["RSPECQ_FILE_SPLIT_THRESHOLD"] || 9_999_999)
108
115
  opts[:report] ||= env_set?("RSPECQ_REPORT")
109
116
  opts[:report_timeout] ||= Integer(ENV["RSPECQ_REPORT_TIMEOUT"] || DEFAULT_REPORT_TIMEOUT)
110
117
  opts[:max_requeues] ||= Integer(ENV["RSPECQ_MAX_REQUEUES"] || DEFAULT_MAX_REQUEUES)
118
+ opts[:queue_wait_timeout] ||= Integer(ENV["RSPECQ_QUEUE_WAIT_TIMEOUT"] || DEFAULT_QUEUE_WAIT_TIMEOUT)
111
119
  opts[:redis_url] ||= ENV["RSPECQ_REDIS_URL"]
112
120
  opts[:fail_fast] ||= Integer(ENV["RSPECQ_FAIL_FAST"] || DEFAULT_FAIL_FAST)
113
121
 
122
+ # rubocop:disable Style/RaiseArgs, Layout/EmptyLineAfterGuardClause
114
123
  raise OptionParser::MissingArgument.new(:build) if opts[:build].nil?
115
124
  raise OptionParser::MissingArgument.new(:worker) if !opts[:report] && opts[:worker].nil?
125
+ # rubocop:enable Style/RaiseArgs, Layout/EmptyLineAfterGuardClause
116
126
 
117
127
  redis_opts = {}
118
128
 
@@ -127,6 +137,7 @@ if opts[:report]
127
137
  build_id: opts[:build],
128
138
  timeout: opts[:report_timeout],
129
139
  redis_opts: redis_opts,
140
+ queue_wait_timeout: opts[:queue_wait_timeout]
130
141
  )
131
142
 
132
143
  reporter.report
@@ -141,6 +152,7 @@ else
141
152
  worker.populate_timings = opts[:timings]
142
153
  worker.file_split_threshold = opts[:file_split_threshold]
143
154
  worker.max_requeues = opts[:max_requeues]
155
+ worker.queue_wait_timeout = opts[:queue_wait_timeout]
144
156
  worker.fail_fast = opts[:fail_fast]
145
157
  worker.work
146
158
  end
@@ -1,5 +1,10 @@
1
1
  module RSpecQ
2
2
  module Formatters
3
+ # Persists failed examples information (i.e. message and backtrace), so
4
+ # that they can be reported to the end user by the Reporter.
5
+ #
6
+ # Also persists non-example error information (e.g. a syntax error that
7
+ # in a spec file).
3
8
  class FailureRecorder
4
9
  def initialize(queue, job, max_requeues)
5
10
  @queue = queue
@@ -33,16 +38,19 @@ module RSpecQ
33
38
  end
34
39
 
35
40
  presenter = RSpec::Core::Formatters::ExceptionPresenter.new(
36
- example.exception, example)
41
+ example.exception, example
42
+ )
37
43
 
38
44
  msg = presenter.fully_formatted(nil, @colorizer)
39
45
  msg << "\n"
40
46
  msg << @colorizer.wrap(
41
47
  "bin/rspec #{example.location_rerun_argument}",
42
- RSpec.configuration.failure_color)
48
+ RSpec.configuration.failure_color
49
+ )
43
50
 
44
51
  msg << @colorizer.wrap(
45
- " # #{example.full_description}", RSpec.configuration.detail_color)
52
+ " # #{example.full_description}", RSpec.configuration.detail_color
53
+ )
46
54
 
47
55
  @queue.record_example_failure(notification.example.id, msg)
48
56
  end
@@ -1,5 +1,8 @@
1
1
  module RSpecQ
2
2
  module Formatters
3
+ # Persists each job's timing (in seconds). Those timings are used when
4
+ # determining the ordering in which jobs are scheduled (slower jobs will
5
+ # be enqueued first).
3
6
  class JobTimingRecorder
4
7
  def initialize(queue, job)
5
8
  @queue = queue
@@ -14,4 +14,3 @@ module RSpecQ
14
14
  end
15
15
  end
16
16
  end
17
-
data/lib/rspecq/queue.rb CHANGED
@@ -79,7 +79,7 @@ module RSpecQ
79
79
  # NOTE: jobs will be processed from head to tail (lpop)
80
80
  def publish(jobs, fail_fast = 0)
81
81
  @redis.multi do
82
- @redis.hset(key_queue_config, 'fail_fast', fail_fast)
82
+ @redis.hset(key_queue_config, "fail_fast", fail_fast)
83
83
  @redis.rpush(key_queue_unprocessed, jobs)
84
84
  @redis.set(key_queue_status, STATUS_READY)
85
85
  end.first
@@ -131,7 +131,7 @@ module RSpecQ
131
131
  @redis.eval(
132
132
  REQUEUE_JOB,
133
133
  keys: [key_queue_unprocessed, key_requeues],
134
- argv: [job, max_requeues],
134
+ argv: [job, max_requeues]
135
135
  )
136
136
  end
137
137
 
@@ -210,9 +210,10 @@ module RSpecQ
210
210
  @redis.get(key_queue_status) == STATUS_READY
211
211
  end
212
212
 
213
- def wait_until_published(timeout=30)
213
+ def wait_until_published(timeout = 30)
214
214
  (timeout * 10).times do
215
215
  return if published?
216
+
216
217
  sleep 0.1
217
218
  end
218
219
 
@@ -250,7 +251,7 @@ module RSpecQ
250
251
  def fail_fast
251
252
  return nil unless published?
252
253
 
253
- @fail_fast ||= Integer(@redis.hget(key_queue_config, 'fail_fast'))
254
+ @fail_fast ||= Integer(@redis.hget(key_queue_config, "fail_fast"))
254
255
  end
255
256
 
256
257
  # Returns true if the number of failed tests, has surpassed the threshold
@@ -9,18 +9,19 @@ module RSpecQ
9
9
  #
10
10
  # Reporters are readers of the queue.
11
11
  class Reporter
12
- def initialize(build_id:, timeout:, redis_opts:)
12
+ def initialize(build_id:, timeout:, redis_opts:, queue_wait_timeout: 30)
13
13
  @build_id = build_id
14
14
  @timeout = timeout
15
15
  @queue = Queue.new(build_id, "reporter", redis_opts)
16
+ @queue_wait_timeout = queue_wait_timeout
16
17
 
17
18
  # We want feedback to be immediattely printed to CI users, so
18
19
  # we disable buffering.
19
- STDOUT.sync = true
20
+ $stdout.sync = true
20
21
  end
21
22
 
22
23
  def report
23
- @queue.wait_until_published
24
+ @queue.wait_until_published(@queue_wait_timeout)
24
25
 
25
26
  finished = false
26
27
 
@@ -28,7 +29,7 @@ module RSpecQ
28
29
  failure_heading_printed = false
29
30
 
30
31
  tests_duration = measure_duration do
31
- @timeout.times do |i|
32
+ @timeout.times do
32
33
  @queue.example_failures.each do |job, rspec_output|
33
34
  next if reported_failures[job]
34
35
 
@@ -124,13 +125,13 @@ module RSpecQ
124
125
  return if jobs.empty?
125
126
 
126
127
  jobs.each do |job|
127
- filename = job.sub(/\[.+\]/, '')
128
+ filename = job.sub(/\[.+\]/, "")
128
129
 
129
130
  extra = {
130
131
  build: @build_id,
131
132
  build_timeout: @timeout,
132
133
  queue: @queue.inspect,
133
- object: self.inspect,
134
+ object: inspect,
134
135
  pid: Process.pid,
135
136
  job_path: job,
136
137
  build_duration: build_duration
@@ -143,7 +144,7 @@ module RSpecQ
143
144
 
144
145
  Raven.capture_message(
145
146
  "Flaky test in #{filename}",
146
- level: 'warning',
147
+ level: "warning",
147
148
  extra: extra,
148
149
  tags: tags
149
150
  )
@@ -1,3 +1,3 @@
1
1
  module RSpecQ
2
- VERSION = "0.4.0".freeze
2
+ VERSION = "0.5.0".freeze
3
3
  end
data/lib/rspecq/worker.rb CHANGED
@@ -46,6 +46,11 @@ module RSpecQ
46
46
  # Defaults to 0
47
47
  attr_accessor :fail_fast
48
48
 
49
+ # Time to wait for a queue to be published.
50
+ #
51
+ # Defaults to 30
52
+ attr_accessor :queue_wait_timeout
53
+
49
54
  attr_reader :queue
50
55
 
51
56
  def initialize(build_id:, worker_id:, redis_opts:)
@@ -55,9 +60,10 @@ module RSpecQ
55
60
  @fail_fast = 0
56
61
  @files_or_dirs_to_run = "spec"
57
62
  @populate_timings = false
58
- @file_split_threshold = 999999
63
+ @file_split_threshold = 999_999
59
64
  @heartbeat_updated_at = nil
60
65
  @max_requeues = 3
66
+ @queue_wait_timeout = 30
61
67
 
62
68
  RSpec::Core::Formatters.register(Formatters::JobTimingRecorder, :dump_summary)
63
69
  RSpec::Core::Formatters.register(Formatters::ExampleCountRecorder, :dump_summary)
@@ -69,7 +75,7 @@ module RSpecQ
69
75
  puts "Working for build #{@build_id} (worker=#{@worker_id})"
70
76
 
71
77
  try_publish_queue!(queue)
72
- queue.wait_until_published
78
+ queue.wait_until_published(queue_wait_timeout)
73
79
 
74
80
  loop do
75
81
  # we have to bootstrap this so that it can be used in the first call
@@ -98,7 +104,7 @@ module RSpecQ
98
104
  # reconfigure rspec
99
105
  RSpec.configuration.detail_color = :magenta
100
106
  RSpec.configuration.seed = srand && srand % 0xFFFF
101
- RSpec.configuration.backtrace_formatter.filter_gem('rspecq')
107
+ RSpec.configuration.backtrace_formatter.filter_gem("rspecq")
102
108
  RSpec.configuration.add_formatter(Formatters::FailureRecorder.new(queue, job, max_requeues))
103
109
  RSpec.configuration.add_formatter(Formatters::ExampleCountRecorder.new(queue))
104
110
  RSpec.configuration.add_formatter(Formatters::WorkerHeartbeatRecorder.new(self))
@@ -155,7 +161,7 @@ module RSpecQ
155
161
  jobs.concat(files_to_run)
156
162
  end
157
163
 
158
- default_timing = timings.values[timings.values.size/2]
164
+ default_timing = timings.values[timings.values.size / 2]
159
165
 
160
166
  # assign timings (based on previous runs) to all jobs
161
167
  jobs = jobs.each_with_object({}) do |j, h|
@@ -180,7 +186,8 @@ module RSpecQ
180
186
  # see https://github.com/rspec/rspec-core/pull/2723
181
187
  if Gem::Version.new(RSpec::Core::Version::STRING) <= Gem::Version.new("3.9.1")
182
188
  RSpec.world.instance_variable_set(
183
- :@example_group_counts_by_spec_file, Hash.new(0))
189
+ :@example_group_counts_by_spec_file, Hash.new(0)
190
+ )
184
191
  end
185
192
 
186
193
  # RSpec.clear_examples does not reset those, which causes issues when
@@ -204,17 +211,17 @@ module RSpecQ
204
211
 
205
212
  if !cmd_result.success?
206
213
  rspec_output = begin
207
- JSON.parse(out)
208
- rescue JSON::ParserError
209
- out
210
- end
214
+ JSON.parse(out)
215
+ rescue JSON::ParserError
216
+ out
217
+ end
211
218
 
212
219
  log_event(
213
220
  "Failed to split slow files, falling back to regular scheduling.\n #{err}",
214
221
  "error",
215
222
  rspec_stdout: rspec_output,
216
223
  rspec_stderr: err,
217
- cmd_result: cmd_result.inspect,
224
+ cmd_result: cmd_result.inspect
218
225
  )
219
226
 
220
227
  pp rspec_output
@@ -236,7 +243,7 @@ module RSpecQ
236
243
 
237
244
  # Prints msg to standard output and emits an event to Sentry, if the
238
245
  # SENTRY_DSN environment variable is set.
239
- def log_event(msg, level, additional={})
246
+ def log_event(msg, level, additional = {})
240
247
  puts msg
241
248
 
242
249
  Raven.capture_message(msg, level: level, extra: {
@@ -247,8 +254,8 @@ module RSpecQ
247
254
  populate_timings: populate_timings,
248
255
  file_split_threshold: file_split_threshold,
249
256
  heartbeat_updated_at: @heartbeat_updated_at,
250
- object: self.inspect,
251
- pid: Process.pid,
257
+ object: inspect,
258
+ pid: Process.pid
252
259
  }.merge(additional))
253
260
  end
254
261
  end
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.0
4
+ version: 0.5.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-07 00:00:00.000000000 Z
11
+ date: 2021-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec-core
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rake
56
+ name: minitest
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -81,7 +81,7 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: minitest
84
+ name: rake
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.93.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.93.0
111
125
  description:
112
126
  email: agis.anast@gmail.com
113
127
  executables:
@@ -149,7 +163,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
163
  - !ruby/object:Gem::Version
150
164
  version: '0'
151
165
  requirements: []
152
- rubygems_version: 3.1.4
166
+ rubyforge_project:
167
+ rubygems_version: 2.7.6.2
153
168
  signing_key:
154
169
  specification_version: 4
155
170
  summary: Optimally distribute and run RSpec suites among parallel workers; for faster