forked from libre-chip/fayalite
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]
|
[features]
|
||||||
unstable-doc = []
|
unstable-doc = []
|
||||||
|
unstable-test-hasher = []
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["unstable-doc"]
|
features = ["unstable-doc"]
|
||||||
|
|
|
@ -8,8 +8,12 @@ mod const_usize;
|
||||||
mod misc;
|
mod misc;
|
||||||
mod scoped_ref;
|
mod scoped_ref;
|
||||||
pub(crate) mod streaming_read_utf8;
|
pub(crate) mod streaming_read_utf8;
|
||||||
|
mod test_hasher;
|
||||||
|
|
||||||
// allow easily switching the hasher crate-wide for testing
|
// 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 DefaultBuildHasher = hashbrown::hash_map::DefaultHashBuilder;
|
||||||
|
|
||||||
pub(crate) type HashMap<K, V> = hashbrown::HashMap<K, V, DefaultBuildHasher>;
|
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