constable-rails 1.4.1 → 1.4.2
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 +4 -4
- data/CHANGELOG.md +35 -1
- data/lib/constable/cold_case/rspec.rb +76 -4
- data/lib/constable/version.rb +1 -1
- data/lib/constable/worker_databases.rb +20 -1
- data/lib/generators/constable/templates/case_helper.rb.tt +66 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 04f058e6367c5b8a7739a80141601c3db8fc4f088ff1a040353cad17c435e4f8
|
|
4
|
+
data.tar.gz: 18ab049c33174d1028c94352c20612fb79c39ed6aee70bce6a462321a4916d32
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3f1e99fc5bbf329fc0bff1bfd9b35afb396ae1ac2f23a1db8f75be493507dce586ff604bf016749975e384f764637c79dfa53e44092f417f582fd63d6009e38f
|
|
7
|
+
data.tar.gz: 18ff4465d65894ee18dbe1e5d116ae1a4128798ed759d41dd894135dd0b30554ab95329301c2559079e35066ce8cfc489ec88fe40760ef6eeedcbfa7f547972c
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ All notable changes to this project are documented here. This project adheres to
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.4.2]
|
|
9
|
+
|
|
10
|
+
### Cold cases run inside a parallel worker
|
|
11
|
+
|
|
12
|
+
The defect recorded as known in 1.4.1, now understood and fixed. Two bugs, stacked, and
|
|
13
|
+
the second one hid the first.
|
|
14
|
+
|
|
15
|
+
**RSpec swallows errors in required files.** `Configuration#requires=` routes through
|
|
16
|
+
`load_file_handling_errors`, which rescues anything raised while loading, reports it
|
|
17
|
+
through a formatter, and sets `world.wants_to_quit = true`. Every later
|
|
18
|
+
`ExampleGroup.run` then returns immediately. Constable points RSpec's streams at a
|
|
19
|
+
throwaway `StringIO` — stdout belongs to the reporter — so that report went nowhere: the
|
|
20
|
+
file loaded, the examples registered, and none of them ran. A forked worker produced no
|
|
21
|
+
results, no error and no exception, and exited 0. Requires are now loaded directly, where
|
|
22
|
+
an exception carries its own message and backtrace, and the quit flag is checked and
|
|
23
|
+
surfaced if anything sets it anyway.
|
|
24
|
+
|
|
25
|
+
**Not every database can be sharded.** With the error visible, the real cause was one
|
|
26
|
+
line: worker setup renamed *every* database config to `<database>_<index>`, including
|
|
27
|
+
connections that are not per-worker test data at all. Caseflow talks to a legacy Oracle
|
|
28
|
+
system (VACOLS) alongside its own Postgres databases, and appending `_3` to an Oracle TNS
|
|
29
|
+
service name produces:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
OCIError: ORA-12162: TNS:net service name is incorrectly specified
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
raised inside a `before(:suite)` hook — which is exactly what RSpec was swallowing. Only
|
|
36
|
+
adapters that per-worker copies make sense for are renamed now (postgresql, postgis,
|
|
37
|
+
mysql2, trilogy, sqlite3). An external system stays shared by every worker, which is what
|
|
38
|
+
it is for.
|
|
39
|
+
|
|
40
|
+
|
|
8
41
|
## [1.4.1]
|
|
9
42
|
|
|
10
43
|
### A parallel run that ran nothing is no longer a pass
|
|
@@ -712,7 +745,8 @@ Initial release.
|
|
|
712
745
|
- Diff-based coverage gate — only lines changed in the current diff are held to the
|
|
713
746
|
threshold. `constable beat` for the full picture, `--html` for a browsable report.
|
|
714
747
|
|
|
715
|
-
[Unreleased]: https://github.com/Ray-Hughes/constable/compare/v1.4.
|
|
748
|
+
[Unreleased]: https://github.com/Ray-Hughes/constable/compare/v1.4.2...HEAD
|
|
749
|
+
[1.4.2]: https://github.com/Ray-Hughes/constable/compare/v1.4.1...v1.4.2
|
|
716
750
|
[1.4.1]: https://github.com/Ray-Hughes/constable/compare/v1.4.0...v1.4.1
|
|
717
751
|
[1.4.0]: https://github.com/Ray-Hughes/constable/compare/v1.3.3...v1.4.0
|
|
718
752
|
[1.3.3]: https://github.com/Ray-Hughes/constable/compare/v1.3.2...v1.3.3
|
|
@@ -120,6 +120,7 @@ module Constable
|
|
|
120
120
|
with_engine do
|
|
121
121
|
collector = Collector.new
|
|
122
122
|
load_error = capture_load(path)
|
|
123
|
+
load_error ||= quit_flag_error(path)
|
|
123
124
|
unless load_error
|
|
124
125
|
# After the file has loaded -- that load is what registers them -- and
|
|
125
126
|
# before its examples run. See #run_pending_before_suite_hooks.
|
|
@@ -306,10 +307,22 @@ module Constable
|
|
|
306
307
|
requires = rspec_option_requires
|
|
307
308
|
return if requires.empty?
|
|
308
309
|
|
|
309
|
-
# `requires
|
|
310
|
-
#
|
|
311
|
-
#
|
|
312
|
-
|
|
310
|
+
# Deliberately NOT `configuration.requires =`, which is RSpec's own accessor.
|
|
311
|
+
# That routes through Configuration#load_file_handling_errors, which rescues
|
|
312
|
+
# anything raised while loading, reports it through a formatter, and sets
|
|
313
|
+
# `world.wants_to_quit`. Since stdout belongs to Constable's reporter, RSpec's
|
|
314
|
+
# streams are a throwaway StringIO, so that report goes nowhere -- and every
|
|
315
|
+
# later ExampleGroup.run returns immediately. The file loads, the examples
|
|
316
|
+
# register, and none of them run. A forked worker produced no results, no error
|
|
317
|
+
# and no exception, and exited 0.
|
|
318
|
+
#
|
|
319
|
+
# So the load path is set up the same way and the requires are done here, where
|
|
320
|
+
# an exception is an exception and carries its own message and backtrace.
|
|
321
|
+
add_spec_load_paths!(configuration)
|
|
322
|
+
requires.each { |path| require path }
|
|
323
|
+
configuration.instance_variable_set(:@requires, requires)
|
|
324
|
+
rescue Constable::Error
|
|
325
|
+
raise
|
|
313
326
|
rescue StandardError => e
|
|
314
327
|
# A helper that will not load is the suite's problem to fix, and it will say so
|
|
315
328
|
# loudly on the first file. Constable's job here is not to disappear.
|
|
@@ -317,6 +330,53 @@ module Constable
|
|
|
317
330
|
"Cold cases will run without it.", kind: :cold_case)
|
|
318
331
|
end
|
|
319
332
|
|
|
333
|
+
# What `configuration.requires=` does before requiring anything: puts `lib` and the
|
|
334
|
+
# default path (`spec`) on the load path, which is what makes a bare
|
|
335
|
+
# `require "rails_helper"` resolve at all.
|
|
336
|
+
def add_spec_load_paths!(configuration)
|
|
337
|
+
default_path = configuration.default_path if configuration.respond_to?(:default_path)
|
|
338
|
+
["lib", default_path].compact.uniq.each do |dir|
|
|
339
|
+
absolute = File.expand_path(dir, Constable.root)
|
|
340
|
+
$LOAD_PATH.unshift(absolute) if File.directory?(absolute) && !$LOAD_PATH.include?(absolute)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# RSpec does not let an error in a required file reach you.
|
|
345
|
+
# Configuration#load_file_handling_errors rescues anything raised while loading,
|
|
346
|
+
# reports it through `notify_non_example_exception`, and sets
|
|
347
|
+
# `world.wants_to_quit = true`. Every later ExampleGroup.run then returns
|
|
348
|
+
# immediately, so the file loads, the examples register, and none of them run.
|
|
349
|
+
#
|
|
350
|
+
# We point RSpec's output at a throwaway StringIO -- stdout belongs to the
|
|
351
|
+
# reporter -- so that report goes nowhere. The result was a forked worker that
|
|
352
|
+
# produced no results, no error and no exception, and exited 0. It took a probe
|
|
353
|
+
# inside the child to find that `wants_to_quit` was the difference.
|
|
354
|
+
#
|
|
355
|
+
# So: ask, and put the swallowed message back in front of the user.
|
|
356
|
+
def raise_if_loading_failed!(configuration, requires)
|
|
357
|
+
return unless ::RSpec.world.wants_to_quit
|
|
358
|
+
|
|
359
|
+
# Clear it, or every later file in this session inherits the flag and runs
|
|
360
|
+
# nothing either.
|
|
361
|
+
::RSpec.world.wants_to_quit = false
|
|
362
|
+
|
|
363
|
+
raise Constable::Error,
|
|
364
|
+
"RSpec could not load #{requires.join(", ")} (from .rspec). " \
|
|
365
|
+
"#{swallowed_output(configuration)}".strip
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Whatever RSpec wrote about it before we could ask. Its streams are ours, so this
|
|
369
|
+
# is the only copy in existence.
|
|
370
|
+
def swallowed_output(configuration)
|
|
371
|
+
[configuration.error_stream, configuration.output_stream, configuration.deprecation_stream]
|
|
372
|
+
.uniq
|
|
373
|
+
.filter_map { |io| io.string.to_s.strip if io.respond_to?(:string) }
|
|
374
|
+
.reject(&:empty?)
|
|
375
|
+
.first.to_s
|
|
376
|
+
rescue StandardError
|
|
377
|
+
""
|
|
378
|
+
end
|
|
379
|
+
|
|
320
380
|
# Parsed by RSpec itself, so `.rspec`, `~/.rspec`, `.rspec-local` and SPEC_OPTS are
|
|
321
381
|
# all read with its precedence rather than a guess at the format.
|
|
322
382
|
def rspec_option_requires
|
|
@@ -372,6 +432,18 @@ module Constable
|
|
|
372
432
|
configuration.start_time = ::RSpec::Core::Time.now if configuration.respond_to?(:start_time=)
|
|
373
433
|
end
|
|
374
434
|
|
|
435
|
+
# Same swallowing, one level down: a `require` inside the spec file that fails is
|
|
436
|
+
# rescued by RSpec, flagged, and never surfaced. Without this the file reports as
|
|
437
|
+
# a clean run of zero tests.
|
|
438
|
+
def quit_flag_error(path)
|
|
439
|
+
return nil unless ::RSpec.world.wants_to_quit
|
|
440
|
+
|
|
441
|
+
::RSpec.world.wants_to_quit = false
|
|
442
|
+
Constable::Error.new(
|
|
443
|
+
"RSpec stopped while loading #{path}. #{swallowed_output(::RSpec.configuration)}".strip
|
|
444
|
+
)
|
|
445
|
+
end
|
|
446
|
+
|
|
375
447
|
# A file that won't even parse is news, not a crash. Report it as one errored
|
|
376
448
|
# result so the run keeps going and the summary names the file.
|
|
377
449
|
def capture_load(path)
|
data/lib/constable/version.rb
CHANGED
|
@@ -236,13 +236,32 @@ module Constable
|
|
|
236
236
|
# Every database config for this environment, renamed to its per-worker sibling.
|
|
237
237
|
# `_database=` is exactly how Rails' own TestDatabases does the renaming, and this
|
|
238
238
|
# runs in a forked child, so the mutation dies with the worker.
|
|
239
|
+
# Adapters where "give each worker its own copy" is a thing that can be done at all.
|
|
240
|
+
#
|
|
241
|
+
# An app can hold connections Constable has no business renaming. Caseflow talks to a
|
|
242
|
+
# legacy Oracle system (VACOLS) alongside its own Postgres databases; appending `_3` to
|
|
243
|
+
# an Oracle TNS service name produces `ORA-12162: TNS:net service name is incorrectly
|
|
244
|
+
# specified`, and since that surfaces inside a `before(:suite)` hook, RSpec swallowed
|
|
245
|
+
# it and the worker ran nothing at all.
|
|
246
|
+
#
|
|
247
|
+
# An external system like that is shared by every worker on purpose: it is not
|
|
248
|
+
# per-worker test data, and there is no per-worker copy of it to make.
|
|
249
|
+
SHARDABLE_ADAPTERS = %w[postgresql postgis mysql2 trilogy sqlite3].freeze
|
|
250
|
+
|
|
251
|
+
def shardable_adapter?(db_config)
|
|
252
|
+
return false unless db_config.respond_to?(:adapter)
|
|
253
|
+
|
|
254
|
+
SHARDABLE_ADAPTERS.include?(db_config.adapter.to_s)
|
|
255
|
+
end
|
|
256
|
+
|
|
239
257
|
def each_worker_config(index)
|
|
240
258
|
configs = ::ActiveRecord::Base.configurations.configs_for(env_name: env_name,
|
|
241
259
|
include_hidden: true)
|
|
242
260
|
configs.each do |db_config|
|
|
243
|
-
db_config._database = "#{db_config.database}_#{index}"
|
|
244
261
|
next unless db_config.database_tasks?
|
|
262
|
+
next unless shardable_adapter?(db_config)
|
|
245
263
|
|
|
264
|
+
db_config._database = "#{db_config.database}_#{index}"
|
|
246
265
|
yield db_config
|
|
247
266
|
end
|
|
248
267
|
end
|
|
@@ -180,6 +180,72 @@ end
|
|
|
180
180
|
# -----------------------------------------------------------------------------
|
|
181
181
|
|
|
182
182
|
Constable.configure do |c|
|
|
183
|
+
# --- Settings. Uncomment what you want; each is shown at its default. --------
|
|
184
|
+
|
|
185
|
+
# Workers default to `auto` -- processor count minus a little headroom, so the
|
|
186
|
+
# machine stays usable while the suite runs. Pin it when a CI container lies
|
|
187
|
+
# about its core count, or to rule parallelism out while debugging.
|
|
188
|
+
#
|
|
189
|
+
# c.parallel_workers = 4
|
|
190
|
+
|
|
191
|
+
# How a parallel worker gets a database of its own. Sharing one is not a
|
|
192
|
+
# speed/safety trade, it is a correctness bug: on SQLite the run dissolves into
|
|
193
|
+
# "database is locked", and on a client/server database tests quietly see each
|
|
194
|
+
# other's rows, which is worse because it looks like it worked.
|
|
195
|
+
#
|
|
196
|
+
# :schema rebuild <database>_<index> from schema.rb every run. What Rails
|
|
197
|
+
# does for `rails test`, and correct by construction -- the
|
|
198
|
+
# databases cannot drift, because they are thrown away each time.
|
|
199
|
+
# The default, and the wrong choice only when it is slow or
|
|
200
|
+
# impossible.
|
|
201
|
+
# :reuse keep them between runs. Faster (a big schema is not replayed per
|
|
202
|
+
# worker), and the ONLY option that works when your schema cannot
|
|
203
|
+
# rebuild the database by itself -- true of any app with Postgres
|
|
204
|
+
# custom types, since CREATE TYPE has no schema.rb representation.
|
|
205
|
+
# Run `constable prepare` once to build them, and again after a
|
|
206
|
+
# migration; keeping them current is your job.
|
|
207
|
+
# :off do not shard, so do not fork. An explicit serial run: no attempt,
|
|
208
|
+
# no warning. Correct, and as slow as one core.
|
|
209
|
+
#
|
|
210
|
+
# c.worker_databases = :reuse
|
|
211
|
+
|
|
212
|
+
# The live stream while the suite runs. The summary is identical either way.
|
|
213
|
+
# :concise one glyph per test, grouped per case (default)
|
|
214
|
+
# :expanded a line per test: glyph, description, duration -- so you can see
|
|
215
|
+
# which test is hanging while it hangs, not afterwards
|
|
216
|
+
#
|
|
217
|
+
# c.output = :expanded
|
|
218
|
+
|
|
219
|
+
# Put a test on the docket by itself when it passed last run and failed this
|
|
220
|
+
# one. Off by default, and the reason is what jailing does: a jailed test is
|
|
221
|
+
# SKIPPED on every later run. On automatically, a suite with order-dependent
|
|
222
|
+
# tests quietly stops running things nobody chose to stop running.
|
|
223
|
+
#
|
|
224
|
+
# c.jail_flakes = true
|
|
225
|
+
|
|
226
|
+
# Rerun a failure in isolation before believing it. A test that then passes is
|
|
227
|
+
# flaky rather than broken: it gets a warrant instead of failing the build, and
|
|
228
|
+
# stays loudly visible until someone deals with it.
|
|
229
|
+
#
|
|
230
|
+
# c.warrants = true
|
|
231
|
+
|
|
232
|
+
# CI: fail the build when the warning count is not trending down. Warnings are
|
|
233
|
+
# cold-case files and `unsafe` blocks -- allowed, but visible.
|
|
234
|
+
#
|
|
235
|
+
# c.fail_on_warnings = true
|
|
236
|
+
|
|
237
|
+
# Diff-based coverage: only lines changed in the current diff are held to it.
|
|
238
|
+
#
|
|
239
|
+
# c.coverage = true
|
|
240
|
+
# c.coverage_threshold = 90
|
|
241
|
+
|
|
242
|
+
# Your existing RSpec/Minitest files, run verbatim as cold cases through their
|
|
243
|
+
# own engine. `constable import --from=rspec` writes this for you.
|
|
244
|
+
#
|
|
245
|
+
# c.cold_cases = ["spec/**/*_spec.rb"]
|
|
246
|
+
|
|
247
|
+
# --- Code. This can only be written here. -----------------------------------
|
|
248
|
+
|
|
183
249
|
# One-time global setup, run once per process before the whole suite. For
|
|
184
250
|
# configuring the world -- drivers, adapters, formats -- and deliberately not
|
|
185
251
|
# for creating records that tests then share. See the note at the bottom of
|