Skip to content

Commit

Permalink
test(core/message): support more tests under miri
Browse files Browse the repository at this point in the history
  • Loading branch information
loyd committed Jun 30, 2024
1 parent d00ebcb commit 7da3f42
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 70 deletions.
17 changes: 10 additions & 7 deletions elfo-core/src/envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ impl EnvelopeBorrowed for Envelope {
}

#[cfg(test)]
mod tests {
mod tests_miri {
use std::sync::Arc;

use elfo_utils::time;
Expand All @@ -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();
Expand Down
3 changes: 2 additions & 1 deletion elfo-core/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
10 changes: 5 additions & 5 deletions elfo-core/src/message/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ impl Serialize for AnyMessageRef<'_> {
}

#[cfg(test)]
mod tests {
mod tests_miri {
use std::sync::Arc;

use super::*;
Expand Down Expand Up @@ -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));
Expand All @@ -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());

Expand Down Expand Up @@ -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 =
Expand All @@ -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();
Expand Down
117 changes: 117 additions & 0 deletions elfo-core/src/message/lookup.rs
Original file line number Diff line number Diff line change
@@ -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::<FxHashSet<_>>()
.into_iter()
.collect::<Vec<_>>())
}

#[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<FxHashMap<Signature, &'static MessageVTable>>);

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<Option<FxHashMap<Signature, &'static MessageVTable>>>);

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);
}
}
}
64 changes: 7 additions & 57 deletions elfo-core/src/message/repr.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -84,6 +80,12 @@ impl<M: Message> MessageRepr<M> {
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,
Expand Down Expand Up @@ -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<FxHashMap<Signature, &'static MessageVTable>> = 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::<FxHashSet<_>>()
.into_iter()
.collect::<Vec<_>>())
}

// === LimitedWrite ===

cfg_network!({
Expand Down

0 comments on commit 7da3f42

Please sign in to comment.