rspecq 0.4.0 → 0.5.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: 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