hecks 1.4.0 → 1.5.1
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/lib/hecks/adapters/driven/claude_code.rb +65 -0
- data/lib/hecks/adapters/driven/folder.rb +73 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
- data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +93 -2
- data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
- data/lib/hecks/adapters/driven/lambda.rb +43 -2
- data/lib/hecks/adapters/driven/local_storage.rb +67 -1
- data/lib/hecks/adapters/driven/memory.rb +13 -13
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
- data/lib/hecks/adapters/driven/prism.rb +46 -7
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +2 -0
- data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
- data/lib/hecks/behaviors/dsl.rb +58 -0
- data/lib/hecks/behaviors/expectations.rb +168 -7
- data/lib/hecks/behaviors/ir.rb +11 -0
- data/lib/hecks/behaviors/rspec.rb +8 -0
- data/lib/hecks/behaviors/runner.rb +19 -0
- data/lib/hecks/behaviors.rb +8 -0
- data/lib/hecks/bluebook/aggregate.rb +30 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
- data/lib/hecks/bluebook/assembly/build.rb +25 -4
- data/lib/hecks/bluebook/assembly/contract.rb +66 -14
- data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
- data/lib/hecks/bluebook/assembly/marks.rb +143 -14
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
- data/lib/hecks/bluebook/assembly.rb +25 -7
- data/lib/hecks/bluebook/attribute.rb +17 -3
- data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
- data/lib/hecks/bluebook/behaviour/command.rb +33 -3
- data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
- data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
- data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
- data/lib/hecks/bluebook/behaviour/query.rb +5 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
- data/lib/hecks/bluebook/chapter.rb +23 -0
- data/lib/hecks/bluebook/command.rb +53 -8
- data/lib/hecks/bluebook/domain_port.rb +25 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
- data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
- data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
- data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
- data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
- data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
- data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
- data/lib/hecks/bluebook/entity.rb +29 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
- data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
- data/lib/hecks/bluebook/expression/resolver.rb +320 -77
- data/lib/hecks/bluebook/hexagon.rb +34 -0
- data/lib/hecks/bluebook/lifecycle.rb +11 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
- data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
- data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator.rb +180 -84
- data/lib/hecks/bluebook/model_check.rb +268 -24
- data/lib/hecks/bluebook/pattern_subset.rb +23 -1
- data/lib/hecks/bluebook/process_manager.rb +13 -0
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +44 -0
- data/lib/hecks/bluebook/query.rb +27 -0
- data/lib/hecks/bluebook/read_model.rb +21 -1
- data/lib/hecks/bluebook/reference.rb +21 -8
- data/lib/hecks/bluebook/smoke_test.rb +29 -6
- data/lib/hecks/bluebook/synthesizer.rb +34 -0
- data/lib/hecks/bluebook/translation.rb +30 -1
- data/lib/hecks/bluebook/value_object.rb +23 -5
- data/lib/hecks/bluebook.rb +3 -4
- data/lib/hecks/codemod.rb +107 -20
- data/lib/hecks/construct.rb +15 -1
- data/lib/hecks/corpus.rb +146 -25
- data/lib/hecks/deploy/bluebook/deploy.bluebook +105 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/deploy/oidc.json +5 -0
- data/lib/hecks/doc/reference.rb +185 -16
- data/lib/hecks/embryonaut_bluebook.rb +32 -9
- data/lib/hecks/facade/handle.rb +76 -3
- data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
- data/lib/hecks/forms/field_shape.rb +3 -0
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/port_argument.rb +12 -0
- data/lib/hecks/forms/query_form_renderer.rb +63 -0
- data/lib/hecks/forms/record_renderer.rb +58 -0
- data/lib/hecks/forms/record_table.rb +27 -0
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +10 -0
- data/lib/hecks/fqn.rb +58 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +221 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +43 -20
- data/lib/hecks/freezer.rb +17 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
- data/lib/hecks/fuzzing/combination_miner.rb +59 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
- data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
- data/lib/hecks/fuzzing/differential.rb +34 -0
- data/lib/hecks/fuzzing/domain_generator.rb +188 -11
- data/lib/hecks/fuzzing/era_boundary.rb +45 -15
- data/lib/hecks/fuzzing/form_census.rb +86 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
- data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
- data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
- data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
- data/lib/hecks/fuzzing/properties/guards.rb +44 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
- data/lib/hecks/fuzzing/properties/querying.rb +68 -14
- data/lib/hecks/fuzzing/properties.rb +24 -15
- data/lib/hecks/fuzzing/qa_settings.rb +12 -0
- data/lib/hecks/fuzzing/replay.rb +137 -29
- data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
- data/lib/hecks/fuzzing/self_consistency.rb +189 -40
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
- data/lib/hecks/fuzzing/shrinker.rb +114 -2
- data/lib/hecks/fuzzing/structural_skips.rb +18 -4
- data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
- data/lib/hecks/fuzzing/value_generator.rb +98 -10
- data/lib/hecks/grammar/evolve.rb +178 -2
- data/lib/hecks/grammar.rb +46 -0
- data/lib/hecks/ir.rb +38 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/literal.rb +32 -0
- data/lib/hecks/naming.rb +88 -7
- data/lib/hecks/ports/access_control.rb +5 -10
- data/lib/hecks/ports/authorization.rb +3 -6
- data/lib/hecks/ports/identity_assignment.rb +1 -2
- data/lib/hecks/ports/identity_resolution.rb +1 -2
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +85 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
- data/lib/hecks/ports/query/in_memory.rb +39 -6
- data/lib/hecks/ports/query/ordering.rb +15 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +43 -8
- data/lib/hecks/projections/deploy/fargate.rb +666 -0
- data/lib/hecks/projections/deploy/lambda.rb +2423 -0
- data/lib/hecks/projections/deploy/shared.rb +624 -0
- data/lib/hecks/projections/diagrams.rb +243 -7
- data/lib/hecks/projections/glossary/html.rb +88 -0
- data/lib/hecks/projections/glossary/markdown.rb +47 -0
- data/lib/hecks/projections/glossary/mermaid.rb +48 -0
- data/lib/hecks/projections/glossary/sentences.rb +105 -0
- data/lib/hecks/projections/glossary.rb +161 -19
- data/lib/hecks/projections/model/deviations.rb +44 -0
- data/lib/hecks/projections/model.rb +51 -1
- data/lib/hecks/projections/oidc.rb +40 -3
- data/lib/hecks/projections/parser_table.rb +49 -8
- data/lib/hecks/projections/reference.rb +12 -0
- data/lib/hecks/projections/rust_vocabulary.rb +219 -16
- data/lib/hecks/projections/shape.rb +8 -0
- data/lib/hecks/projections/statements.rb +63 -16
- data/lib/hecks/projections/vocabulary.rb +17 -0
- data/lib/hecks/projections.rb +3 -0
- data/lib/hecks/projector/cli_projector.rb +218 -10
- data/lib/hecks/projector/docs_projector.rb +145 -19
- data/lib/hecks/projector/exporter.rb +65 -11
- data/lib/hecks/projector/ir_projector.rb +6 -0
- data/lib/hecks/projector/narrate_projector.rb +136 -15
- data/lib/hecks/projector/target.rb +47 -10
- data/lib/hecks/projector.rb +101 -19
- data/lib/hecks/query_ir.rb +47 -0
- data/lib/hecks/query_specification/common/null_policy.rb +5 -3
- data/lib/hecks/rendering.rb +6 -0
- data/lib/hecks/router/namespace_installer.rb +13 -0
- data/lib/hecks/router.rb +55 -0
- data/lib/hecks/runtime/aggregate_lock.rb +9 -0
- data/lib/hecks/runtime/boot_gates.rb +18 -0
- data/lib/hecks/runtime/caller.rb +32 -0
- data/lib/hecks/runtime/capability_graph.rb +11 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
- data/lib/hecks/runtime/command_interpreter.rb +42 -17
- data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
- data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
- data/lib/hecks/runtime/command_rules/references.rb +118 -28
- data/lib/hecks/runtime/dependency_planning.rb +45 -0
- data/lib/hecks/runtime/dispatcher.rb +28 -50
- data/lib/hecks/runtime/entity_element.rb +161 -8
- data/lib/hecks/runtime/entity_interpreter.rb +44 -9
- data/lib/hecks/runtime/errors.rb +18 -4
- data/lib/hecks/runtime/event.rb +10 -5
- data/lib/hecks/runtime/identity.rb +71 -3
- data/lib/hecks/runtime/instance.rb +67 -7
- data/lib/hecks/runtime/interpreting.rb +13 -5
- data/lib/hecks/runtime/invocation.rb +118 -36
- data/lib/hecks/runtime/loader.rb +94 -8
- data/lib/hecks/runtime/outbox.rb +145 -7
- data/lib/hecks/runtime/policy_interpreter.rb +22 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
- data/lib/hecks/runtime/query_interpreter.rb +40 -12
- data/lib/hecks/runtime/reaction_invocation.rb +53 -8
- data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
- data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
- data/lib/hecks/runtime/reference_hop.rb +42 -0
- data/lib/hecks/runtime/refusal_wording.rb +50 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
- data/lib/hecks/runtime/registry/verification.rb +119 -4
- data/lib/hecks/runtime/registry.rb +157 -4
- data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
- data/lib/hecks/runtime/routing.rb +27 -2
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
- data/lib/hecks/runtime/saga_interpreter.rb +27 -13
- data/lib/hecks/runtime/tenant_check.rb +26 -6
- data/lib/hecks/runtime/tenant_scope.rb +18 -0
- data/lib/hecks/runtime/value/coercion.rb +255 -33
- data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
- data/lib/hecks/runtime/value.rb +50 -0
- data/lib/hecks/runtime.rb +32 -0
- data/lib/hecks/storehouse.rb +305 -9
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +79 -1
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
- metadata +14 -5
- data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
- data/lib/hecks/deprecation.rb +0 -95
- data/lib/hecks/framework/bluebook/compliance.bluebook +0 -1
|
@@ -18,7 +18,9 @@ module Hecks
|
|
|
18
18
|
# domain, so the practice's own adversarial generator gets to pick the
|
|
19
19
|
# conflict instead of a human picking it once and never again.
|
|
20
20
|
#
|
|
21
|
-
# The mechanism
|
|
21
|
+
# ## The mechanism
|
|
22
|
+
#
|
|
23
|
+
# Generalized from that spec rather than rederived: a
|
|
22
24
|
# command step partway through a generated sequence is chosen as the
|
|
23
25
|
# race step; every step before it is setup (replayed once, sequentially,
|
|
24
26
|
# to bring a fresh disposable schema to the state the race step expects
|
|
@@ -26,7 +28,9 @@ module Hecks
|
|
|
26
28
|
# each of two real, separate, forked OS processes racing against that
|
|
27
29
|
# same schema, no artificial gating, whichever the scheduler favors.
|
|
28
30
|
#
|
|
29
|
-
# The oracle
|
|
31
|
+
# ## The oracle
|
|
32
|
+
#
|
|
33
|
+
# The same pair, dispatched sequentially, not a
|
|
30
34
|
# hardcoded expectation — unlike the hand-authored spec (which can
|
|
31
35
|
# assert `%w[refused succeeded]` because it knows its own fixture's
|
|
32
36
|
# business rule), this module has no idea whether an arbitrary
|
|
@@ -42,7 +46,9 @@ module Hecks
|
|
|
42
46
|
# (which real racer wins a genuine race is never controlled), the set
|
|
43
47
|
# of outcomes does.
|
|
44
48
|
#
|
|
45
|
-
# What a broken lock looks like here
|
|
49
|
+
# ## What a broken lock looks like here
|
|
50
|
+
#
|
|
51
|
+
# The concurrent pair settling as
|
|
46
52
|
# {"succeeded", "succeeded"} where the sequential oracle says
|
|
47
53
|
# {"succeeded", "refused"} — two processes each hydrated the
|
|
48
54
|
# pre-write state, neither saw the other's write, and the second
|
|
@@ -51,7 +57,9 @@ module Hecks
|
|
|
51
57
|
# PostgresEra and `postgres_concurrent_dispatch_spec.rb` still
|
|
52
58
|
# documents, unfixed, for plain Postgres.
|
|
53
59
|
#
|
|
54
|
-
# Not a replacement for the hand-authored spec
|
|
60
|
+
# ## Not a replacement for the hand-authored spec
|
|
61
|
+
#
|
|
62
|
+
# That spec proves the
|
|
55
63
|
# mechanism once, precisely, with controlled gating so the assertion
|
|
56
64
|
# is deterministic; this module proves the same mechanism holds for
|
|
57
65
|
# whatever a real domain's own generated sequences throw at it, with
|
|
@@ -75,6 +83,15 @@ module Hecks
|
|
|
75
83
|
# duration — the caller creates neither ahead of time (both are
|
|
76
84
|
# wiped fresh by the boots below) and drops both afterward, the
|
|
77
85
|
# same lifecycle `persistence_parity_schema` already has.
|
|
86
|
+
#
|
|
87
|
+
# @param domain_path [String] filesystem path to the target domain's directory
|
|
88
|
+
# @param steps [Array<Hash>] a generated sequence's own steps, any-keyed (normalized
|
|
89
|
+
# to String keys internally)
|
|
90
|
+
# @param database [String] the shared, never-dropped scratch database to connect to
|
|
91
|
+
# @param race_schema [String] a disposable schema name for the two racing processes
|
|
92
|
+
# @param reference_schema [String] a disposable schema name for the sequential oracle
|
|
93
|
+
# @return [Array<Hash>] `[]` if nothing raceable was found or no divergence occurred;
|
|
94
|
+
# otherwise one or more `field:`-keyed divergence findings
|
|
78
95
|
def check(domain_path, steps, database:, race_schema:, reference_schema:)
|
|
79
96
|
normalized = steps.map { |step| step.transform_keys(&:to_s) }
|
|
80
97
|
lockable, probe_errors = lockable_verbs(domain_path, normalized, database: database, schema: reference_schema)
|
|
@@ -82,9 +99,9 @@ module Hecks
|
|
|
82
99
|
unless race_index
|
|
83
100
|
# `[]` here means "nothing to race", and must only ever mean that.
|
|
84
101
|
# A sequence of pure queries/dry-runs is a legitimate clean
|
|
85
|
-
# result; a probe that raised for every verb is not —
|
|
86
|
-
#
|
|
87
|
-
#
|
|
102
|
+
# result; a probe that raised for every verb is not — see
|
|
103
|
+
# `lockable_verbs`, whose own `probe_errors` half exists so this
|
|
104
|
+
# branch can tell the two apart rather than answering `[]` for both.
|
|
88
105
|
return [] if probe_errors.empty?
|
|
89
106
|
|
|
90
107
|
return [{ field: "concurrency_unraceable",
|
|
@@ -103,7 +120,7 @@ module Hecks
|
|
|
103
120
|
[{ field: "process", detail: "#{e.class}: #{e.message}" }]
|
|
104
121
|
end
|
|
105
122
|
|
|
106
|
-
# The command step
|
|
123
|
+
# The command step closest to the middle of the sequence — not the
|
|
107
124
|
# first (racing a bare identity-creation with no setup at all is a
|
|
108
125
|
# legitimate, useful case, so index 0 is not excluded) and not
|
|
109
126
|
# chosen for any domain-specific reason: a mid-sequence step has, on
|
|
@@ -120,6 +137,12 @@ module Hecks
|
|
|
120
137
|
# Racing anything outside that set is not a legitimate race at
|
|
121
138
|
# all — nil when nothing eligible is left, same as the "no command
|
|
122
139
|
# step" case, never a finding of its own.
|
|
140
|
+
#
|
|
141
|
+
# @param steps [Array<Hash>] a normalized (String-keyed) generated sequence
|
|
142
|
+
# @param lockable_verbs [Array<String>, nil] the verbs eligible to race, as
|
|
143
|
+
# `lockable_verbs` returns; nil to consider every command step eligible
|
|
144
|
+
# @return [Integer, nil] the index of the chosen race step, or nil if no eligible
|
|
145
|
+
# command step exists
|
|
123
146
|
def pick_race_index(steps, lockable_verbs = nil)
|
|
124
147
|
command_indices = steps.each_index.select { |i| COMMAND_STEP.call(steps[i]) }
|
|
125
148
|
command_indices = command_indices.select { |i| lockable_verbs.include?(steps[i]["verb"]) } if lockable_verbs
|
|
@@ -170,6 +193,14 @@ module Hecks
|
|
|
170
193
|
# `lockable` came back empty, `check` returned `[]`, and the sweep
|
|
171
194
|
# logged a clean concurrency Check for a race that never happened.
|
|
172
195
|
# `check` reports that case now instead of holding it.
|
|
196
|
+
#
|
|
197
|
+
# @param domain_path [String] filesystem path to the target domain's directory
|
|
198
|
+
# @param steps [Array<Hash>] a normalized (String-keyed) generated sequence
|
|
199
|
+
# @param database [String] the shared, never-dropped scratch database to connect to
|
|
200
|
+
# @param schema [String] a disposable schema name for the structural-inspection boot
|
|
201
|
+
# @return [Array(Array<String>, Array<String>)] `[lockable, probe_errors]` — the
|
|
202
|
+
# command verbs bound to a `:cross_process_lock`-declaring adapter, and one
|
|
203
|
+
# `"verb: Class: message"` entry per verb whose resolution raised
|
|
173
204
|
def lockable_verbs(domain_path, steps, database:, schema:)
|
|
174
205
|
verbs = steps.select { |step| COMMAND_STEP.call(step) }.map { |step| step["verb"] }.uniq
|
|
175
206
|
lockable = []
|
|
@@ -183,6 +214,15 @@ module Hecks
|
|
|
183
214
|
[lockable, probe_errors]
|
|
184
215
|
end
|
|
185
216
|
|
|
217
|
+
# Answers whether `verb`'s own resolved repository declares a cross-process lock.
|
|
218
|
+
#
|
|
219
|
+
# @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted,
|
|
220
|
+
# structural-inspection-only runtime to resolve `verb` against
|
|
221
|
+
# @param verb [String] the command verb to check
|
|
222
|
+
# @param probe_errors [Array<String>] mutated in place: appended with
|
|
223
|
+
# `"verb: Class: message"` if resolving `verb` raises
|
|
224
|
+
# @return [Boolean] whether `verb`'s aggregate resolves and declares
|
|
225
|
+
# `:cross_process_lock`; false (with an appended probe error) if resolution raises
|
|
186
226
|
def verb_cross_process_lockable?(runtime, verb, probe_errors = [])
|
|
187
227
|
domain, aggregate_name, = Naming.split_verb(verb)
|
|
188
228
|
return false unless domain && aggregate_name
|
|
@@ -200,6 +240,17 @@ module Hecks
|
|
|
200
240
|
false
|
|
201
241
|
end
|
|
202
242
|
|
|
243
|
+
# Compares the sequential oracle's outcomes against the two racing
|
|
244
|
+
# processes' own outcomes, and reports any crash or disagreement.
|
|
245
|
+
#
|
|
246
|
+
# @param race_step [Hash] the race step, String-keyed
|
|
247
|
+
# @param reference [Array<String>] the sequential oracle's two outcomes,
|
|
248
|
+
# `"succeeded"`, `"refused"`, or `"crashed:..."`
|
|
249
|
+
# @param concurrent [Array<String>] the two racing processes' own outcomes,
|
|
250
|
+
# the same shape as `reference`
|
|
251
|
+
# @return [Array<Hash>] `[]` if the two multisets agree and neither side crashed;
|
|
252
|
+
# otherwise one or more `field:`-keyed divergence findings (`"concurrency_crash"`
|
|
253
|
+
# or `"concurrency_race"`)
|
|
203
254
|
def divergences_for(race_step, reference, concurrent)
|
|
204
255
|
crashes = (reference + concurrent).select { |outcome| outcome.start_with?("crashed:") }.uniq
|
|
205
256
|
return crashes.map { |c| { field: "concurrency_crash", verb: race_step["verb"], detail: c } } if crashes.any?
|
|
@@ -216,6 +267,14 @@ module Hecks
|
|
|
216
267
|
# twice in immediate succession. Nothing else ever touches this
|
|
217
268
|
# schema while this runs, so whatever the domain itself settles on
|
|
218
269
|
# is correct by construction, not asserted.
|
|
270
|
+
#
|
|
271
|
+
# @param domain_path [String] filesystem path to the target domain's directory
|
|
272
|
+
# @param setup_steps [Array<Hash>] the normalized steps replayed once, sequentially,
|
|
273
|
+
# before the race step
|
|
274
|
+
# @param race_step [Hash] the normalized step dispatched twice in immediate succession
|
|
275
|
+
# @param database [String] the shared, never-dropped scratch database to connect to
|
|
276
|
+
# @param schema [String] this oracle's own disposable schema name
|
|
277
|
+
# @return [Array<String>] the two outcomes `dispatch_one` recorded, in dispatch order
|
|
219
278
|
def reference_outcomes(domain_path, setup_steps, race_step, database:, schema:)
|
|
220
279
|
outcomes = []
|
|
221
280
|
IsolatedBoot.call(domain_path, adapter: :postgres_era, database: database, schema: schema) do |copy|
|
|
@@ -256,6 +315,15 @@ module Hecks
|
|
|
256
315
|
# method's own code. `bin/qa_concurrency_racer` is this method's own
|
|
257
316
|
# worker, one real `ruby` process per racer — read that script's own
|
|
258
317
|
# header for the rest of this reasoning.
|
|
318
|
+
#
|
|
319
|
+
# @param domain_path [String] filesystem path to the target domain's directory
|
|
320
|
+
# @param setup_steps [Array<Hash>] the normalized steps replayed once, sequentially,
|
|
321
|
+
# before the two racers spawn
|
|
322
|
+
# @param race_step [Hash] the normalized step both racer processes dispatch
|
|
323
|
+
# @param database [String] the shared, never-dropped scratch database to connect to
|
|
324
|
+
# @param schema [String] this race's own disposable schema name, shared by both racers
|
|
325
|
+
# @return [Array<String>] the two racers' own outcomes, in `Process.wait` order;
|
|
326
|
+
# `"crashed:no output from bin/qa_concurrency_racer"` for a racer that printed nothing
|
|
259
327
|
def concurrent_outcomes(domain_path, setup_steps, race_step, database:, schema:)
|
|
260
328
|
IsolatedBoot.call(domain_path, adapter: :postgres_era, database: database, schema: schema) do |copy|
|
|
261
329
|
dispatch_all!(Hecks.boot(copy), setup_steps)
|
|
@@ -286,6 +354,11 @@ module Hecks
|
|
|
286
354
|
# give), anything else escaping is "crashed:<class>: <message>", a
|
|
287
355
|
# genuine finding this module's own caller surfaces rather than lets
|
|
288
356
|
# kill a forked racer silently.
|
|
357
|
+
#
|
|
358
|
+
# @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
|
|
359
|
+
# to dispatch against
|
|
360
|
+
# @param step [Hash] the normalized (String-keyed) step to dispatch
|
|
361
|
+
# @return [String] `"succeeded"`, `"refused"`, or `"crashed:<class>: <message>"`
|
|
289
362
|
def dispatch_one(runtime, step)
|
|
290
363
|
args = (step["args"] || {}).transform_keys(&:to_sym)
|
|
291
364
|
runtime.dispatch_flat(step["verb"], args)
|
|
@@ -296,12 +369,21 @@ module Hecks
|
|
|
296
369
|
"crashed:#{e.class}: #{e.message}"
|
|
297
370
|
end
|
|
298
371
|
|
|
372
|
+
# Dispatches every step in order, raising the first time one crashes.
|
|
373
|
+
#
|
|
299
374
|
# Setup tolerates an ordinary refusal (a generated sequence's own
|
|
300
375
|
# earlier step can legitimately refuse — every other mode in this
|
|
301
376
|
# practice already replays a prefix that way) but never a crash: an
|
|
302
377
|
# unexpected exception during setup means the schema this race is
|
|
303
378
|
# about to run against is in an unknown state, which is itself
|
|
304
379
|
# worth surfacing, not silently racing anyway.
|
|
380
|
+
#
|
|
381
|
+
# @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted runtime
|
|
382
|
+
# to dispatch against
|
|
383
|
+
# @param steps [Array<Hash>] the normalized steps to dispatch, in order
|
|
384
|
+
# @return [void]
|
|
385
|
+
# @raise [RuntimeError] if any step's own `dispatch_one` outcome is a crash, naming
|
|
386
|
+
# the step's verb and outcome
|
|
305
387
|
def dispatch_all!(runtime, steps)
|
|
306
388
|
steps.each do |step|
|
|
307
389
|
outcome = dispatch_one(runtime, step)
|
|
@@ -309,6 +391,9 @@ module Hecks
|
|
|
309
391
|
end
|
|
310
392
|
end
|
|
311
393
|
|
|
394
|
+
# Boots a `PostgresEra`-rebound copy of the domain against an
|
|
395
|
+
# existing schema, without wiping it first.
|
|
396
|
+
#
|
|
312
397
|
# The same copy-and-rebind `IsolatedBoot.call(..., adapter:
|
|
313
398
|
# :postgres_era, ...)` does, minus the schema wipe — deliberately
|
|
314
399
|
# not reusing `IsolatedBoot.rebind_to_postgres_era!` itself, which
|
|
@@ -322,6 +407,14 @@ module Hecks
|
|
|
322
407
|
# this writes is duplicated from it, not re-derived, because the
|
|
323
408
|
# shape a `PostgresEra`-bound copy's `.world` needs is exactly that
|
|
324
409
|
# method's own, one step short.
|
|
410
|
+
#
|
|
411
|
+
# @param domain_path [String] filesystem path to the target domain's directory
|
|
412
|
+
# @param database [String] the PostgresEra database name to bind the copy to
|
|
413
|
+
# @param schema [String] the existing PostgresEra schema name to bind the copy to,
|
|
414
|
+
# left exactly as found
|
|
415
|
+
# @yield [copy] runs against the rebound copy
|
|
416
|
+
# @yieldparam copy [String] filesystem path to the rebound domain copy
|
|
417
|
+
# @return [Object] the block's own result
|
|
325
418
|
def boot_preserving_schema(domain_path, database:, schema:)
|
|
326
419
|
Dir.mktmpdir("hecks-concurrency") do |tmp|
|
|
327
420
|
copy = File.join(tmp, File.basename(domain_path))
|
|
@@ -333,6 +426,14 @@ module Hecks
|
|
|
333
426
|
end
|
|
334
427
|
end
|
|
335
428
|
|
|
429
|
+
# Writes a `.world` file binding every hecksagon-named member in
|
|
430
|
+
# `copy` to `database`/`schema`, replacing any other `.world` file
|
|
431
|
+
# the copy already carries.
|
|
432
|
+
#
|
|
433
|
+
# @param copy [String] filesystem path to the domain copy to rewrite
|
|
434
|
+
# @param database [String] the PostgresEra database name to write into the `.world` file
|
|
435
|
+
# @param schema [String] the PostgresEra schema name to write into the `.world` file
|
|
436
|
+
# @return [void]
|
|
336
437
|
def write_postgres_era_world!(copy, database:, schema:)
|
|
337
438
|
Dir.glob(File.join(copy, "**", "*.hecksagon")).each do |hecksagon_path|
|
|
338
439
|
names = File.read(hecksagon_path).scan(/Hecks\.hecksagon\s+"([^"]+)"/).flatten.uniq
|
|
@@ -3,23 +3,27 @@ module Hecks
|
|
|
3
3
|
# What one sweep has already reached, and where the next seed starts.
|
|
4
4
|
#
|
|
5
5
|
# `SequenceGenerator` steers within one sequence (an unexercised verb is
|
|
6
|
-
# weighted up — picker.rb's `steer`), but every seed of a
|
|
7
|
-
# start from nothing and forget everything the seeds before
|
|
8
|
-
# Roughly half of the QA ledger's bugs are refusal-kind splits (BUG#2–4,
|
|
6
|
+
# weighted up — picker.rb's `steer`), but without this, every seed of a
|
|
7
|
+
# sweep would start from nothing and forget everything the seeds before
|
|
8
|
+
# it reached. Roughly half of the QA ledger's bugs are refusal-kind splits (BUG#2–4,
|
|
9
9
|
# 7, 8, 14–16, 19–21, 23, 27, 28, 36–38, 41, 54, 56): a particular verb,
|
|
10
10
|
# in a particular state, with a particular malformed argument, refused
|
|
11
11
|
# one way on Ruby and another on Rust. BUG#11 was found by hand because
|
|
12
12
|
# a 25-step random walk never reached `Annotate` at all. Uniform seeds
|
|
13
13
|
# reach those corners by luck; this makes the sweep remember.
|
|
14
14
|
#
|
|
15
|
-
# The coverage unit
|
|
16
|
-
# lifecycle state before | adversarial mutation | outcome` (see
|
|
17
|
-
# `SequenceGenerator#coverage_tuple`). "Renew refused LifecycleRefused
|
|
18
|
-
# from free" and "Renew ok from held" are different places a runtime can
|
|
19
|
-
# be wrong, where a verb-level count calls them the same.
|
|
15
|
+
# ## The coverage unit
|
|
20
16
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
17
|
+
# A tuple, not a verb — `verb | step kind | lifecycle state before |
|
|
18
|
+
# adversarial mutation | outcome` (see `SequenceGenerator#coverage_tuple`).
|
|
19
|
+
# "Renew refused LifecycleRefused from free" and "Renew ok from held" are
|
|
20
|
+
# different places a runtime can be wrong, where a verb-level count
|
|
21
|
+
# calls them the same.
|
|
22
|
+
#
|
|
23
|
+
# ## Two levers
|
|
24
|
+
#
|
|
25
|
+
# Both expressed as plain generator arguments so a seed stays
|
|
26
|
+
# reproducible from one call:
|
|
23
27
|
#
|
|
24
28
|
# 1. Splicing (`prefix:`). A sequence that reached a new tuple joins
|
|
25
29
|
# the corpus, cut at its last new tuple. A later seed may start by
|
|
@@ -32,18 +36,37 @@ module Hecks
|
|
|
32
36
|
# including declared verbs it has never hit at all — are weighted
|
|
33
37
|
# up for the next seed.
|
|
34
38
|
#
|
|
39
|
+
# ## Reproducibility
|
|
40
|
+
#
|
|
35
41
|
# Nothing here is random at the campaign's own level beyond
|
|
36
42
|
# `Random.new(seed)` per plan, and a plan is printed with every finding
|
|
37
43
|
# (`bin/qa_sweep`'s `reproduce:` line), so no finding depends on
|
|
38
44
|
# re-running the whole campaign to get back to it.
|
|
39
45
|
class CoverageCampaign
|
|
46
|
+
# One seed's generation instructions, printable and reproducible.
|
|
40
47
|
Plan = Struct.new(:prefix, :favor, keyword_init: true) do
|
|
48
|
+
# Renders this plan as the keyword arguments a generator call accepts.
|
|
49
|
+
#
|
|
50
|
+
# @return [Hash{Symbol => Object}] `{ prefix:, favor: }`, the keyword arguments
|
|
51
|
+
# `SequenceGenerator.generate` and `.trace` accept for this plan
|
|
41
52
|
def generator_options = { prefix: prefix, favor: favor }
|
|
53
|
+
|
|
54
|
+
# Answers whether this plan starts from a spliced corpus prefix.
|
|
55
|
+
#
|
|
56
|
+
# @return [Boolean] whether this plan starts from a spliced corpus prefix
|
|
42
57
|
def spliced? = !prefix.nil?
|
|
43
58
|
end
|
|
44
59
|
|
|
45
60
|
attr_reader :corpus
|
|
46
61
|
|
|
62
|
+
# @param splice_probability [Float, Integer] chance, per seed with a non-empty
|
|
63
|
+
# corpus, of splicing a corpus entry's prefix instead of generating fresh
|
|
64
|
+
# @param favor_count [Integer, Float] how many rare verbs to favor per plan; 0
|
|
65
|
+
# or non-positive disables favoring entirely
|
|
66
|
+
# @param corpus_limit [Integer] maximum corpus entries kept; the oldest is
|
|
67
|
+
# dropped once this is exceeded
|
|
68
|
+
# @param max_prefix_depth [Integer] maximum nested-prefix depth a new corpus
|
|
69
|
+
# entry may reach before splicing stops extending it further
|
|
47
70
|
def initialize(splice_probability:, favor_count:, corpus_limit: 64, max_prefix_depth: 6)
|
|
48
71
|
@splice_probability = splice_probability.to_f
|
|
49
72
|
@favor_count = favor_count.to_i
|
|
@@ -58,6 +81,13 @@ module Hecks
|
|
|
58
81
|
@seeds_with_new = 0
|
|
59
82
|
end
|
|
60
83
|
|
|
84
|
+
# Builds this seed's generation plan — a possible splice, plus which
|
|
85
|
+
# verbs to favor.
|
|
86
|
+
#
|
|
87
|
+
# @param seed [Integer] the seed this plan is for; drives the splice-or-not
|
|
88
|
+
# coin flip and, when splicing, which corpus entry and prefix length are drawn
|
|
89
|
+
# @return [Fuzzing::CoverageCampaign::Plan] the plan `SequenceGenerator.generate`
|
|
90
|
+
# or `.trace` should be run with for this seed
|
|
61
91
|
def plan(seed)
|
|
62
92
|
random = Random.new(seed)
|
|
63
93
|
prefix = nil
|
|
@@ -68,9 +98,15 @@ module Hecks
|
|
|
68
98
|
Plan.new(prefix: prefix, favor: rare_verbs)
|
|
69
99
|
end
|
|
70
100
|
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
101
|
+
# Folds one seed's generation results into the campaign's running
|
|
102
|
+
# coverage, and admits its corpus entry if it reached anything new.
|
|
103
|
+
#
|
|
104
|
+
# @param seed [Integer] the seed this trace was generated from
|
|
105
|
+
# @param plan [Fuzzing::CoverageCampaign::Plan] the plan that seed was generated with
|
|
106
|
+
# @param trace [Fuzzing::SequenceGenerator::Trace] `SequenceGenerator.trace`'s answer:
|
|
107
|
+
# `coverage` is `[[attempt_index, tuple], ...]`, `verbs` every verb the booted
|
|
108
|
+
# catalog offered
|
|
109
|
+
# @return [void]
|
|
74
110
|
def record(seed, plan, trace)
|
|
75
111
|
@seeds += 1
|
|
76
112
|
@spliced += 1 if plan.spliced?
|
|
@@ -86,8 +122,15 @@ module Hecks
|
|
|
86
122
|
admit(seed, plan, new_at.max + 1)
|
|
87
123
|
end
|
|
88
124
|
|
|
125
|
+
# Counts the distinct coverage tuples this campaign has reached so far.
|
|
126
|
+
#
|
|
127
|
+
# @return [Integer] the number of distinct coverage tuples seen
|
|
89
128
|
def tuples_seen = @seen.size
|
|
90
129
|
|
|
130
|
+
# Renders the campaign's running totals as one human-readable line.
|
|
131
|
+
#
|
|
132
|
+
# @return [String] a one-line summary of tuples seen, seeds run, seeds that
|
|
133
|
+
# reached something new, and how many were spliced from the corpus
|
|
91
134
|
def summary
|
|
92
135
|
"coverage: #{@seen.size} distinct (verb, kind, state, mutation, outcome) tuple(s) over #{@seeds} seed(s); " \
|
|
93
136
|
"#{@seeds_with_new} seed(s) reached something new; #{@spliced} spliced from a corpus of #{@corpus.size}"
|
|
@@ -45,6 +45,16 @@ module Hecks
|
|
|
45
45
|
# tolerated verb Rust nonetheless answered with a query row: the
|
|
46
46
|
# manifest says "not generated" but the binary disagrees, so the
|
|
47
47
|
# tolerance itself is wrong and must not silently hold.
|
|
48
|
+
# @param gaps [Hecks::Fuzzing::RustGapManifest] the compiled binary's manifest
|
|
49
|
+
# @param ruby_refusals [Array<Hash>] Ruby's own refusal rows for this history
|
|
50
|
+
# @param rust_refusals [Array<Hash>] the Rust binary's own refusal rows
|
|
51
|
+
# @param ruby_queries [Array<Hash>] Ruby's own query rows for this history
|
|
52
|
+
# @param rust_queries [Array<Hash>] the Rust binary's own query rows
|
|
53
|
+
# @return [Hash] `ruby_refusals:`/`rust_refusals:`/`ruby_queries:`/
|
|
54
|
+
# `rust_queries:` (each with tolerated verbs removed), `skipped:` (a
|
|
55
|
+
# `Set<String>` of every tolerated verb this history actually reached),
|
|
56
|
+
# and `stale:` (an `Array<Hash>` divergence per tolerated verb the Rust
|
|
57
|
+
# binary nonetheless answered)
|
|
48
58
|
def manifest_partition(gaps, ruby_refusals:, rust_refusals:, ruby_queries:, rust_queries:)
|
|
49
59
|
verb_of = ->(row) { row.key?("verb") ? row["verb"] : row["query"] }
|
|
50
60
|
declared = ->(row) { gaps.not_generated?(verb_of.call(row)) }
|
|
@@ -60,11 +70,22 @@ module Hecks
|
|
|
60
70
|
skipped: reached.to_set(&verb_of), stale: stale }
|
|
61
71
|
end
|
|
62
72
|
|
|
73
|
+
# Runs the standard property battery and reports every one that failed.
|
|
74
|
+
#
|
|
75
|
+
# @param history [Hash] a replayed history as returned by `Replay.call`
|
|
76
|
+
# @return [Array<Hash>] one `{field:, detail:}` entry per failed property
|
|
63
77
|
def property_divergences(history)
|
|
64
78
|
Properties.check(history).reject { |_, result| result == true }
|
|
65
79
|
.map { |name, message| { field: name.to_s, detail: message } }
|
|
66
80
|
end
|
|
67
81
|
|
|
82
|
+
# Flattens `history[:self_consistency]`'s own per-check divergence lists.
|
|
83
|
+
#
|
|
84
|
+
# @param history [Hash] a replayed history as returned by `Replay.call`,
|
|
85
|
+
# optionally carrying `:self_consistency` (`Replay.call`'s own
|
|
86
|
+
# `self_consistency: true`)
|
|
87
|
+
# @return [Array<Hash>] every self-consistency divergence recorded; empty if
|
|
88
|
+
# `history` carries no `:self_consistency`
|
|
68
89
|
def self_consistency_divergences(history)
|
|
69
90
|
return [] unless history[:self_consistency]
|
|
70
91
|
|
|
@@ -75,6 +96,19 @@ module Hecks
|
|
|
75
96
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
76
97
|
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
77
98
|
# rubocop:disable-next Metrics/MethodLength
|
|
99
|
+
# @param differ [Object] duck-typed comparison helper answering
|
|
100
|
+
# `RustConformanceHelpers`' interface (adapter-defined)
|
|
101
|
+
# @param domain_path [String] path to the domain directory to replay
|
|
102
|
+
# @param steps [Array<Hash>] the step sequence to replay on both engines
|
|
103
|
+
# @param binary [String] path to the compiled Rust conformance binary to run
|
|
104
|
+
# @param modes [Array<Symbol>] which comparisons to run — any of
|
|
105
|
+
# `:differential` (always run), `:self_consistency`,
|
|
106
|
+
# `:properties_in_differential`, `:adapter_parity_sqlite`
|
|
107
|
+
# @param adapter_parity_sqlite [Proc, nil] callable answering the
|
|
108
|
+
# `:adapter_parity_sqlite` mode's own comparison, or `nil` to skip it
|
|
109
|
+
# even when named in `modes`
|
|
110
|
+
# @return [Hash{Symbol => Array<Hash>}] one divergence list per active mode,
|
|
111
|
+
# keyed by the mode name; `:differential` is always present
|
|
78
112
|
def diff(differ, domain_path, steps, binary, modes:, adapter_parity_sqlite: nil)
|
|
79
113
|
self_consistency = modes.include?(:self_consistency)
|
|
80
114
|
ruby_result = Replay.call(domain_path, steps, self_consistency: self_consistency)
|