Module: Hecks::Fuzzing::IsolatedBoot
- Defined in:
- lib/hecks/fuzzing/isolated_boot.rb
Overview
A FRESH, IN-PROCESS ADAPTER FOR EVERY EPHEMERAL BOOT.
Fuzzing/replay copies a domain to a tmpdir and boots from there
specifically to get zero-history state — rm_rfing the copy's own
data/ achieves that for a file-based adapter (Memory,
SqlitePersistence, Heki), because copying the DIRECTORY copies the
store. It achieves nothing for an adapter that lives outside the
copied directory entirely — Postgres, named by a fixed connection
string in .world (examples/pizzas/bluebook/pizzas.world, for
instance). Copying the directory does not copy or isolate the
DATABASE, so a "fresh" boot against a Postgres-bound domain would
still see every record any other run, ever, wrote to it.
So every .hecksagon in the copy gets its persistence binding
rewritten to Memory before booting, and any projected_by bind
dropped outright (optional — Registry#read_repository already
falls back to the authoritative repository when none exists). The
domain's own rules and shape are untouched ; only WHICH adapter this
one ephemeral copy answers through changes. What the domain is
bound to for real deployment is never touched — only this tmp copy.
Class Method Summary collapse
- .call(domain_path) ⇒ Object
-
.copy_dereferencing(source, destination) ⇒ Object
SYMLINKS ARE FOLLOWED, NOT COPIED.
- .rebind_to_memory!(copy) ⇒ Object
Class Method Details
.call(domain_path) ⇒ Object
29 30 31 32 33 34 35 36 37 |
# File 'lib/hecks/fuzzing/isolated_boot.rb', line 29 def call(domain_path) Dir.mktmpdir("hecks-fuzz") do |tmp| copy = File.join(tmp, File.basename(domain_path)) copy_dereferencing(domain_path, copy) FileUtils.rm_rf(File.join(copy, "data")) rebind_to_memory!(copy) yield copy end end |
.copy_dereferencing(source, destination) ⇒ Object
SYMLINKS ARE FOLLOWED, NOT COPIED. FileUtils.cp_r reproduces a
symlink AS a symlink, and a RELATIVE one then points at nothing
from a tmpdir — lib/hecks/framework/bluebook/compliance .bluebook is exactly that, a link to
examples/compliance/bluebook/compliance.bluebook, so the whole
framework domain failed to boot here with a LoadError naming a
path under /var/folders that had never existed. Nothing about the
domain was wrong; the copy was.
FileUtils.cp follows a symlink and copies its CONTENT, which is
what an isolated boot wants: the copy has to stand alone, since
rebind_to_memory! rewrites files in it and must not reach back
through a link into the real tree.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/hecks/fuzzing/isolated_boot.rb', line 52 def copy_dereferencing(source, destination) FileUtils.mkdir_p(destination) Dir.glob(File.join(source, "**", "*"), File::FNM_DOTMATCH).each do |path| next if [".", ".."].include?(File.basename(path)) target = File.join(destination, path.delete_prefix("#{source}/")) if File.directory?(path) FileUtils.mkdir_p(target) else FileUtils.mkdir_p(File.dirname(target)) FileUtils.cp(path, target) end end end |
.rebind_to_memory!(copy) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/hecks/fuzzing/isolated_boot.rb', line 67 def rebind_to_memory!(copy) Dir.glob(File.join(copy, "**", "*.hecksagon")).each do |path| # `persisted_by`/`projected_by` can be spelled two ways now — # aggregate-scoped (`Banking::Customer.persisted_by("Heki")`, # always parenthesised) and, since §0's domain-level default # binds, a bare call at the hecksagon's own root # (`persisted_by "Heki"`, no parens, no receiver). Both must # be caught here or a domain that only declares the bare form # keeps its real adapter under an "isolated" fuzz boot. lines = File.readlines(path).reject { |line| line.match?(/\bprojected_by\s*\(?\s*"/) } File.write(path, lines.join.gsub(/persisted_by\s*\(?\s*"[^"]+"\s*\)?/, 'persisted_by("Memory")')) end # THE SETTINGS, NOT JUST THE BIND — `WorldBuilder#method_missing` # stores a settings block under BOTH "verb:adapter" and the bare # "verb" (world_builder.rb:32-33), so a bind rewritten to Memory # still falls back to whatever adapter's settings were declared # bare — Postgres's `database:`, which Memory does not take and # WiringError refuses on sight. Memory needs no settings at all # (a domain with no .world boots it as the default), so the # simplest correct fix is dropping .world from the copy entirely. Dir.glob(File.join(copy, "**", "*.world")).each { |path| File.delete(path) } end |