aboutsummaryrefslogtreecommitdiffstats
path: root/src/simtest
diff options
context:
space:
mode:
Diffstat (limited to 'src/simtest')
-rw-r--r--src/simtest/reach.rs111
1 files changed, 109 insertions, 2 deletions
diff --git a/src/simtest/reach.rs b/src/simtest/reach.rs
index f501aef..eca0a47 100644
--- a/src/simtest/reach.rs
+++ b/src/simtest/reach.rs
@@ -1,5 +1,6 @@
-//! Whom neighbours reach (`swarm::reach`): told in STATUSes, as measured; and the
-//! writers' trees built on it (`swarm::tree`).
+//! Whom neighbours reach (`swarm::reach`): told in STATUSes, as measured, and kept for
+//! the session; and the writers' trees built on it (`swarm::tree`), which changes go
+//! along however they came.
use std::time::Duration;
@@ -83,3 +84,109 @@ async fn writer_trees_go_around_links_down() {
)
.await;
}
+
+/// What `node` counted: pulls started, changes passed on along writers' trees.
+fn counted(node: &AppState) -> (u64, u64) {
+ let relay = node.stats.snapshot().relay;
+ (relay.pulls, relay.tree_forwards)
+}
+
+/// x, b, c and d, one full mesh, but nothing passes between c and x, nor between c and
+/// d: c's parent in x's tree is b, the only member it reaches. And x's sync messages to
+/// b are lost, so b gets x's write pulled from d (after d's slow round), not along the
+/// tree: pulled, it says nothing of whose; its author does, so b passes it on to c along
+/// x's tree all the same, and c pulls nothing.
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+async fn pulled_changes_go_along_their_writers_trees() {
+ // Long, so x's own STATUS (every third keepalive) doesn't make b pull from x, whose
+ // answers are lost, before d's slow round.
+ let slow = |config: &mut crate::node::NodeConfig| {
+ config.timing = crate::state::Timing::new(
+ Duration::from_secs(5),
+ super::SYNC_REPLY_TIMEOUT,
+ crate::transport::sim::DELIVERY_LIMIT,
+ );
+ };
+ let sim = Sim::with_identity(68);
+ let cut = crate::transport::sim::LinkParams {
+ loss: 1.0,
+ ..Default::default()
+ };
+ for (from, to) in [("x", "c"), ("c", "x"), ("d", "c"), ("c", "d")] {
+ sim.net.set_link(from, to, cut.clone());
+ }
+ sim.net.set_link(
+ "x",
+ "b",
+ crate::transport::sim::LinkParams {
+ drop: vec![crate::transport::MessageKind::Sync],
+ ..Default::default()
+ },
+ );
+ let b = sim.start_on_with("b", &["sim"], &[], Start::Genesis, slow);
+ let c = sim.start_on_with("c", &["sim"], &["b"], Start::Join, slow);
+ let d = sim.start_on_with("d", &["sim"], &["b"], Start::Join, slow);
+ let x = sim.start_on_with("x", &["sim"], &["b"], Start::Join, slow);
+ sim.wait_ready(&[&b, &c, &d, &x], SOON).await;
+ sim.wait_converged(&[&b, &c, &d, &x], SOON * 2).await;
+ let (b, c, x) = (&b, &c, &x);
+ let writer = topology::pair_key(x.local_id());
+ sim.wait_until("c under b in x's tree", SOON, move || async move {
+ tree::children(b, writer).await == [c.local_id().to_owned()]
+ })
+ .await;
+
+ let (c_pulls, _) = counted(c);
+ let (_, b_forwards) = counted(b);
+ super::write(x, "y", serde_json::json!(1)).await;
+ sim.wait_until("c has x's write", SOON, move || async move {
+ super::read(c, "y").await == Some(serde_json::json!(1))
+ })
+ .await;
+ assert!(counted(b).1 > b_forwards, "b passed it on along x's tree");
+ assert_eq!(counted(c).0, c_pulls, "c pulled nothing");
+}
+
+/// x, b and c, nothing between x and c: x's changes reach c through b, along x's tree.
+/// x writes every 100 ms for 4 s, many keepalive intervals: the pairs are busy, so no
+/// keepalive goes, and no STATUS. b still knows whom c reaches (it said so once, and
+/// nothing changed), and passes every write on to c: c pulls none.
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+async fn trees_hold_through_continuous_writes() {
+ let sim = Sim::with_identity(69);
+ let cut = crate::transport::sim::LinkParams {
+ loss: 1.0,
+ ..Default::default()
+ };
+ sim.net.set_link("x", "c", cut.clone());
+ sim.net.set_link("c", "x", cut);
+ let b = sim.start_on("b", &["sim"], &[], Start::Genesis);
+ let c = sim.start_on("c", &["sim"], &["b"], Start::Join);
+ let x = sim.start_on("x", &["sim"], &["b"], Start::Join);
+ sim.wait_ready(&[&b, &c, &x], SOON).await;
+ sim.wait_converged(&[&b, &c, &x], SOON).await;
+ let (b, c, x) = (&b, &c, &x);
+ let writer = topology::pair_key(x.local_id());
+ sim.wait_until("c under b in x's tree", SOON, move || async move {
+ heard(b, c, b).await && tree::children(b, writer).await == [c.local_id().to_owned()]
+ })
+ .await;
+
+ let (c_pulls, _) = counted(c);
+ let (_, b_forwards) = counted(b);
+ let writes = 40;
+ for i in 0..writes {
+ super::write(x, &format!("w/{i}"), serde_json::json!(i)).await;
+ tokio::time::sleep(Duration::from_millis(100)).await;
+ }
+ sim.wait_until("c has x's last write", SOON, move || async move {
+ super::read(c, &format!("w/{}", writes - 1)).await == Some(serde_json::json!(writes - 1))
+ })
+ .await;
+ // Some may go together, if b had several to pass on at once.
+ assert!(
+ counted(b).1 >= b_forwards + writes as u64 / 2,
+ "b passed the writes on along x's tree"
+ );
+ assert_eq!(counted(c).0, c_pulls, "c pulled nothing");
+}