add custom hasher for testing
This commit is contained in:
parent
4eda4366c8
commit
b1f9706e4e
3 changed files with 245 additions and 0 deletions
|
@ -40,6 +40,7 @@ fayalite-visit-gen.workspace = true
|
|||
|
||||
[features]
|
||||
unstable-doc = []
|
||||
unstable-test-hasher = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["unstable-doc"]
|
||||
|
|
|
@ -8,8 +8,12 @@ mod const_usize;
|
|||
mod misc;
|
||||
mod scoped_ref;
|
||||
pub(crate) mod streaming_read_utf8;
|
||||
mod test_hasher;
|
||||
|
||||
// allow easily switching the hasher crate-wide for testing
|
||||
#[cfg(feature = "unstable-test-hasher")]
|
||||
pub type DefaultBuildHasher = test_hasher::DefaultBuildHasher;
|
||||
#[cfg(not(feature = "unstable-test-hasher"))]
|
||||
pub(crate) type DefaultBuildHasher = hashbrown::hash_map::DefaultHashBuilder;
|
||||
|
||||
pub(crate) type HashMap<K, V> = hashbrown::HashMap<K, V, DefaultBuildHasher>;
|
||||
|
|
240
crates/fayalite/src/util/test_hasher.rs
Normal file
240
crates/fayalite/src/util/test_hasher.rs
Normal file
|
@ -0,0 +1,240 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
#![cfg(feature = "unstable-test-hasher")]
|
||||
|
||||
use std::{
|
||||
fmt::Write as _,
|
||||
hash::{BuildHasher, Hash, Hasher},
|
||||
io::Write as _,
|
||||
marker::PhantomData,
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
type BoxDynHasher = Box<dyn Hasher + Send + Sync>;
|
||||
type BoxDynBuildHasher = Box<dyn DynBuildHasherTrait + Send + Sync>;
|
||||
type BoxDynMakeBuildHasher = Box<dyn Fn() -> BoxDynBuildHasher + Send + Sync>;
|
||||
|
||||
trait TryGetDynBuildHasher: Copy {
|
||||
type Type;
|
||||
fn try_get_make_build_hasher(self) -> Option<BoxDynMakeBuildHasher>;
|
||||
}
|
||||
|
||||
impl<T> TryGetDynBuildHasher for PhantomData<T> {
|
||||
type Type = T;
|
||||
fn try_get_make_build_hasher(self) -> Option<BoxDynMakeBuildHasher> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + BuildHasher<Hasher: Send + Sync + 'static> + Send + Sync + 'static + Clone>
|
||||
TryGetDynBuildHasher for &'_ PhantomData<T>
|
||||
{
|
||||
type Type = T;
|
||||
fn try_get_make_build_hasher(self) -> Option<BoxDynMakeBuildHasher> {
|
||||
Some(Box::new(|| Box::<DynBuildHasher<T>>::default()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct DynBuildHasher<T>(T);
|
||||
|
||||
trait DynBuildHasherTrait: BuildHasher<Hasher = BoxDynHasher> {
|
||||
fn clone_dyn_build_hasher(&self) -> BoxDynBuildHasher;
|
||||
}
|
||||
|
||||
impl<BH: BuildHasher<Hasher: Send + Sync + 'static>> BuildHasher for DynBuildHasher<BH> {
|
||||
type Hasher = BoxDynHasher;
|
||||
|
||||
fn build_hasher(&self) -> Self::Hasher {
|
||||
Box::new(self.0.build_hasher())
|
||||
}
|
||||
|
||||
fn hash_one<T: Hash>(&self, x: T) -> u64 {
|
||||
self.0.hash_one(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl<BH> DynBuildHasherTrait for DynBuildHasher<BH>
|
||||
where
|
||||
Self: Clone + BuildHasher<Hasher = BoxDynHasher> + Send + Sync + 'static,
|
||||
{
|
||||
fn clone_dyn_build_hasher(&self) -> BoxDynBuildHasher {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultBuildHasher(BoxDynBuildHasher);
|
||||
|
||||
impl Clone for DefaultBuildHasher {
|
||||
fn clone(&self) -> Self {
|
||||
DefaultBuildHasher(self.0.clone_dyn_build_hasher())
|
||||
}
|
||||
}
|
||||
|
||||
const ENV_VAR_NAME: &'static str = "FAYALITE_TEST_HASHER";
|
||||
|
||||
struct EnvVarValue {
|
||||
key: &'static str,
|
||||
try_get_make_build_hasher: fn() -> Option<BoxDynMakeBuildHasher>,
|
||||
description: &'static str,
|
||||
}
|
||||
|
||||
macro_rules! env_var_value {
|
||||
(
|
||||
key: $key:literal,
|
||||
build_hasher: $build_hasher:ty,
|
||||
description: $description:literal,
|
||||
) => {
|
||||
EnvVarValue {
|
||||
key: $key,
|
||||
try_get_make_build_hasher: || {
|
||||
// use rust method resolution to detect if $build_hasher is usable
|
||||
// (e.g. hashbrown's hasher won't be usable without the right feature enabled)
|
||||
(&PhantomData::<DynBuildHasher<$build_hasher>>).try_get_make_build_hasher()
|
||||
},
|
||||
description: $description,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct AlwaysZeroHasher;
|
||||
|
||||
impl Hasher for AlwaysZeroHasher {
|
||||
fn write(&mut self, _bytes: &[u8]) {}
|
||||
fn finish(&self) -> u64 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
const ENV_VAR_VALUES: &'static [EnvVarValue] = &[
|
||||
env_var_value! {
|
||||
key: "std",
|
||||
build_hasher: std::hash::RandomState,
|
||||
description: "use std::hash::RandomState",
|
||||
},
|
||||
env_var_value! {
|
||||
key: "hashbrown",
|
||||
build_hasher: hashbrown::hash_map::DefaultHashBuilder,
|
||||
description: "use hashbrown's DefaultHashBuilder",
|
||||
},
|
||||
env_var_value! {
|
||||
key: "always_zero",
|
||||
build_hasher: std::hash::BuildHasherDefault<AlwaysZeroHasher>,
|
||||
description: "use a hasher that always returns 0 for all hashes,\n \
|
||||
this is useful for checking that PartialEq impls are correct",
|
||||
},
|
||||
];
|
||||
|
||||
fn report_bad_env_var(msg: impl std::fmt::Display) -> ! {
|
||||
let mut msg = format!("{ENV_VAR_NAME}: {msg}\n");
|
||||
for &EnvVarValue {
|
||||
key,
|
||||
try_get_make_build_hasher,
|
||||
description,
|
||||
} in ENV_VAR_VALUES
|
||||
{
|
||||
let availability = match try_get_make_build_hasher() {
|
||||
Some(_) => "available",
|
||||
None => "unavailable",
|
||||
};
|
||||
writeln!(msg, "{key}: ({availability})\n {description}").expect("can't fail");
|
||||
}
|
||||
std::io::stderr()
|
||||
.write_all(msg.as_bytes())
|
||||
.expect("should be able to write to stderr");
|
||||
std::process::abort();
|
||||
}
|
||||
|
||||
impl Default for DefaultBuildHasher {
|
||||
fn default() -> Self {
|
||||
static DEFAULT_FN: LazyLock<BoxDynMakeBuildHasher> = LazyLock::new(|| {
|
||||
let var = std::env::var_os(ENV_VAR_NAME);
|
||||
let var = var.as_deref().unwrap_or("std".as_ref());
|
||||
for &EnvVarValue {
|
||||
key,
|
||||
try_get_make_build_hasher,
|
||||
description: _,
|
||||
} in ENV_VAR_VALUES
|
||||
{
|
||||
if var.as_encoded_bytes().eq_ignore_ascii_case(key.as_bytes()) {
|
||||
return try_get_make_build_hasher().unwrap_or_else(|| {
|
||||
report_bad_env_var(format_args!(
|
||||
"unavailable hasher: {key} (is the appropriate feature enabled?)"
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
report_bad_env_var(format_args!("unrecognized hasher: {var:?}"));
|
||||
});
|
||||
Self(DEFAULT_FN())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultHasher(BoxDynHasher);
|
||||
|
||||
impl BuildHasher for DefaultBuildHasher {
|
||||
type Hasher = DefaultHasher;
|
||||
|
||||
fn build_hasher(&self) -> Self::Hasher {
|
||||
DefaultHasher(self.0.build_hasher())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hasher for DefaultHasher {
|
||||
fn finish(&self) -> u64 {
|
||||
self.0.finish()
|
||||
}
|
||||
|
||||
fn write(&mut self, bytes: &[u8]) {
|
||||
self.0.write(bytes)
|
||||
}
|
||||
|
||||
fn write_u8(&mut self, i: u8) {
|
||||
self.0.write_u8(i)
|
||||
}
|
||||
|
||||
fn write_u16(&mut self, i: u16) {
|
||||
self.0.write_u16(i)
|
||||
}
|
||||
|
||||
fn write_u32(&mut self, i: u32) {
|
||||
self.0.write_u32(i)
|
||||
}
|
||||
|
||||
fn write_u64(&mut self, i: u64) {
|
||||
self.0.write_u64(i)
|
||||
}
|
||||
|
||||
fn write_u128(&mut self, i: u128) {
|
||||
self.0.write_u128(i)
|
||||
}
|
||||
|
||||
fn write_usize(&mut self, i: usize) {
|
||||
self.0.write_usize(i)
|
||||
}
|
||||
|
||||
fn write_i8(&mut self, i: i8) {
|
||||
self.0.write_i8(i)
|
||||
}
|
||||
|
||||
fn write_i16(&mut self, i: i16) {
|
||||
self.0.write_i16(i)
|
||||
}
|
||||
|
||||
fn write_i32(&mut self, i: i32) {
|
||||
self.0.write_i32(i)
|
||||
}
|
||||
|
||||
fn write_i64(&mut self, i: i64) {
|
||||
self.0.write_i64(i)
|
||||
}
|
||||
|
||||
fn write_i128(&mut self, i: i128) {
|
||||
self.0.write_i128(i)
|
||||
}
|
||||
|
||||
fn write_isize(&mut self, i: isize) {
|
||||
self.0.write_isize(i)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue