From b55a4ff77a952f0b946073af66005778553c851d Mon Sep 17 00:00:00 2001 From: "hegel-release[bot]" <3077575+hegel-release[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 20:43:11 +0000 Subject: [PATCH] Bump pinned libhegel to 0.37.2 --- RELEASE.md | 3 +++ cmake/libhegel.cmake | 2 +- libhegel/hegel.h | 30 +++++++++++++++++++++--------- nix/flake.nix | 8 ++++---- src/engine.cpp | 8 +++++--- src/generators.cpp | 6 +++--- tests/test_stateful.cpp | 7 +++++-- 7 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..152c0c6f --- /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.2](https://github.com/hegeldev/hegel-rust/releases/tag/v0.37.2). diff --git a/cmake/libhegel.cmake b/cmake/libhegel.cmake index c918ebc5..698ea9db 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.2" 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/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..a745ed6a 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.2"; libhegelAssets = { "x86_64-linux" = { asset = "libhegel-linux-amd64.so"; - sha256 = "3d2113a606896286e2d64c1fa383ae0c1c8a285cea8852ea87794305c9c932dd"; + sha256 = "9dc244c5042b1fc98cacd5f865e5efb75e33c3ee114d746484a92fc1bd4753f2"; }; "aarch64-linux" = { asset = "libhegel-linux-arm64.so"; - sha256 = "33d2b276e7aa3b9c2e0beef384970ccc32b41bef9018b94ae582577f0ea28ecb"; + sha256 = "080520633c6f80c2999e8b83abe5146393904f73b0ea58edb8dbbe8393ad5f55"; }; "aarch64-darwin" = { asset = "libhegel-darwin-arm64.dylib"; - sha256 = "4a037fc978c46b170e8407a28b3300c1411fbe06755a47a5a507bd48dd9925d9"; + sha256 = "5d4c0d871dedccfe5e302ae49ccce2d533aad6a8eca9412642dd6401a63f150e"; }; }; diff --git a/src/engine.cpp b/src/engine.cpp index eff44d1e..9b05f8b4 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); + // A NULL invariant_always_check flags every invariant as sampled, + // so each runs with probability 1 / stateful_step_count. 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..911b090d 100644 --- a/src/generators.cpp +++ b/src/generators.cpp @@ -193,10 +193,10 @@ namespace hegel::generators { /// iff the microsecond is nonzero, matching `isoformat()`. std::string format_time(const hegel_time_t& t) { char buf[24]; - if (t.microsecond != 0) { + unsigned microsecond = static_cast(t.nanosecond / 1000); + if (microsecond != 0) { std::snprintf(buf, sizeof(buf), "%02u:%02u:%02u.%06u", t.hour, - t.minute, t.second, - static_cast(t.microsecond)); + t.minute, t.second, microsecond); } 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..1d6082a4 100644 --- a/tests/test_stateful.cpp +++ b/tests/test_stateful.cpp @@ -64,8 +64,11 @@ 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, it does not error. The + // gate keeps some cases valid so the run stays satisfiable. + if (tc.draw(gs::booleans())) { + tc.draw(hegel::stateful::values_reusable(pool)); + } }); }