v0.28.0
This release fixes a number of bugs found in a full review of the frontend, generators, and proc macros, alongside the engine fixes in the corresponding hegeltest-c release (whose regex, shrinking, and replay fixes all surface here — see its changelog).
Breaking changes:
Settings::suppress_health_checknow replaces any previously configured suppressions, likeSettings::phases, instead of accumulating across calls. Callers that chained multiple calls to build up a set should pass all the checks in one call.- Several generator configurations that previously misbehaved silently are now clean usage errors:
gs::hashsets(...)/gs::hashmaps(...)with amin_sizelarger than the element generator's distinct-value pool (previously returned a too-small collection, violating the documented contract),gs::durations().min_value(...)beyondu64::MAXnanoseconds (previously generated values below the requested minimum),gs::uuids().version(n)outside 1–5 (previously generated non-RFC-4122 output), and chrono time/datetime bounds using a mid-day leap-second representation (previously could generate values outside the bounds). - The write-only
panic_messageandreproduce_blobfields have been removed from the doc-hiddenbackend::Failuretype.
Bug fixes:
- Builder methods called on a string-shaped generator (
text,characters,from_regex,domains) after it had already been drawn from were silently ignored; they now take effect. - Running out of data in the middle of a
#[rule]now unwinds throughhegel::stateful::run()as an overrun instead of returning normally with a half-applied rule, so code afterrun()can no longer observe torn state. Engine errors fromPooloperations are classified properly instead of all being treated as out-of-data. - A test body that caught its own panic no longer donates that panic's location to a later failure on the same thread, which could group failures under the wrong origin.
- In
Mode::SingleTestCase, a failedassume()insidetc.repeat(...)skips that iteration and continues, matching normal mode, instead of silently ending the supposedly endless loop. hegel::with_output_overriderestores the previous sink even if the wrapped closure panics, and explicit-test-case replay output goes through the output sink like every other replay line.#[derive(DefaultGenerator)]now supports generic types: the generated generator andDefaultGeneratorimpl carry the type's parameters withDefaultGeneratorbounds, so#[derive(DefaultGenerator)] struct Point<T> { x: T }makesgs::default::<Point<i32>>()work for anyT: DefaultGenerator. Const parameters are supported too; lifetime parameters get a clean compile error (previously any generic type produced a confusing error pointing into generated code). The derive also reports clean errors for zero-variant enums and fields namedneworboxed(previously a proc-macro panic or a builder-collision error), and an enum with variantsFoo(...)andFooWith { ... }now compiles. Generated code is fully qualified, so a localmod hegelor a shadowedVecno longer breaks expansion, and a doc comment on a#[rule]no longer emits anunused_doc_commentswarning into your crate.#[hegel::composite]and#[hegel::state_machine]now reject arguments instead of silently ignoring them, and#[hegel::test]rejects a declared return type with a targeted message.
Improvements:
gs::hashmapswith an enumerable key generator (e.g.sampled_from) draws keys without replacement, so maps that must contain most of a small key alphabet generate efficiently instead of tripping theTooSlowhealth check..filter(...)on an enumerable generator computes the filtered value set once instead of re-cloning the source's elements on every draw, andone_ofgenerators of enumerable children are themselves enumerable.hegel::extras::serde_json::values()bounds its recursive arrays and objects so generated JSON trees terminate naturally instead of routinely exhausting the choice buffer.HashSet<T>now has aDefaultGeneratorimpl, matchingVecandHashMap, andhegel::extras::chrono::naive_weeks()keeps its default range clear ofNaiveDate::MIN/MAX, where chrono's ownNaiveWeekaccessors panic.text()andbinary()withmin_size > 100and nomax_sizegenerate lengths in[min_size, min_size + 100]instead of collapsing to a fixed length.
libhegel C ABI
This release fixes a number of correctness bugs found in a full review of the engine, hardens the C ABI against misuse, and improves generation and shrinking performance.
Breaking C ABI changes:
hegel_settings_set_mode,hegel_settings_set_backend,hegel_settings_set_verbosity, andhegel_mark_completenow take their enum-valued parameter as a validateduint32_tinstead of the enum type itself. Passing an out-of-range value is now a reportableHEGEL_E_INVALID_ARGinstead of undefined behavior in the library. C callers passing the enum constants are source-compatible and just need a recompile against the new header.hegel_settings_set_suppress_health_checknow replaces the set of suppressed checks on each call, likehegel_settings_set_phases, instead of accumulating across calls (which made it impossible to clear a suppression). Callers that relied on accumulation should OR their bits together into a single call.hegel_next_test_case,hegel_run_result,hegel_test_case_from_blob, andhegel_test_case_clonenow check the handle before the out parameter, so passing both as NULL returnsHEGEL_E_INVALID_HANDLErather thanHEGEL_E_INVALID_ARG, consistent with every other function.
Generation fixes:
- Strings generated from regex patterns now actually match patterns using
\b,\B, or$/\Zin non-final positions (previously the anchors were ignored, so e.g. most strings generated for\bfoo\bcontained no match), and fullmatch generation no longer emits lookaround assertion bodies into the output. Atomic groups and possessive repeats re-validate their output against the pattern, and(?i)negated character classes exclude the full case-folding closure of their members. - A string generator whose alphabet is empty with
max_size = 0— a legal configuration whose only value is the empty string — no longer crashes the engine on its first test case. - Times and datetimes drawn near a bound expressed with chrono's leap-second representation could exceed the bound; such bounds are now rejected up front (except the end-of-day leap second, which remains fully supported).
Shrinking and replay fixes:
- Fixed an engine panic when a shrink pass revisited an integer node whose kind had changed under it mid-pass.
- The pre-shrink verification run now requires the failure to reproduce with the same origin. Previously a test that panicked at a different location on replay could be reported under the wrong origin with a reproduction blob that did not reproduce it; it is now correctly reported as a flaky test.
- Several shrink passes are substantially more effective per invocation: the length-redistribution passes can move more than one element at a time, the adaptive deletion pass's leftward walk accumulates across accepted steps, and string truncation binary-searches instead of trying every length.
- The targeting phase no longer corrupts its hill-climbing steps for byte values wider than 128 bits.
- Database replay no longer runs an example twice when it is stored under both the primary and secondary keys, and a stored counterexample that replays with different values no longer skips the shrink phase just because it realised the same length.
Performance: regex . and negated-literal draws, string-constant injection, and codepoint lookups no longer rescan their alphabets on every drawn character, and the per-draw choice-configuration clone in the draw hot path is gone.
Diagnostics: test-case handle errors (HEGEL_E_INVALID_HANDLE, HEGEL_E_ALREADY_COMPLETE, HEGEL_E_CONCURRENT_USE) now record a message on the context like every other handle family, and the header documentation has been corrected in several places (the hegel_pool_generate empty-pool result is HEGEL_E_ASSUME and callers may recover from it like any failed assumption, hegel_settings_new defaults are CI-dependent, run handles are single-threaded while settings handles document their share-after-configuring contract, and hegel_date_t spans the proleptic year range its draws actually use).