chore: ci script and README
This commit is contained in:
@@ -9,10 +9,10 @@
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
pub mod trace;
|
||||
pub mod world;
|
||||
pub mod transport;
|
||||
pub mod sim;
|
||||
pub mod trace;
|
||||
pub mod transport;
|
||||
pub mod world;
|
||||
|
||||
pub use sim::{Sim, SimNode};
|
||||
pub use trace::{Contact, ContactTrace};
|
||||
|
||||
@@ -53,7 +53,12 @@ mod tests {
|
||||
#[test]
|
||||
fn in_contact_matches_both_orders() {
|
||||
let mut trace = ContactTrace::default();
|
||||
trace.contacts.push(Contact { start_ms: 100, end_ms: 200, a: 3, b: 7 });
|
||||
trace.contacts.push(Contact {
|
||||
start_ms: 100,
|
||||
end_ms: 200,
|
||||
a: 3,
|
||||
b: 7,
|
||||
});
|
||||
assert!(!trace.in_contact(3, 7, 50)); // before the window
|
||||
assert!(trace.in_contact(3, 7, 100)); // at the start edge
|
||||
assert!(trace.in_contact(7, 3, 150)); // mid-window, either order
|
||||
|
||||
@@ -49,8 +49,7 @@ impl Transport for SimTransport {
|
||||
if !w.trace.in_contact(self.me, *to, w.prev_t) {
|
||||
return Err(SendError::Unreachable);
|
||||
}
|
||||
if frame.len() > w.cfg.mtu
|
||||
&& !matches!(Frame::decode(frame), Ok(Frame::Summary { .. })) {
|
||||
if frame.len() > w.cfg.mtu && !matches!(Frame::decode(frame), Ok(Frame::Summary { .. })) {
|
||||
return Err(SendError::TooLarge);
|
||||
}
|
||||
if w.rng.gen::<f64>() < w.cfg.loss {
|
||||
|
||||
+13
-9
@@ -150,9 +150,12 @@ mod tests {
|
||||
seed,
|
||||
};
|
||||
let mut trace = ContactTrace::default();
|
||||
trace
|
||||
.contacts
|
||||
.push(Contact { start_ms: start, end_ms: end, a: 0, b: 1 });
|
||||
trace.contacts.push(Contact {
|
||||
start_ms: start,
|
||||
end_ms: end,
|
||||
a: 0,
|
||||
b: 1,
|
||||
});
|
||||
SimWorld::new(cfg, trace)
|
||||
}
|
||||
|
||||
@@ -163,9 +166,7 @@ mod tests {
|
||||
|
||||
/// The number of `Frame` events queued in `node`'s inbox.
|
||||
fn frame_count(world: &Rc<RefCell<SimWorld>>, node: usize) -> usize {
|
||||
world
|
||||
.borrow()
|
||||
.inboxes[node]
|
||||
world.borrow().inboxes[node]
|
||||
.iter()
|
||||
.filter(|e| matches!(e, Event::Frame(_, _)))
|
||||
.count()
|
||||
@@ -244,9 +245,12 @@ mod tests {
|
||||
};
|
||||
let make_trace = || {
|
||||
let mut trace = ContactTrace::default();
|
||||
trace
|
||||
.contacts
|
||||
.push(Contact { start_ms: 0, end_ms: 1000, a: 0, b: 1 });
|
||||
trace.contacts.push(Contact {
|
||||
start_ms: 0,
|
||||
end_ms: 1000,
|
||||
a: 0,
|
||||
b: 1,
|
||||
});
|
||||
trace
|
||||
};
|
||||
let w1 = SimWorld::new(cfg.clone(), make_trace());
|
||||
|
||||
@@ -27,14 +27,25 @@ fn two_nodes_one_contact_converge() {
|
||||
|
||||
let mut sim = Sim::new(cfg, trace);
|
||||
for i in 0..10 {
|
||||
sim.observe(0, format!("n0-{}", i).as_bytes(), format!("p{}", i).as_bytes());
|
||||
sim.observe(
|
||||
0,
|
||||
format!("n0-{}", i).as_bytes(),
|
||||
format!("p{}", i).as_bytes(),
|
||||
);
|
||||
}
|
||||
for i in 0..10 {
|
||||
sim.observe(1, format!("n1-{}", i).as_bytes(), format!("p{}", i).as_bytes());
|
||||
sim.observe(
|
||||
1,
|
||||
format!("n1-{}", i).as_bytes(),
|
||||
format!("p{}", i).as_bytes(),
|
||||
);
|
||||
}
|
||||
sim.run_until(5000);
|
||||
|
||||
assert!(sim.converged(&[0, 1]), "both nodes must hold every observation");
|
||||
assert!(
|
||||
sim.converged(&[0, 1]),
|
||||
"both nodes must hold every observation"
|
||||
);
|
||||
assert_eq!(sim.id_set(0).len(), 20, "each node stores 10 + 10 obs");
|
||||
assert_eq!(sim.id_set(1).len(), 20);
|
||||
}
|
||||
@@ -66,7 +77,11 @@ fn relay_through_middle_node() {
|
||||
|
||||
let mut sim = Sim::new(cfg, trace);
|
||||
for i in 0..5 {
|
||||
sim.observe(0, format!("a-{}", i).as_bytes(), format!("a-p{}", i).as_bytes());
|
||||
sim.observe(
|
||||
0,
|
||||
format!("a-{}", i).as_bytes(),
|
||||
format!("a-p{}", i).as_bytes(),
|
||||
);
|
||||
}
|
||||
sim.run_until(7000);
|
||||
|
||||
@@ -74,7 +89,10 @@ fn relay_through_middle_node() {
|
||||
let node_2 = sim.id_set(2);
|
||||
// 0 and 2 never shared a contact, yet 2 received everything through 1.
|
||||
for id in &from_0 {
|
||||
assert!(node_2.contains(id), "node 2 is missing node 0's observation {id:?}");
|
||||
assert!(
|
||||
node_2.contains(id),
|
||||
"node 2 is missing node 0's observation {id:?}"
|
||||
);
|
||||
}
|
||||
assert_eq!(from_0.len(), 5, "node 0 observed 5 things");
|
||||
}
|
||||
@@ -114,7 +132,10 @@ fn disconnected_component_does_not_converge() {
|
||||
vec![vec![0, 1], vec![2, 3]],
|
||||
"the contact graph splits into two pairs"
|
||||
);
|
||||
assert!(!sim.converged(&[0, 1, 2, 3]), "cross-component never converges");
|
||||
assert!(
|
||||
!sim.converged(&[0, 1, 2, 3]),
|
||||
"cross-component never converges"
|
||||
);
|
||||
assert!(sim.converged(&[0, 1]), "component A converges");
|
||||
assert!(sim.converged(&[2, 3]), "component B converges");
|
||||
}
|
||||
@@ -139,8 +160,16 @@ fn lossy_link_still_converges_given_time() {
|
||||
|
||||
let mut sim = Sim::new(cfg, trace);
|
||||
for i in 0..50 {
|
||||
sim.observe(0, format!("l0-{}", i).as_bytes(), format!("l0-p{}", i).as_bytes());
|
||||
sim.observe(1, format!("l1-{}", i).as_bytes(), format!("l1-p{}", i).as_bytes());
|
||||
sim.observe(
|
||||
0,
|
||||
format!("l0-{}", i).as_bytes(),
|
||||
format!("l0-p{}", i).as_bytes(),
|
||||
);
|
||||
sim.observe(
|
||||
1,
|
||||
format!("l1-{}", i).as_bytes(),
|
||||
format!("l1-p{}", i).as_bytes(),
|
||||
);
|
||||
}
|
||||
sim.run_until(60_000);
|
||||
|
||||
|
||||
+25
-27
@@ -19,14 +19,7 @@ use tines_sim::{Contact, ContactTrace, Sim, WorldConfig};
|
||||
/// A fixed set of short observation topics the tests reuse so the generated
|
||||
/// observations round-trip cleanly through the core (`MAX_TOPIC` is 64 bytes).
|
||||
const TOPICS: [&[u8]; 8] = [
|
||||
b"temp",
|
||||
b"humid",
|
||||
b"door",
|
||||
b"motion",
|
||||
b"light",
|
||||
b"batt",
|
||||
b"press",
|
||||
b"power",
|
||||
b"temp", b"humid", b"door", b"motion", b"light", b"batt", b"press", b"power",
|
||||
];
|
||||
|
||||
/// A single contact window over `nodes` nodes. `start` is bounded well below the
|
||||
@@ -44,28 +37,28 @@ fn contact(nodes: usize) -> impl Strategy<Value = Contact> {
|
||||
0usize..nodes,
|
||||
0usize..nodes.saturating_sub(1),
|
||||
)
|
||||
.prop_map(|(start, duration, a, b_prime)| {
|
||||
let mut b = b_prime;
|
||||
if b >= a {
|
||||
b += 1;
|
||||
}
|
||||
Contact {
|
||||
start_ms: start,
|
||||
end_ms: start + duration,
|
||||
a,
|
||||
b,
|
||||
}
|
||||
})
|
||||
.prop_map(|(start, duration, a, b_prime)| {
|
||||
let mut b = b_prime;
|
||||
if b >= a {
|
||||
b += 1;
|
||||
}
|
||||
Contact {
|
||||
start_ms: start,
|
||||
end_ms: start + duration,
|
||||
a,
|
||||
b,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// A single observation: a topic drawn from [`TOPICS`] and a random 1..=64 byte
|
||||
/// payload.
|
||||
fn observation() -> impl Strategy<Value = (Vec<u8>, Vec<u8>)> {
|
||||
(
|
||||
0u32..8u32,
|
||||
proptest::collection::vec(any::<u8>(), 1..=64usize),
|
||||
)
|
||||
.prop_map(|(topic_idx, payload)| (TOPICS[topic_idx as usize].to_vec(), payload))
|
||||
(
|
||||
0u32..8u32,
|
||||
proptest::collection::vec(any::<u8>(), 1..=64usize),
|
||||
)
|
||||
.prop_map(|(topic_idx, payload)| (TOPICS[topic_idx as usize].to_vec(), payload))
|
||||
}
|
||||
|
||||
/// A generated scenario: a node count, a contact trace over those nodes, and a
|
||||
@@ -93,8 +86,13 @@ fn scenario() -> impl Strategy<Value = Scenario> {
|
||||
|
||||
/// A 3-node observation list, reused by the order-independence test.
|
||||
fn observations_3nodes() -> impl Strategy<Value = Vec<(usize, Vec<u8>, Vec<u8>)>> {
|
||||
proptest::collection::vec(observation(), 3..=3)
|
||||
.prop_map(|per_node| per_node.into_iter().enumerate().map(|(n, o)| (n, o.0, o.1)).collect())
|
||||
proptest::collection::vec(observation(), 3..=3).prop_map(|per_node| {
|
||||
per_node
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(n, o)| (n, o.0, o.1))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// Maps a `u64` seed to one of the six permutations of three slots.
|
||||
|
||||
Reference in New Issue
Block a user