Skip to content

Commit 075bc34

Browse files
joostjagerclaude
andcommitted
Make test hash map iteration order configurable for deterministic runs
Add a `RandomState` hasher implementation for tests that supports deterministic behavior via the `LDK_TEST_DETERMINISTIC_HASHES=1` environment variable. When set, hash maps use fixed keys ensuring consistent iteration order across test runs. By default, tests continue to use std's RandomState for random hashing, keeping test behavior close to production. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5b21115 commit 075bc34

2 files changed

Lines changed: 68 additions & 1 deletion

File tree

CONTRIBUTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ welcomed.
190190
* `FULL_BLOCK_VIA_LISTEN`
191191
* `FULL_BLOCK_DISCONNECTIONS_SKIPPING_VIA_LISTEN`
192192

193+
* `LDK_TEST_DETERMINISTIC_HASHES` - When set to `1`, uses deterministic hash map iteration order in tests. This ensures consistent test output across runs, useful for comparing logs before and after changes.
194+
193195
C/C++ Bindings
194196
--------------
195197

lightning/src/util/hash_tables.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,75 @@
66
pub use hashbrown::hash_map;
77

88
mod hashbrown_tables {
9-
#[cfg(feature = "std")]
9+
#[cfg(all(feature = "std", not(test)))]
1010
mod hasher {
1111
pub use std::collections::hash_map::RandomState;
1212
}
13+
#[cfg(all(feature = "std", test))]
14+
mod hasher {
15+
#![allow(deprecated)] // hash::SipHasher was deprecated in favor of something only in std.
16+
use core::hash::{BuildHasher, Hasher};
17+
18+
/// A [`BuildHasher`] for tests that supports deterministic behavior via environment variable.
19+
///
20+
/// When `LDK_TEST_DETERMINISTIC_HASHES` is set, uses fixed keys for deterministic iteration.
21+
/// Otherwise, delegates to std's RandomState for random hashing.
22+
#[derive(Clone)]
23+
pub enum RandomState {
24+
Std(std::collections::hash_map::RandomState),
25+
Deterministic,
26+
}
27+
28+
impl RandomState {
29+
pub fn new() -> RandomState {
30+
if std::env::var("LDK_TEST_DETERMINISTIC_HASHES").map(|v| v == "1").unwrap_or(false)
31+
{
32+
RandomState::Deterministic
33+
} else {
34+
RandomState::Std(std::collections::hash_map::RandomState::new())
35+
}
36+
}
37+
}
38+
39+
impl Default for RandomState {
40+
fn default() -> RandomState {
41+
RandomState::new()
42+
}
43+
}
44+
45+
/// A hasher wrapper that delegates to either std's DefaultHasher or a deterministic SipHasher.
46+
pub enum RandomStateHasher {
47+
Std(std::collections::hash_map::DefaultHasher),
48+
Deterministic(core::hash::SipHasher),
49+
}
50+
51+
impl Hasher for RandomStateHasher {
52+
fn finish(&self) -> u64 {
53+
match self {
54+
RandomStateHasher::Std(h) => h.finish(),
55+
RandomStateHasher::Deterministic(h) => h.finish(),
56+
}
57+
}
58+
fn write(&mut self, bytes: &[u8]) {
59+
match self {
60+
RandomStateHasher::Std(h) => h.write(bytes),
61+
RandomStateHasher::Deterministic(h) => h.write(bytes),
62+
}
63+
}
64+
}
65+
66+
impl BuildHasher for RandomState {
67+
type Hasher = RandomStateHasher;
68+
fn build_hasher(&self) -> RandomStateHasher {
69+
match self {
70+
RandomState::Std(s) => RandomStateHasher::Std(s.build_hasher()),
71+
RandomState::Deterministic => {
72+
RandomStateHasher::Deterministic(core::hash::SipHasher::new_with_keys(0, 0))
73+
},
74+
}
75+
}
76+
}
77+
}
1378
#[cfg(not(feature = "std"))]
1479
mod hasher {
1580
#![allow(deprecated)] // hash::SipHasher was deprecated in favor of something only in std.

0 commit comments

Comments
 (0)