Skip to content

Improve conformance testing of non-basic generators#46

Open
Liam-DeVoe wants to merge 24 commits intomainfrom
add-dual-path-conformance-testing
Open

Improve conformance testing of non-basic generators#46
Liam-DeVoe wants to merge 24 commits intomainfrom
add-dual-path-conformance-testing

Conversation

@Liam-DeVoe
Copy link
Copy Markdown
Member

Claude-written description

Part of a cross-repo effort to add dual-path conformance testing. Every conformance binary now reads a mode field from its JSON params and acts on it:

  • Added make_non_basic helper to metrics.h — wraps a generator in a trivial .filter() to strip its schema, forcing the compositional fallback path.
  • Added get_mode helper to metrics.h — extracts the mode string from parsed JSON args.
  • Updated all 8 conformance binaries (booleans, integers, floats, text, binary, sampled_from, lists, hashmaps) to extract mode and conditionally wrap generators when mode == "non_basic".
  • For collection binaries (lists, hashmaps), the element generators are wrapped, not the collection itself.
  • test_booleans.cpp now parses JSON params from argv (previously took no arguments).

No library source files changed — only conformance test binaries.

Note: Conformance CI may fail until the corresponding hegel-core PR merges (which adds the mode field to conformance test params).

All conformance binaries now read a `mode` field from JSON params.
When mode is "non_basic", generators are wrapped with a trivial filter
to lose their schema, forcing the compositional fallback path.
Report the raw vector of integers instead of computing size/min_element/max_element
in the binary. nlohmann/json natively serializes std::vector<int> as a JSON array.
Only test_lists and test_hashmaps need dual-path (basic/non_basic) testing.
Scalar generators do not benefit from it, so revert them to simple hegel::draw(gen).
@Liam-DeVoe Liam-DeVoe changed the title claude: Add dual-path conformance testing (basic/non_basic mode) Improve conformance testing of non-basic generators Apr 12, 2026
The compositional fallback paths of `vectors` and `dictionaries` did
not honor uniqueness when element/key generators lacked a schema. This
was exposed by the new dual-path conformance tests:

- `vectors({.unique = true})` could return a non-unique vector when the
  element generator had been transformed (e.g. via `.filter()`).
- `dictionaries` could loop indefinitely if the key generator kept
  returning the same value (for instance while Hypothesis was exploring
  its zero/minimum inputs).

Both fallbacks now cap attempts and reject the test case via
`hegel::internal::assume` when the generator cannot satisfy the
requested size with unique values. `test_lists.cpp` now also threads
the `unique` parameter through to the `vectors` generator, so basic-
mode list conformance actually exercises the unique path.
Comment thread RELEASE.md Outdated
potentially non-unique vector, and `dictionaries` could loop for an
unbounded number of attempts when the key generator repeatedly returned
duplicates. Both now cap attempts and reject the test case via
`hegel::internal::assume` when the generator cannot satisfy the
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude loves doing this. It's the wrong solution. The correct solution is to actually check whether reject returned an error and raise it appropriately.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, it's not using server side collections at all, no wonder.

Comment thread include/hegel/generators/collections.h Outdated
std::vector<T> result;
result.reserve(len);

if constexpr (requires(T a, T b) { a == b; }) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things I don't love about this:

  1. It always uses the O(n^2) version. Mostly fine but I don't like it and don't want people relying on Hypothesis collections always being small because a) I'd like an option to change that and b) We definitely don't want that true in Antithesis.
  2. It silently fails to apply uniqueness if == isn't available when it should raise an error.

Comment thread include/hegel/generators/collections.h Outdated

size_t max_attempts = len * 10 + 10;
for (size_t attempts = 0;
result.size() < len && attempts < max_attempts; ++attempts) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be using server side collections?

Comment thread RELEASE.md Outdated
potentially non-unique vector, and `dictionaries` could loop for an
unbounded number of attempts when the key generator repeatedly returned
duplicates. Both now cap attempts and reject the test case via
`hegel::internal::assume` when the generator cannot satisfy the
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, it's not using server side collections at all, no wonder.

Resolves conflicts introduced by the large refactor on main (public API
rename, namespace alias convention, HegelSettings/hegel::draw) against
the dual-path conformance changes on this branch.

- RELEASE.md: combine main's minor-release description with this
  branch's patch fix notes under RELEASE_TYPE: minor.
- tests/conformance/cpp/test_lists.cpp: adopt main's `gs::` alias and
  hegel::draw / test_cases settings form while preserving the `mode`
  dual-path wiring, the `unique` parameter, and the simplified
  `{"elements": vec}` metric output (matches hegel-core's list.py.
- tests/conformance/cpp/test_hashmaps.cpp: adopt  alias and the
  new draw/settings form while preserving -based make_non_basic
  wrapping for both keys and values.
EOF
)
Resolve conflicts between the dual-path conformance testing changes
and main's refactor that passes a TestCase& to test callbacks and
generator do_draw calls.

For collections.h, port the unique-element retry loops in vectors()
and dictionaries() to the new API (const TestCase& tc, do_draw(tc),
tc.assume(...) instead of hegel::internal::assume).

For test_lists.cpp, combine the dual-path (basic vs non_basic) branch
selection and raw-element metrics from HEAD with the TestCase&
callback signature from main.
v0.2.0 was released on main, which removed RELEASE.md. Keep only the
still-unreleased `vectors`/`dictionaries` fallback uniqueness fix note.
… classes

Main's big-refactor moved vectors/maps logic into VectorsGenerator and
MapsGenerator classes and renamed dictionaries -> maps (with
DictionariesParams -> MapsParams). This merge:

- Reapplies the fallback-path uniqueness enforcement for vectors() and
  duplicate-key avoidance for maps() into the new do_draw() methods.
- Updates test_hashmaps.cpp to call gs::maps() (was gs::dictionaries())
  while preserving the mode=non_basic wrapping for keys and values.
Merged origin/main. The newly added test_settings flaky-reporting tests
and the exception-message output test referenced the pre-0.3.0 API
(`hegel::hegel`, `hegel::settings::HealthCheck`, `hegel::settings::Database`).
Updated them to the current names: `hegel::test`, `hegel::HealthCheck`,
`hegel::Database`.

Combined the pending RELEASE.md entries from both sides.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants