diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..b11f9dc1 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,3 @@ +RELEASE_TYPE: patch + +This patch bumps our pinned `libhegel` ([hegel-rust](hegeldev/hegel-rust)) from [0.35.0](https://github.com/hegeldev/hegel-rust/releases/tag/v0.35.0) to [0.37.3](https://github.com/hegeldev/hegel-rust/releases/tag/v0.37.3). diff --git a/cmake/libhegel.cmake b/cmake/libhegel.cmake index c918ebc5..2f6d4fca 100644 --- a/cmake/libhegel.cmake +++ b/cmake/libhegel.cmake @@ -12,7 +12,7 @@ # libhegel release the bundled C header matches. Keep in sync with # libhegel/hegel.h. -set(HEGEL_LIBHEGEL_VERSION "0.35.0" +set(HEGEL_LIBHEGEL_VERSION "0.37.3" CACHE STRING "libhegel (hegeltest) release version to download") set(HEGEL_LIBHEGEL_BASE_URL "https://github.com/hegeldev/hegel-rust/releases/download/v${HEGEL_LIBHEGEL_VERSION}") diff --git a/include/hegel/generators/formats.h b/include/hegel/generators/formats.h index 640d728d..cd41ee2f 100644 --- a/include/hegel/generators/formats.h +++ b/include/hegel/generators/formats.h @@ -120,8 +120,8 @@ namespace hegel::generators { /** * @brief Generate times of day. * - * Generates a time between 00:00:00 and 23:59:59.999999 and returns the - * ISO 8601 serialization (`HH:MM:SS[.ffffff]`). Values shrink towards + * Generates a time between 00:00:00 and 23:59:59.999999999 and returns the + * ISO 8601 serialization (`HH:MM:SS[.fffffffff]`). Values shrink towards * midnight. No timezone component is requested; generated values are naive. * * @return Generator producing ISO 8601 time strings. @@ -132,8 +132,8 @@ namespace hegel::generators { * @brief Generate datetimes. * * Generates datetimes between January 01, 0001 at 00:00:00 and December 31, - * 9999 at 23:59:59.999999 and returns the ISO 8601 serialization - * (`YYYY-MM-DDTHH:MM:SS[.ffffff]`). + * 9999 at 23:59:59.999999999 and returns the ISO 8601 serialization + * (`YYYY-MM-DDTHH:MM:SS[.fffffffff]`). * No timezone is requested; generated values are naive. Examples from this * strategy shrink towards midnight on January 1st 2000. * diff --git a/libhegel/hegel.h b/libhegel/hegel.h index 201305f1..652900f6 100644 --- a/libhegel/hegel.h +++ b/libhegel/hegel.h @@ -793,13 +793,13 @@ typedef struct { /* A drawn time of day: `hour` in `[0, 23]`, `minute` and `second` in - `[0, 59]`, `microsecond` in `[0, 999999]`. + `[0, 59]`, `nanosecond` in `[0, 999999999]`. */ typedef struct { uint8_t hour; uint8_t minute; uint8_t second; - uint32_t microsecond; + uint32_t nanosecond; } hegel_time_t; /* @@ -865,6 +865,12 @@ const char* hegel_context_last_error(const hegel_context_t* ctx); When a CI environment is detected (via `CI`, `GITHUB_ACTIONS`, and similar variables) the defaults change: the database is disabled and derandomization is enabled. Override either with the explicit setters. + + When running inside Antithesis (detected via `ANTITHESIS_OUTPUT_DIR`) + the database is disabled and every health check is skipped. The database + can still be enabled with `hegel_settings_set_database`; the health + checks cannot be re-enabled, since Antithesis's thread pausing would trip + wall-clock checks such as `TooSlow` spuriously. */ hegel_result_t hegel_settings_new(hegel_context_t* ctx, hegel_settings_t** out_settings); @@ -1420,7 +1426,11 @@ hegel_result_t hegel_pool_free(hegel_context_t* ctx, hegel_pool_t* pool); testing, sequential or concurrent: `num_rules` rules — each assigned to a concurrency group by `rule_groups`, an array of group ids parallel to `rule_names` — and `num_invariants` invariants, with names as - NUL-terminated UTF-8, plus concurrency bounds. Group ids are arbitrary + NUL-terminated UTF-8, plus concurrency bounds. `invariant_always_check` + is an array of `num_invariants` flags parallel to `invariant_names` + (NULL for all-false): `hegel_state_machine_should_check_invariant` + answers true unconditionally for a flagged invariant and samples the + rest. Group ids are arbitrary (any value except `HEGEL_STATE_MACHINE_DONE`, which `hegel_state_machine_next_group` reserves as its termination sentinel): the machine has one concurrency group per distinct value of @@ -1490,8 +1500,8 @@ hegel_result_t hegel_pool_free(hegel_context_t* ctx, hegel_pool_t* pool); hegel_result_t hegel_new_state_machine( hegel_context_t* ctx, hegel_test_case_t* tc, const char* const* rule_names, const int64_t* rule_groups, size_t num_rules, - const char* const* invariant_names, size_t num_invariants, - int64_t min_concurrency, int64_t max_concurrency, + const char* const* invariant_names, const bool* invariant_always_check, + size_t num_invariants, int64_t min_concurrency, int64_t max_concurrency, hegel_state_machine_t** out_state_machine, int64_t* out_concurrency); /* @@ -1589,10 +1599,12 @@ hegel_state_machine_rule_rejected(hegel_context_t* ctx, hegel_test_case_t* tc, /* Decide whether the caller should run invariant `invariant_index` at the - current join point, writing the decision into `*out_should_check`: a + current join point, writing the decision into `*out_should_check`: true + unconditionally (consuming no entropy) for an invariant whose + `invariant_always_check` flag was set at creation, otherwise a recorded boolean draw that is true with probability - `1 / stateful_step_count`, so each invariant's expected number of - sampled runs over a full-length test case is one, regardless of the + `1 / stateful_step_count`, so each sampled invariant's expected number + of sampled runs over a full-length test case is one, regardless of the step count. The caller owns the machine's guaranteed invariant checks — its initial state, and its final state once `hegel_state_machine_next_group` signals termination — and should run @@ -1856,7 +1868,7 @@ hegel_result_t hegel_generate_date(hegel_context_t* ctx, hegel_test_case_t* tc, /* Parameters: `min_value` / `max_value`: Inclusive bounds. Pass all-zeros and - `{23, 59, 59, 999999}` for the full day. + `{23, 59, 59, 999999999}` for the full day. Returns `HEGEL_OK` or `HEGEL_E_STOP_TEST`. diff --git a/nix/flake.nix b/nix/flake.nix index ab817c88..99bfb37f 100644 --- a/nix/flake.nix +++ b/nix/flake.nix @@ -25,19 +25,19 @@ # Prebuilt libhegel (Hegel's native engine) release. Keep the version and # hashes in sync with cmake/libhegel.cmake and libhegel/hegel.h. Hashes # are the SHA-256 sidecars published next to each release asset. - libhegelVersion = "0.35.0"; + libhegelVersion = "0.37.3"; libhegelAssets = { "x86_64-linux" = { asset = "libhegel-linux-amd64.so"; - sha256 = "3d2113a606896286e2d64c1fa383ae0c1c8a285cea8852ea87794305c9c932dd"; + sha256 = "48d8cf719811f288e85cc3e380318a4540a0d03947a97c0b71caf6931561eec0"; }; "aarch64-linux" = { asset = "libhegel-linux-arm64.so"; - sha256 = "33d2b276e7aa3b9c2e0beef384970ccc32b41bef9018b94ae582577f0ea28ecb"; + sha256 = "182b05ee427f27602e3e70edfcb1870705d00838d68f5d59c8567fc5db3e0c19"; }; "aarch64-darwin" = { asset = "libhegel-darwin-arm64.dylib"; - sha256 = "4a037fc978c46b170e8407a28b3300c1411fbe06755a47a5a507bd48dd9925d9"; + sha256 = "b94dae6e3fa7b01d5d12fd2086e844291bfadca9048a505d490219b8eb8d34bb"; }; }; diff --git a/src/engine.cpp b/src/engine.cpp index eff44d1e..3ce9e7cb 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -416,7 +416,7 @@ namespace hegel::impl { hegel_time_t value{}; scope.raise_for_rc( hegel_generate_time(scope.ctx, scope.tc, hegel_time_t{0, 0, 0, 0}, - hegel_time_t{23, 59, 59, 999999}, &value), + hegel_time_t{23, 59, 59, 999999999}, &value), "hegel_generate_time"); return value; } @@ -425,7 +425,7 @@ namespace hegel::impl { DrawScope scope(tc); hegel_datetime_t value{}; hegel_datetime_t min_value{{1, 1, 1}, {0, 0, 0, 0}}; - hegel_datetime_t max_value{{9999, 12, 31}, {23, 59, 59, 999999}}; + hegel_datetime_t max_value{{9999, 12, 31}, {23, 59, 59, 999999999}}; scope.raise_for_rc(hegel_generate_datetime(scope.ctx, scope.tc, min_value, max_value, &value), @@ -700,10 +700,12 @@ namespace hegel::internal { std::vector invariant_name_cstrings = to_cstrings(invariant_names); + // invariant_always_check is NULL: every invariant is sampled, so no + // invariant is checked unconditionally. scope.raise_for_rc(hegel_new_state_machine( scope.ctx, scope.tc, rule_name_cstrings.data(), rule_groups.data(), rule_names.size(), - invariant_name_cstrings.data(), + invariant_name_cstrings.data(), nullptr, invariant_names.size(), min_concurrency, max_concurrency, &handle_, &concurrency_), "hegel_new_state_machine"); diff --git a/src/generators.cpp b/src/generators.cpp index 5b97c9c4..9e0b80b4 100644 --- a/src/generators.cpp +++ b/src/generators.cpp @@ -189,14 +189,14 @@ namespace hegel::generators { return buf; } - /// ISO 8601 `HH:MM:SS[.ffffff]` — the fractional part is present - /// iff the microsecond is nonzero, matching `isoformat()`. + /// ISO 8601 `HH:MM:SS[.fffffffff]` — the fractional part is present + /// iff the nanosecond is nonzero. std::string format_time(const hegel_time_t& t) { char buf[24]; - if (t.microsecond != 0) { - std::snprintf(buf, sizeof(buf), "%02u:%02u:%02u.%06u", t.hour, + if (t.nanosecond != 0) { + std::snprintf(buf, sizeof(buf), "%02u:%02u:%02u.%09u", t.hour, t.minute, t.second, - static_cast(t.microsecond)); + static_cast(t.nanosecond)); } else { std::snprintf(buf, sizeof(buf), "%02u:%02u:%02u", t.hour, t.minute, t.second); diff --git a/tests/test_stateful.cpp b/tests/test_stateful.cpp index bd103666..eb4b7a67 100644 --- a/tests/test_stateful.cpp +++ b/tests/test_stateful.cpp @@ -62,11 +62,17 @@ TEST(Pools, PoolsNoConsume) { } TEST(Pools, DrawFromEmptyPool) { - hegel::test([](hegel::TestCase& tc) { - hegel::stateful::Pool pool = hegel::stateful::Pool(tc); - tc.draw(hegel::stateful::values_reusable(pool)); - // should not error just a test case rejection - }); + // A draw from an empty pool rejects the case rather than erroring. Draw a + // value first so the case is not empty (an entirely dataless test is + // unsatisfiable), and suppress the health checks, because every case + // rejects at the pool draw. + hegel::test( + [](hegel::TestCase& tc) { + (void)tc.draw(gs::integers()); + hegel::stateful::Pool pool = hegel::stateful::Pool(tc); + tc.draw(hegel::stateful::values_reusable(pool)); + }, + hegel::Settings{.suppress_health_check = hegel::all_health_checks()}); } namespace {