Class: Hecks::Fuzzing::SequenceGenerator
- Inherits:
-
Object
- Object
- Hecks::Fuzzing::SequenceGenerator
- Includes:
- Catalog, OutcomeTracker, Picker, StepBuilder
- Defined in:
- lib/hecks/fuzzing/sequence_generator.rb,
lib/hecks/fuzzing/sequence_generator/picker.rb,
lib/hecks/fuzzing/sequence_generator/catalog.rb,
lib/hecks/fuzzing/sequence_generator/step_builder.rb,
lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb
Overview
A random-but-valid sequence of dispatches and queries, in the exact
{name, note, steps} shape spec/corpus/*.json already uses — so a
generated sequence replays as a corpus member, completely unchanged.
Boots a throwaway copy of
the domain and DISPATCHES each candidate step for real as it builds the
sequence (not just synthesizing plausible-looking JSON) : the only way to
know whether a step actually reached a new state, or which id an
auto-minted entity landed on, is to run it and watch what happened.
Single-call fuzzing mostly misses the bugs this project has actually found — they needed STATE first (a saga leg acting on a transfer that already exists, a reference pointing at a customer already registered). So this tracks what it has created as it goes, the same way spec/banking_state_machine_spec.rb's hand-written generator does, and weights later steps toward acting on what already exists.
One concern per file beside this one: what the domain offers (sequence_generator/catalog.rb), which step to try next (picker.rb), how a step is built and dispatched (step_builder.rb), and what a success taught us (outcome_tracker.rb).
Defined Under Namespace
Modules: Catalog, OutcomeTracker, Picker, StepBuilder
Constant Summary collapse
- CREATING_WEIGHT =
Creating commands are always eligible ; weighting them heavier (not exclusively — a domain with only one or two aggregates would starve everything else) makes a sequence reach an actionable state sooner instead of spending its early budget refusing "acts on nothing yet."
2- MALFORMED_ARGUMENT_PROBABILITY =
How often a payload is deliberately the wrong shape. Low, because a refused step reaches no new state and a sequence of them is SILENT.
0.12- OPTIONAL_OMITTED_PROBABILITY =
HOW OFTEN AN OPTIONAL ARGUMENT IS SIMPLY NOT GIVEN — a fair coin, because that is exactly what
optional:means: present or absent, both legal, neither the interesting case.This is NOT a malformation and must not be filed as one.
malformdrops an argument too, but a dropped REQUIRED argument is refused, a refusal writes no record, and bin/fuzz counts it SILENT — so the one outcome worth reaching (a stored record carrying a null, then QUERIED) was unreachable from that path by construction. Until this existed no generated history contained a null at all, which meantquery_answers_match_reference— the differential that diffs every native adapter against the reference interpreter — had never once compared a nullable field. Four query bugs shipped in exactly that blind spot (ne:with an empty string, arrayin:,ne:against a null,inon a numeric field); the adapter agreement gate had the identical hole and found a fourth bug the hour it was closed. 0.5- UNEXERCISED_WEIGHT =
How strongly an unexercised verb is preferred over one this sequence has already dispatched. Random picking revisits the same handful of verbs and leaves whole commands untouched for a whole run — which is the same "reached no interesting state" problem the SILENT count reports, seen from the generating end rather than the scoring end.
4
Instance Attribute Summary collapse
-
#event_count ⇒ Object
readonly
How many EVENTS the generated sequence actually produced — not steps, not successful dispatches, but the sum of every Result#events length across the run.
Class Method Summary collapse
Instance Method Summary collapse
- #call ⇒ Object
-
#initialize(domain_path, seed:, steps:) ⇒ SequenceGenerator
constructor
A new instance of SequenceGenerator.
Constructor Details
#initialize(domain_path, seed:, steps:) ⇒ SequenceGenerator
Returns a new instance of SequenceGenerator.
90 91 92 93 94 95 96 97 98 99 |
# File 'lib/hecks/fuzzing/sequence_generator.rb', line 90 def initialize(domain_path, seed:, steps:) @domain_path = domain_path @seed = seed @step_count = steps @random = Random.new(seed) @known_ids = Hash.new { |h, k| h[k] = [] } @entity_known_ids = Hash.new { |h, k| h[k] = [] } @exercised = Set.new @event_count = 0 end |
Instance Attribute Details
#event_count ⇒ Object (readonly)
How many EVENTS the generated sequence actually produced — not
steps, not successful dispatches, but the sum of every Result#events
length across the run. This is the count bin/fuzz declares as the
script's own expectations.events claim: whatever was achieved
DURING generation becomes the claim a fresh replay of the same
script is held to. Zero means the sequence never
reached an interesting state — a fuzzer-effectiveness fact, not a
replay one.
88 89 90 |
# File 'lib/hecks/fuzzing/sequence_generator.rb', line 88 def event_count @event_count end |
Class Method Details
.generate(domain_path, seed:, steps:) ⇒ Object
76 77 78 |
# File 'lib/hecks/fuzzing/sequence_generator.rb', line 76 def self.generate(domain_path, seed:, steps:) new(domain_path, seed: seed, steps: steps).call end |
Instance Method Details
#call ⇒ Object
101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/hecks/fuzzing/sequence_generator.rb', line 101 def call # Real leftover data from ordinary use (bin/console, whatever) lives # under the example's data/ — a generator that boots against it # starts from state its own known_ids tracking doesn't know about. # IsolatedBoot resets that AND rebinds persistence to Memory, since # a Postgres-bound domain's real store lives outside the copied # directory entirely and `rm_rf`ing data/ alone cannot reach it — # see isolated_boot.rb's own header. IsolatedBoot.call(@domain_path) do |copy| runtime = Hecks.boot(copy) catalog = build_catalog(runtime) Array.new(@step_count) { attempt_step(runtime, catalog) }.compact end end |