From 7da3f42ba9aac8db5036b4205d70650132e60768 Mon Sep 17 00:00:00 2001 From: Paul Loyd Date: Sun, 30 Jun 2024 16:21:25 +0400 Subject: [PATCH] test(core/message): support more tests under miri --- elfo-core/src/envelope.rs | 17 +++-- elfo-core/src/message.rs | 3 +- elfo-core/src/message/any.rs | 10 +-- elfo-core/src/message/lookup.rs | 117 ++++++++++++++++++++++++++++++++ elfo-core/src/message/repr.rs | 64 ++--------------- 5 files changed, 141 insertions(+), 70 deletions(-) create mode 100644 elfo-core/src/message/lookup.rs diff --git a/elfo-core/src/envelope.rs b/elfo-core/src/envelope.rs index fb8202c6..af80deed 100644 --- a/elfo-core/src/envelope.rs +++ b/elfo-core/src/envelope.rs @@ -401,7 +401,7 @@ impl EnvelopeBorrowed for Envelope { } #[cfg(test)] -mod tests { +mod tests_miri { use std::sync::Arc; use elfo_utils::time; @@ -427,18 +427,21 @@ mod tests { } } - #[test] - fn duplicate_miri() { - let (counter, message) = Sample::new(42); - + fn make_regular_envelope(message: impl Message) -> Envelope { // Miri doesn't support asm, so mock the time. - let envelope = time::with_instant_mock(|_mock| { + time::with_instant_mock(|_mock| { Envelope::with_trace_id( message, MessageKind::Regular { sender: Addr::NULL }, TraceId::try_from(1).unwrap(), ) - }); + }) + } + + #[test] + fn duplicate() { + let (counter, message) = Sample::new(42); + let envelope = make_regular_envelope(message); assert_eq!(Arc::strong_count(&counter), 2); let envelope2 = envelope.duplicate(); diff --git a/elfo-core/src/message.rs b/elfo-core/src/message.rs index b24c7ec9..218106e8 100644 --- a/elfo-core/src/message.rs +++ b/elfo-core/src/message.rs @@ -9,9 +9,10 @@ use smallbox::smallbox; use crate::dumping; -pub use self::{any::*, protocol::*, repr::*}; +pub use self::{any::*, lookup::*, protocol::*, repr::*}; mod any; +mod lookup; mod protocol; mod repr; diff --git a/elfo-core/src/message/any.rs b/elfo-core/src/message/any.rs index a73d66a2..3f19a1df 100644 --- a/elfo-core/src/message/any.rs +++ b/elfo-core/src/message/any.rs @@ -464,7 +464,7 @@ impl Serialize for AnyMessageRef<'_> { } #[cfg(test)] -mod tests { +mod tests_miri { use std::sync::Arc; use super::*; @@ -525,7 +525,7 @@ mod tests { } #[test] - fn basic_ops_miri() { + fn basic_ops() { check_basic_ops(P0); check_basic_ops(P1(42)); check_basic_ops(P8(424242)); @@ -536,7 +536,7 @@ mod tests { struct WithImplicitDrop(Arc<()>); #[test] - fn drop_miri() { + fn drop_impl() { let counter = Arc::new(()); let message = WithImplicitDrop(counter.clone()); @@ -574,7 +574,7 @@ mod tests { } #[test] - fn serialize_miri() { + fn json_serialize() { let any_msg = AnyMessage::new(MyCoolMessage::example()); for mode in [SerdeMode::Normal, SerdeMode::Network] { let dump = @@ -595,7 +595,7 @@ mod tests { } #[test] - fn serde_roundtrip() { + fn json_roundtrip() { let msg = MyCoolMessage::example(); let any_msg = AnyMessage::new(msg.clone()); let serialized = serde_json::to_string(&any_msg).unwrap(); diff --git a/elfo-core/src/message/lookup.rs b/elfo-core/src/message/lookup.rs new file mode 100644 index 00000000..5b6dce78 --- /dev/null +++ b/elfo-core/src/message/lookup.rs @@ -0,0 +1,117 @@ +use std::borrow::Borrow; + +use fxhash::{FxHashMap, FxHashSet}; + +use super::{MessageTypeId, MessageVTable}; + +/// A list of all registered message vtables via the `linkme` crate. +/// Used only for collecting, all lookups are done via a hashmap. +// Reexported in `elfo::_priv`. +#[doc(hidden)] +#[linkme::distributed_slice] +pub static MESSAGE_VTABLES_LIST: [&'static MessageVTable] = [..]; + +static MESSAGE_VTABLES_MAP: vtables_map::VTablesMap = vtables_map::VTablesMap::new(); + +/// Checks that all registered message have different protocol and name. +/// Returns a list of duplicates if it's violated. +pub(crate) fn check_uniqueness() -> Result<(), Vec<(String, String)>> { + if MESSAGE_VTABLES_MAP.len() == MESSAGE_VTABLES_LIST.len() { + return Ok(()); + } + + Err(MESSAGE_VTABLES_LIST + .iter() + .filter(|vtable| { + let stored = MessageVTable::lookup(vtable.protocol, vtable.name).unwrap(); + MessageTypeId::new(stored) != MessageTypeId::new(vtable) + }) + .map(|vtable| (vtable.protocol.to_string(), vtable.name.to_string())) + .collect::>() + .into_iter() + .collect::>()) +} + +#[derive(PartialEq, Eq, Hash)] +struct Signature([&'static str; 2]); // [protocol, name] + +impl<'a> Borrow<[&'a str; 2]> for Signature { + fn borrow(&self) -> &[&'a str; 2] { + &self.0 + } +} + +impl MessageVTable { + /// Finds a vtable by protocol and name. + /// Used for deserialization of `AnyMessage` and in networking. + pub(crate) fn lookup(protocol: &str, name: &str) -> Option<&'static Self> { + MESSAGE_VTABLES_MAP.get(protocol, name) + } + + #[cfg(miri)] + pub(crate) fn register_for_miri(&'static self) { + MESSAGE_VTABLES_MAP.register(self); + } +} + +#[cfg(not(miri))] +mod vtables_map { + use once_cell::sync::Lazy; + + use super::*; + + pub(super) struct VTablesMap(Lazy>); + + impl VTablesMap { + pub(super) const fn new() -> Self { + let inner: Lazy<_> = Lazy::new(|| { + MESSAGE_VTABLES_LIST + .iter() + .map(|vtable| (Signature([vtable.protocol, vtable.name]), *vtable)) + .collect() + }); + + Self(inner) + } + + pub(super) fn get(&self, protocol: &str, name: &str) -> Option<&'static MessageVTable> { + self.0.get(&[protocol, name]).copied() + } + + pub(super) fn len(&self) -> usize { + self.0.len() + } + } +} + +#[cfg(miri)] +mod vtables_map { + use std::sync::Mutex; + + use super::*; + + // parking-lot doesn't compile with `-Zmiri-strict-provenance`, + // so we cannot use `parking_lot::Mutex` and `Lazy` here. + pub(super) struct VTablesMap(Mutex>>); + + impl VTablesMap { + pub(super) const fn new() -> Self { + Self(Mutex::new(None)) + } + + pub(super) fn get(&self, protocol: &str, name: &str) -> Option<&'static MessageVTable> { + let guard = self.0.lock().unwrap(); + guard.as_ref()?.get(&[protocol, name]).copied() + } + + pub(super) fn len(&self) -> usize { + self.0.lock().unwrap().as_ref().map_or(0, |m| m.len()) + } + + pub(super) fn register(&self, vtable: &'static MessageVTable) { + let key = Signature([vtable.protocol, vtable.name]); + let mut map = self.0.lock().unwrap(); + map.get_or_insert_with(<_>::default).insert(key, vtable); + } + } +} diff --git a/elfo-core/src/message/repr.rs b/elfo-core/src/message/repr.rs index 46cc761d..14bd3151 100644 --- a/elfo-core/src/message/repr.rs +++ b/elfo-core/src/message/repr.rs @@ -1,13 +1,9 @@ use std::{ - alloc, - borrow::Borrow, - fmt, + alloc, fmt, ptr::{self, NonNull}, }; -use fxhash::{FxHashMap, FxHashSet}; use metrics::Label; -use once_cell::sync::Lazy; use smallbox::smallbox; use super::Message; @@ -84,6 +80,12 @@ impl MessageRepr { pub(crate) fn new(message: M) -> Self { debug_assert_ne!(M::_type_id(), MessageTypeId::any()); + // Miri doesn't support extern statics required for the default `linkme`-based + // registration, so we need to register them manually. This constructor is most + // likely called during tests with `lookup`, so this is the best place to do it. + #[cfg(miri)] + message._vtable().register_for_miri(); + Self { vtable: message._vtable(), data: message, @@ -263,58 +265,6 @@ mod vtablefns { }); } -// === VTable registration & lookup === - -/// A list of all registered message vtables via the `linkme` crate. -/// Used only for collecting, all lookups are done via a hashmap. -// Reexported in `elfo::_priv`. -#[doc(hidden)] -#[linkme::distributed_slice] -pub static MESSAGE_VTABLES_LIST: [&'static MessageVTable] = [..]; - -#[derive(PartialEq, Eq, Hash)] -pub struct Signature([&'static str; 2]); // [protocol, name] - -impl<'a> Borrow<[&'a str; 2]> for Signature { - fn borrow(&self) -> &[&'a str; 2] { - &self.0 - } -} - -static MESSAGE_VTABLES_MAP: Lazy> = Lazy::new(|| { - MESSAGE_VTABLES_LIST - .iter() - .map(|vtable| (Signature([vtable.protocol, vtable.name]), *vtable)) - .collect() -}); - -impl MessageVTable { - /// Finds a vtable by protocol and name. - /// Used for deserialization of `AnyMessage` and in networking. - pub(crate) fn lookup(protocol: &str, name: &str) -> Option<&'static Self> { - MESSAGE_VTABLES_MAP.get(&[protocol, name]).copied() - } -} - -/// Checks that all registered message have different protocol and name. -/// Returns a list of duplicates if it's violated. -pub(crate) fn check_uniqueness() -> Result<(), Vec<(String, String)>> { - if MESSAGE_VTABLES_MAP.len() == MESSAGE_VTABLES_LIST.len() { - return Ok(()); - } - - Err(MESSAGE_VTABLES_LIST - .iter() - .filter(|vtable| { - let stored = MessageVTable::lookup(vtable.protocol, vtable.name).unwrap(); - MessageTypeId::new(stored) != MessageTypeId::new(vtable) - }) - .map(|vtable| (vtable.protocol.to_string(), vtable.name.to_string())) - .collect::>() - .into_iter() - .collect::>()) -} - // === LimitedWrite === cfg_network!({