Module: Varar::RSpec
- Defined in:
- lib/varar/rspec.rb
Overview
RSpec adapter. One call defines an RSpec example group per oath matched by
varar.config.json, with one it per Markdown example (header-bound rows are
separate examples) and a drift gate. See ADR 0005.
# spec/varar_spec.rb
require "varar/rspec"
Varar::RSpec.generate
Constant Summary collapse
- VERSION =
'0.8.1'
Class Method Summary collapse
- .define_group(oath_path, root, loaded, store, update, results, workspace) ⇒ Object
- .generate(root: nil) ⇒ Object
- .project_workspace(oaths, root) ⇒ Object
-
.referenced_sources(plan, workspace) ⇒ Object
Whether a section is a standalone example depends on whether another oath references it, which is whole-project knowledge (ADR 0016).
- .var_diff_error?(error) ⇒ Boolean
Class Method Details
.define_group(oath_path, root, loaded, store, update, results, workspace) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/varar/rspec.rb', line 68 def define_group(oath_path, root, loaded, store, update, results, workspace) rel = Runner.rel_posix(oath_path, root) source = File.read(oath_path, encoding: 'UTF-8') # `rel`, not the basename: doc.path is an oath's identity in every port, # so a relative reference resolves alike and two same-named oaths in # different directories stay distinct (ADR 0016). plan = Runner.plan_oath(rel, source, loaded.registry, workspace) referenced = referenced_sources(plan, workspace) pairs = Runner.examples_with_runs(plan, loaded.create_context, Runner::RecordingReporter.new) drifts = Core::Drifts.reconcile_drift(store, rel, source, plan.doc, plan, update: update) ::RSpec.describe(rel) do pairs.each do |example, run| # Lines in THIS oath: a step a reference block spliced in from # another oath (ADR 0016) contributes none, since its line is not in # this file. lines = example.steps.reject(&:doc_path).map { |s| s.match_span.start_line }.uniq # A var diff surfaces as a failure carrying the span-anchored render; # any other exception propagates. RSpec reports both as failures. it(example.name) do run.call rescue StandardError => e # Recorded here, where the error object is still in hand: to_failure # reads the anchor the executor attached to it. results.record(rel, source, Core::ExampleResult.new( name: example.name, status: 'failed', lines: lines, failure: Core::Failures.to_failure(e, rel, lines.first || 0) ), referenced) raise Runner.render_failure(e, source, rel) if RSpec.var_diff_error?(e) raise else results.record(rel, source, Core::ExampleResult.new( name: example.name, status: 'passed', lines: lines, failure: nil ), referenced) end end drifts.each do |drift| = Core::Diagnostics.drift_detected(drift.name, drift.span). it("var drift at line #{drift.line}") { raise } end # This oath's examples are all in — write its results. A passing oath is # written too: a stale file would keep a diagnostic on screen that this # run has just cleared. after(:all) { results.flush(root, rel) } end end |
.generate(root: nil) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/varar/rspec.rb', line 19 def generate(root: nil) root ||= File.dirname(caller_locations(1, 1).first.path) root = File.(root) cfg = Config.read_config(root) loaded = Runner.load_steps(cfg.steps, root) store = Runner.create_file_baseline_store(root) update = %w[1 true].include?(ENV.fetch('VARAR_UPDATE', nil)) oaths = Runner.find_oaths(cfg.docs_include, cfg.docs_exclude, root) # Run results for the language server (ADR 0014). RSpec has no per-oath # completion hook here, so each group flushes its own oath in after(:all). results = Runner::Results.new # Drop baselines for oaths the config no longer discovers. Reconciliation is # per-oath and never sees a path that has gone, so the lock would otherwise # accumulate dead entries forever (#70). Once per run, keyed off the config # globs — which here IS the full set, since generate always discovers # everything. Core::Drifts.prune_baselines(store, oaths.map { |p| Runner.rel_posix(p, root) }, update: update) workspace = project_workspace(oaths, root) oaths.each do |oath_path| define_group(oath_path, root, loaded, store, update, results, workspace) end end |
.project_workspace(oaths, root) ⇒ Object
59 60 61 62 63 64 65 66 |
# File 'lib/varar/rspec.rb', line 59 def project_workspace(oaths, root) docs = oaths.filter_map do |path| Core::Parse.parse(Runner.rel_posix(path, root), File.read(path, encoding: 'UTF-8')) rescue SystemCallError nil end Core::Reference.build_workspace(docs) end |
.referenced_sources(plan, workspace) ⇒ Object
Whether a section is a standalone example depends on whether another oath references it, which is whole-project knowledge (ADR 0016). Built from the config globs — the full set, for the same reason baseline pruning is. The sources of every oath this plan's steps were spliced in from (ADR 0016). Their hashes go in the run record, so a consumer can tell a stale failure from a live one.
52 53 54 55 56 57 |
# File 'lib/varar/rspec.rb', line 52 def referenced_sources(plan, workspace) plan.examples.flat_map { |ex| ex.steps.map(&:doc_path) }.compact.uniq.each_with_object({}) do |path, out| doc = workspace.docs[path] out[path] = doc.source if doc end end |
.var_diff_error?(error) ⇒ Boolean
118 119 120 121 |
# File 'lib/varar/rspec.rb', line 118 def var_diff_error?(error) error.is_a?(Core::CellMismatchError) || error.is_a?(Core::ReturnShapeError) || error.is_a?(Core::UnexpectedPassError) end |