constable-rails 1.4.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5fd3b3f867d958ed8f1306a51fb6a001145d88bd4c79839dd6d603f0b565248b
4
- data.tar.gz: 1dee6cf2bece623c102744cb44e271461907d386715ed435fcc871ee131dd92c
3
+ metadata.gz: 04f058e6367c5b8a7739a80141601c3db8fc4f088ff1a040353cad17c435e4f8
4
+ data.tar.gz: 18ab049c33174d1028c94352c20612fb79c39ed6aee70bce6a462321a4916d32
5
5
  SHA512:
6
- metadata.gz: 1f6c418f0dd90a2e6e45733b237e8aa3c8e2e0d7c62b55d145dc7596bf047569983f243ba92558050612328f78834971cf5fef19e52efe4eb4deee0f5a751506
7
- data.tar.gz: 74e6fd7f76215036de95d36ccdd84a068a71b999368e97f998e5e832c2230d3624eaf6aba4aafe0ff5edf2aa0245b15a050e85fb5784347c6407a01d72842711
6
+ metadata.gz: 3f1e99fc5bbf329fc0bff1bfd9b35afb396ae1ac2f23a1db8f75be493507dce586ff604bf016749975e384f764637c79dfa53e44092f417f582fd63d6009e38f
7
+ data.tar.gz: 18ff4465d65894ee18dbe1e5d116ae1a4128798ed759d41dd894135dd0b30554ab95329301c2559079e35066ce8cfc489ec88fe40760ef6eeedcbfa7f547972c
data/CHANGELOG.md CHANGED
@@ -5,6 +5,87 @@ 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
+
41
+ ## [1.4.1]
42
+
43
+ ### A parallel run that ran nothing is no longer a pass
44
+
45
+ Pointing a real app at `worker_databases: reuse` produced this:
46
+
47
+ ```
48
+ CONSTABLE 0 tests · 0 cases · 7.9s
49
+ ✓ 0 passed ✗ 0 failed
50
+ ```
51
+
52
+ Nineteen files scheduled. None ran. Exit 0. The third time this shape has appeared in a
53
+ week, and the most dangerous instance of it.
54
+
55
+ Two causes. A forked worker only reported `Constable::Error`, so anything else killed it
56
+ silently — the parent saw a closed pipe, no results and no reason. Workers now report
57
+ whatever they die of, including the exit status when they die below Ruby (a signal, a
58
+ segfault, an OOM kill; forking an app that already holds native database connections can
59
+ do exactly that). And the fallback to a serial run was conditional on a worker having
60
+ managed to *explain* itself; it now triggers on the fact that matters — work was
61
+ scheduled and nothing came back. If the serial fallback also produces nothing, that
62
+ raises rather than being summarised as a clean zero.
63
+
64
+ Same command now: **84 tests run**, serially, with a warning saying why.
65
+
66
+ ### Workers no longer build databases; `constable prepare` does
67
+
68
+ `reuse` had each worker build its own missing databases after forking. On Postgres the
69
+ clone must disconnect everything attached to the template first — and the template is the
70
+ shared test database *every other worker* is cloning from at the same moment. Twelve
71
+ workers terminated each other's connections and died mid-run.
72
+
73
+ Preparation happens once, in the parent, through `constable prepare`. A worker that finds
74
+ nothing to connect to says so and names the command. `constable prepare` also boots the
75
+ app first, which it was not doing: it reported "this app has no ActiveRecord test
76
+ databases to prepare" on an app with three of them.
77
+
78
+ ### Configuration is documented where you read it
79
+
80
+ Every setting in `.constable/config.yml` now carries its explanation, and the ones with
81
+ fixed choices name and describe each value inline — `worker_databases`, `output`, the
82
+ storage adapter. Two tests enforce it: a setting cannot arrive without an explanation, and
83
+ one with fixed choices has to name them.
84
+
85
+ The blotter section now says outright that it is **not** your application's database.
86
+ Two people read it the other way, which is a naming problem, not a reading problem.
87
+
88
+
8
89
  ## [1.4.0]
9
90
 
10
91
  Two problems from the same 1,277-file suite: a docket nobody asked for, and 110 minutes.
@@ -664,7 +745,9 @@ Initial release.
664
745
  - Diff-based coverage gate — only lines changed in the current diff are held to the
665
746
  threshold. `constable beat` for the full picture, `--html` for a browsable report.
666
747
 
667
- [Unreleased]: https://github.com/Ray-Hughes/constable/compare/v1.4.0...HEAD
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
750
+ [1.4.1]: https://github.com/Ray-Hughes/constable/compare/v1.4.0...v1.4.1
668
751
  [1.4.0]: https://github.com/Ray-Hughes/constable/compare/v1.3.3...v1.4.0
669
752
  [1.3.3]: https://github.com/Ray-Hughes/constable/compare/v1.3.2...v1.3.3
670
753
  [1.3.2]: https://github.com/Ray-Hughes/constable/compare/v1.3.1...v1.3.2
data/lib/constable/cli.rb CHANGED
@@ -428,6 +428,10 @@ module Constable
428
428
  option :workers, type: :numeric, desc: "How many to prepare (default: the configured worker count)"
429
429
  def prepare
430
430
  config = load_config
431
+ # The app has to be up before we can ask ActiveRecord anything about it.
432
+ Runner.boot!
433
+ config.apply_overrides!(Constable.configuration.overrides)
434
+
431
435
  unless WorkerDatabases.shardable?
432
436
  CLI.complain("This app has no ActiveRecord test databases to prepare.")
433
437
  exit(EXIT_USAGE)
@@ -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=` rather than plain Kernel#require: it is RSpec's own accessor, and
310
- # it puts `lib` and the default path (`spec`) on the load path first, which is
311
- # what makes a bare `require "rails_helper"` resolve.
312
- configuration.requires = requires
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)
@@ -64,6 +64,20 @@ module Constable
64
64
  def jail_run? = @jail_run
65
65
  def coverage? = @coverage_requested
66
66
 
67
+ # Boots the app the way a run does -- test/case_helper.rb, which requires
68
+ # config/environment -- without selecting or running anything.
69
+ #
70
+ # `constable prepare` needs this: it asks ActiveRecord what databases exist, and
71
+ # before the helper has run there is no ActiveRecord to ask. It reported "this app has
72
+ # no test databases to prepare" on an app with three of them.
73
+ def self.boot!
74
+ helper = %w[test/case_helper.rb spec/case_helper.rb]
75
+ .map { |p| File.join(Constable.root, p) }
76
+ .find { |p| File.exist?(p) }
77
+ require helper if helper
78
+ helper
79
+ end
80
+
67
81
  # Loads every case file and hands back the identities the suite actually defines,
68
82
  # without running anything. `constable prune` needs this: which tests still exist is
69
83
  # only knowable once the whole suite has been loaded.
@@ -352,8 +366,18 @@ module Constable
352
366
  exit!(0)
353
367
  end
354
368
 
355
- bucket.each do |item|
356
- run_item(item).each { |result| write_message(writer, :result, result.to_h) }
369
+ # Anything at all, not just Constable::Error. An uncaught exception in a forked
370
+ # child kills it silently: the parent sees a closed pipe, no results and no
371
+ # reason, and a run that scheduled nineteen files reports zero tests and exits
372
+ # 0. A worker that dies has to say so.
373
+ begin
374
+ bucket.each do |item|
375
+ run_item(item).each { |result| write_message(writer, :result, result.to_h) }
376
+ end
377
+ rescue Exception => e # rubocop:disable Lint/RescueException
378
+ write_message(writer, :worker_error, "#{e.class}: #{e.message}")
379
+ writer.close
380
+ exit!(0)
357
381
  end
358
382
 
359
383
  # A worker owns its own cold-case session, and it dies here. Fire the engine's
@@ -374,7 +398,7 @@ module Constable
374
398
  end
375
399
 
376
400
  collected = drain(readers)
377
- pids.each { |pid| Process.waitpid(pid) rescue nil } # rubocop:disable Style/RescueModifier
401
+ record_worker_exits(pids)
378
402
 
379
403
  # No worker could build itself a database, so no test ran. Not every app can be
380
404
  # sharded: an app whose schema.rb cannot rebuild the database on its own -- Postgres
@@ -385,7 +409,13 @@ module Constable
385
409
  #
386
410
  # Nothing has run yet, so falling back to a serial run costs a restart, not
387
411
  # correctness.
388
- return run_serially_after_worker_failure(items) if collected.empty? && worker_errors.any?
412
+ #
413
+ # The condition is deliberately "nothing came back", not "a worker said why". A
414
+ # child can die without managing to report -- and then a run that scheduled
415
+ # nineteen files says "0 tests, 0 failed" and exits 0, which is the worst thing a
416
+ # test runner can do. If work was scheduled and no result arrived, something is
417
+ # wrong whether or not anyone explained it.
418
+ return run_serially_after_worker_failure(items) if collected.empty? && !items.empty?
389
419
 
390
420
  # A warning raised inside a worker only ever reached that worker's memory, so the
391
421
  # results carry them home. Nothing that bends the rules is allowed to go missing
@@ -396,22 +426,58 @@ module Constable
396
426
 
397
427
  def worker_errors = (@worker_errors ||= [])
398
428
 
429
+ # A worker can die below Ruby: a segfault, an OOM kill, a signal. No `rescue` reaches
430
+ # that, so the only evidence is the exit status, and without it the run can only say
431
+ # "the workers exited without reporting anything" -- true, and useless.
432
+ #
433
+ # Forking a process that already holds database connections is where this comes from.
434
+ # An app with a native driver -- Oracle's OCI, for instance -- can have a child die
435
+ # the moment it touches an inherited handle.
436
+ def record_worker_exits(pids)
437
+ pids.each do |pid|
438
+ _, status = Process.waitpid2(pid)
439
+ next if status.nil? || status.success?
440
+
441
+ worker_errors << if status.signaled?
442
+ "a worker was killed by SIG#{Signal.signame(status.termsig)} " \
443
+ "-- forking an app that already holds native database " \
444
+ "connections can do this"
445
+ else
446
+ "a worker exited with status #{status.exitstatus}"
447
+ end
448
+ rescue StandardError
449
+ nil
450
+ end
451
+ end
452
+
399
453
  def run_serially_after_worker_failure(items)
400
454
  reason = worker_errors.first.to_s
455
+ reason = "the workers exited without reporting anything" if reason.empty?
401
456
 
402
457
  Constable.warn!(
403
- "no parallel worker could build its own test database, so the suite ran serially " \
404
- "instead. This usually means the app's schema cannot rebuild the database by " \
405
- "itself -- Postgres custom types, functions and triggers are the common reason, " \
406
- "and `rails test` parallelization fails the same way. Set " \
407
- "`worker_databases: reuse` to keep prepared databases between runs instead, or " \
408
- "`worker_databases: off` to stop trying. The first worker said: #{reason}",
458
+ "the parallel workers produced no results, so the suite ran serially instead -- " \
459
+ "everything ran, nothing was skipped. Two things cause this: the app's schema " \
460
+ "cannot rebuild a database by itself (Postgres custom types; try " \
461
+ "`worker_databases: reuse` with `constable prepare`), or forking is unsafe in " \
462
+ "this app, which happens when a native driver's connections are inherited by a " \
463
+ "child. `worker_databases: off` stops the attempt. The workers said: #{reason}",
409
464
  kind: :parallel
410
465
  )
411
466
 
412
467
  # The blotter handle was closed before forking, and the pool was cleared. Both come
413
468
  # back on their next use, so there is nothing to reopen by hand.
414
- run_serial(items)
469
+ results = run_serial(items)
470
+
471
+ # Belt and braces. If the serial fallback also produces nothing for work that was
472
+ # scheduled, the run is broken in a way no summary can honestly describe, and
473
+ # reporting a clean zero would be a lie.
474
+ if results.empty? && !items.empty?
475
+ raise Constable::Error,
476
+ "#{items.size} test file(s) were scheduled and none of them ran. " \
477
+ "The first worker said: #{reason}"
478
+ end
479
+
480
+ results
415
481
  end
416
482
 
417
483
  # Every message on the pipe is tagged, because results are not the only thing a worker
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Constable
4
- VERSION = "1.4.0"
4
+ VERSION = "1.4.2"
5
5
  end
@@ -58,7 +58,7 @@ module Constable
58
58
  # that works when the schema cannot rebuild the database by itself.
59
59
  def after_fork!(index, mode: :schema)
60
60
  return false unless shardable?
61
- return reuse!(index) if mode.to_sym == :reuse
61
+ return connect_worker!(index) if mode.to_sym == :reuse
62
62
 
63
63
  ::ActiveRecord::TestDatabases.create_and_load_schema(index, env_name: env_name)
64
64
  true
@@ -172,8 +172,38 @@ module Constable
172
172
  nil
173
173
  end
174
174
 
175
- # The :reuse half. Points every database this environment declares at its `_<index>`
176
- # sibling, and only builds the ones that are not there yet.
175
+ # The :reuse half, inside a worker: connect to `<database>_<index>` and nothing else.
176
+ #
177
+ # Building is deliberately not done here. Twelve workers forked at once would each try
178
+ # to build the same missing databases simultaneously, and on Postgres the clone has to
179
+ # disconnect everything attached to the template first -- which is the shared test
180
+ # database every other worker is also cloning from. They terminate each other's
181
+ # connections and die, silently, mid-run. Observed exactly that: nineteen files
182
+ # scheduled, zero results, workers gone without a word.
183
+ #
184
+ # So preparation happens once, in the parent, through `constable prepare`. A worker
185
+ # that finds nothing to connect to says so, and the runner falls back to serial.
186
+ def connect_worker!(index)
187
+ missing = []
188
+
189
+ each_worker_config(index) do |db_config|
190
+ missing << db_config.database unless populated?(db_config)
191
+ end
192
+
193
+ unless missing.empty?
194
+ raise Constable::Error,
195
+ "worker #{index} has no database to use (#{missing.join(", ")}). " \
196
+ "`worker_databases: reuse` expects them to exist already -- run " \
197
+ "`constable prepare` once, then run the suite."
198
+ end
199
+
200
+ ::ActiveRecord::Base.establish_connection
201
+ []
202
+ end
203
+
204
+ # The building half, run from the parent by `constable prepare`. Points every database
205
+ # this environment declares at its `_<index>` sibling, and builds the ones that are
206
+ # not there yet.
177
207
  #
178
208
  # "There" means present *and* populated: an empty database is not a prepared one, and
179
209
  # connecting to it would hand the worker a suite with no tables. Deciding that per
@@ -206,13 +236,32 @@ module Constable
206
236
  # Every database config for this environment, renamed to its per-worker sibling.
207
237
  # `_database=` is exactly how Rails' own TestDatabases does the renaming, and this
208
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
+
209
257
  def each_worker_config(index)
210
258
  configs = ::ActiveRecord::Base.configurations.configs_for(env_name: env_name,
211
259
  include_hidden: true)
212
260
  configs.each do |db_config|
213
- db_config._database = "#{db_config.database}_#{index}"
214
261
  next unless db_config.database_tasks?
262
+ next unless shardable_adapter?(db_config)
215
263
 
264
+ db_config._database = "#{db_config.database}_#{index}"
216
265
  yield db_config
217
266
  end
218
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
@@ -43,6 +43,11 @@ cold_cases: []
43
43
 
44
44
  # The blotter: flake history, the jail docket, warrants.
45
45
  #
46
+ # NOT your application's database, and nothing to do with it. Your tests keep using
47
+ # whatever config/database.yml says -- Postgres stays Postgres. This is a separate file
48
+ # holding Constable's own bookkeeping, and it never contains a row of your data. Leaving
49
+ # it on SQLite is right even for a Postgres app.
50
+ #
46
51
  # Constable owns this store outright. It is deliberately not the app's own
47
52
  # database, for two reasons. Native cases run inside a transaction that gets
48
53
  # rolled back, so writing "this test just failed" through that connection would
@@ -54,7 +59,10 @@ cold_cases: []
54
59
  # need one queryable store shared across many CI machines -- and always at a
55
60
  # separate database from the app's own, never its test connection.
56
61
  storage:
57
- adapter: sqlite # sqlite (default) | postgres | mysql
62
+ adapter: sqlite # sqlite (default, and right for almost everyone)
63
+ # postgres / mysql: only when many CI machines
64
+ # need to share ONE blotter. Always a separate
65
+ # database from your app's own.
58
66
  path: .constable/constable.sqlite3 # sqlite only
59
67
  url: # postgres/mysql only, e.g.
60
68
  # postgres://user:pass@host/constable_metadata
@@ -81,29 +89,45 @@ fail_on_warnings: false # CI: fail the build when the warning count doesn't tre
81
89
 
82
90
  parallel_workers: auto # or an explicit integer
83
91
 
84
- # How a parallel worker gets a database of its own. Sharing one is not a speed/safety
85
- # trade but a correctness bug: on SQLite the run dissolves into "database is locked", and
86
- # on a client/server database tests quietly see each other's rows.
87
- #
88
- # schema rebuild <database>_<index> from schema on every run. What Rails does for
89
- # `rails test`, and correct by construction -- no drift is possible.
90
- # reuse connect to <database>_<index> when it is already there, and build it from
91
- # schema only when it is not. Faster, because a large schema is not reloaded
92
- # every run -- and the only option that works at all when the schema cannot
93
- # rebuild the database by itself, which is true of any app with Postgres custom
94
- # types (`CREATE TYPE` has no schema.rb representation, so a from-scratch load
95
- # fails). Keeping those databases current becomes your job.
96
- # off do not shard, so do not fork. An explicit serial run: no attempt, no warning.
97
- worker_databases: schema # schema | reuse | off
92
+ # How a parallel worker gets a database of its own.
93
+ #
94
+ # Sharing one is not a speed/safety trade, it is a correctness bug: on SQLite the run
95
+ # dissolves into "database is locked", and on a client/server database tests quietly see
96
+ # each other's rows, which is worse because it looks like it worked.
97
+ #
98
+ # Three options:
99
+ #
100
+ # schema Rebuild <database>_<index> from schema.rb on every run.
101
+ # What Rails itself does for `rails test`. Correct by construction -- the
102
+ # databases cannot drift, because they are thrown away and rebuilt. The right
103
+ # default, and the wrong choice only when it is slow or impossible.
104
+ #
105
+ # reuse Keep <database>_<index> between runs, building it only when it is missing.
106
+ # Two reasons to want this:
107
+ # * Speed. A large schema is not replayed once per worker per run. On
108
+ # Postgres the databases are cloned in one statement
109
+ # (CREATE DATABASE ... TEMPLATE), which is far faster than a schema load.
110
+ # * It is the ONLY option that works when your schema cannot rebuild the
111
+ # database by itself -- true of any app with Postgres custom types, since
112
+ # CREATE TYPE has no schema.rb representation and a from-scratch load
113
+ # fails on a dump that references types it never defines.
114
+ # The cost: keeping those databases current is now your job. Run
115
+ # `constable prepare` after a migration.
116
+ #
117
+ # off Do not shard, so do not fork. An explicit serial run: no attempt to build
118
+ # anything, and no warning about it. Correct, and as slow as one core.
119
+ #
120
+ worker_databases: schema # schema (rebuild each run) | reuse (keep them) | off (serial)
98
121
 
99
- # How much the live stream says while the suite is running. The summary is identical
100
- # either way -- this only changes what you watch on the way there.
101
- #
102
- # concise one glyph per test, grouped into a run per case. A thousand tests stay
103
- # inside one screen, and a wall of green is the point.
104
- # expanded a line per test: glyph, name, duration. Slower to read in bulk, but you
105
- # can see which test is hanging while it hangs, rather than after.
106
- output: concise # concise | expanded
122
+ # How much the live stream says while the suite is running. The summary at the end is
123
+ # identical either way -- this only changes what you watch on the way there.
124
+ #
125
+ # concise One glyph per test, grouped into a run per case. A thousand tests stay
126
+ # inside one screen, and a wall of green is the point.
127
+ # expanded A line per test: glyph, description, duration. Slower to read in bulk, but
128
+ # you can see which test is hanging while it hangs, rather than afterwards.
129
+ #
130
+ output: concise # concise (one glyph per test) | expanded (a line per test)
107
131
 
108
132
  # Fallback path-based tier inference, used only when a case doesn't inherit from
109
133
  # a tiered base class. The base classes in test/case_helper.rb are the primary
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: constable-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ray Hughes