chore: ci script and README

This commit is contained in:
2026-10-17 21:48:37 -07:00
parent 4706004f47
commit ab9b892d94
19 changed files with 468 additions and 137 deletions
+3 -3
View File
@@ -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};
+6 -1
View File
@@ -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
+1 -2
View File
@@ -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
View File
@@ -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());
+37 -8
View File
@@ -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
View File
@@ -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.