diff --git a/neqo-transport/Cargo.toml b/neqo-transport/Cargo.toml index b1d86fc789..00c46eb37b 100644 --- a/neqo-transport/Cargo.toml +++ b/neqo-transport/Cargo.toml @@ -16,7 +16,7 @@ qlog = { git = "https://github.com/cloudflare/quiche", rev = "09ea4b244096a01307 smallvec = "1.11.1" [dev-dependencies] -criterion = "0.5.1" +criterion = { version = "0.5.1", features = ["html_reports"] } enum-map = "2.7" test-fixture = { path = "../test-fixture" } @@ -25,6 +25,11 @@ bench = [] deny-warnings = [] fuzzing = ["neqo-crypto/fuzzing"] +[[bench]] +name = "transfer" +harness = false +required-features = ["bench"] + [[bench]] name = "rx_stream_orderer" harness = false diff --git a/neqo-transport/benches/transfer.rs b/neqo-transport/benches/transfer.rs new file mode 100644 index 0000000000..59f0264a98 --- /dev/null +++ b/neqo-transport/benches/transfer.rs @@ -0,0 +1,64 @@ +use std::time::Duration; + +use criterion::{criterion_group, criterion_main, BatchSize::SmallInput, Criterion}; +use test_fixture::{ + boxed, + sim::{ + connection::{ConnectionNode, ReceiveData, SendData}, + network::{Delay, TailDrop}, + Simulator, + }, +}; + +const ZERO: Duration = Duration::from_millis(0); +const JITTER: Duration = Duration::from_millis(10); +const TRANSFER_AMOUNT: usize = 1 << 22; // 4Mbyte + +fn benchmark_transfer(c: &mut Criterion, label: &str, seed: Option>) { + c.bench_function(label, |b| { + b.iter_batched( + || { + let nodes = boxed![ + ConnectionNode::default_client(boxed![SendData::new(TRANSFER_AMOUNT)]), + TailDrop::dsl_uplink(), + Delay::new(ZERO..JITTER), + ConnectionNode::default_server(boxed![ReceiveData::new(TRANSFER_AMOUNT)]), + TailDrop::dsl_downlink(), + Delay::new(ZERO..JITTER), + ]; + let mut sim = Simulator::new(label, nodes); + if let Some(seed) = &seed { + sim.seed_str(seed); + } + sim.setup() + }, + |sim| { + sim.run(); + }, + SmallInput, + ) + }); +} + +fn benchmark_transfer_variable(c: &mut Criterion) { + benchmark_transfer( + c, + "Run multiple transfers with varying seeds", + std::env::var("SIMULATION_SEED").ok(), + ); +} + +fn benchmark_transfer_fixed(c: &mut Criterion) { + benchmark_transfer( + c, + "Run multiple transfers with the same seed", + Some("62df6933ba1f543cece01db8f27fb2025529b27f93df39e19f006e1db3b8c843"), + ); +} + +criterion_group! { + name = transfer; + config = Criterion::default().warm_up_time(Duration::from_secs(5)).measurement_time(Duration::from_secs(15)); + targets = benchmark_transfer_variable, benchmark_transfer_fixed +} +criterion_main!(transfer); diff --git a/neqo-transport/tests/network.rs b/neqo-transport/tests/network.rs index 8c388457c5..d7a537159b 100644 --- a/neqo-transport/tests/network.rs +++ b/neqo-transport/tests/network.rs @@ -7,15 +7,17 @@ #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn(clippy::pedantic)] -mod sim; - use std::{ops::Range, time::Duration}; use neqo_transport::{ConnectionError, ConnectionParameters, Error, State}; -use sim::{ - connection::{ConnectionNode, ReachState, ReceiveData, SendData}, - network::{Delay, Drop, TailDrop}, - Simulator, +use test_fixture::{ + boxed, + sim::{ + connection::{ConnectionNode, ReachState, ReceiveData, SendData}, + network::{Delay, Drop, TailDrop}, + Simulator, + }, + simulate, }; /// The amount of transfer. Much more than this takes a surprising amount of time. @@ -32,26 +34,28 @@ const fn weeks(m: u32) -> Duration { simulate!( connect_direct, [ - ConnectionNode::default_client(boxed![ReachState::new(State::Confirmed)]), - ConnectionNode::default_server(boxed![ReachState::new(State::Confirmed)]), + ConnectionNode::new_client( + ConnectionParameters::default(), + [], + boxed![ReachState::new(State::Confirmed)] + ), + ConnectionNode::new_server( + ConnectionParameters::default(), + [], + boxed![ReachState::new(State::Confirmed)] + ), ] ); simulate!( idle_timeout, [ - ConnectionNode::default_client(boxed![ - ReachState::new(State::Confirmed), - ReachState::new(State::Closed(ConnectionError::Transport( - Error::IdleTimeout - ))) - ]), - ConnectionNode::default_server(boxed![ - ReachState::new(State::Confirmed), - ReachState::new(State::Closed(ConnectionError::Transport( - Error::IdleTimeout - ))) - ]), + ConnectionNode::default_client(boxed![ReachState::new(State::Closed( + ConnectionError::Transport(Error::IdleTimeout) + ))]), + ConnectionNode::default_server(boxed![ReachState::new(State::Closed( + ConnectionError::Transport(Error::IdleTimeout) + ))]), ] ); @@ -60,23 +64,19 @@ simulate!( [ ConnectionNode::new_client( ConnectionParameters::default().idle_timeout(weeks(1000)), - boxed![ - ReachState::new(State::Confirmed), - ReachState::new(State::Closed(ConnectionError::Transport( - Error::IdleTimeout - ))) - ] + boxed![ReachState::new(State::Confirmed),], + boxed![ReachState::new(State::Closed(ConnectionError::Transport( + Error::IdleTimeout + )))] ), Delay::new(weeks(6)..weeks(6)), Drop::percentage(10), ConnectionNode::new_server( ConnectionParameters::default().idle_timeout(weeks(1000)), - boxed![ - ReachState::new(State::Confirmed), - ReachState::new(State::Closed(ConnectionError::Transport( - Error::IdleTimeout - ))) - ] + boxed![ReachState::new(State::Confirmed),], + boxed![ReachState::new(State::Closed(ConnectionError::Transport( + Error::IdleTimeout + )))] ), Delay::new(weeks(8)..weeks(8)), Drop::percentage(10), @@ -94,9 +94,17 @@ simulate!( simulate!( connect_fixed_rtt, [ - ConnectionNode::default_client(boxed![ReachState::new(State::Confirmed)]), + ConnectionNode::new_client( + ConnectionParameters::default(), + [], + boxed![ReachState::new(State::Confirmed)] + ), Delay::new(DELAY..DELAY), - ConnectionNode::default_server(boxed![ReachState::new(State::Confirmed)]), + ConnectionNode::new_server( + ConnectionParameters::default(), + [], + boxed![ReachState::new(State::Confirmed)] + ), Delay::new(DELAY..DELAY), ], ); @@ -104,22 +112,38 @@ simulate!( simulate!( connect_taildrop_jitter, [ - ConnectionNode::default_client(boxed![ReachState::new(State::Confirmed)]), - TailDrop::dsl_uplink(), - Delay::new(ZERO..JITTER), - ConnectionNode::default_server(boxed![ReachState::new(State::Confirmed)]), + ConnectionNode::new_client( + ConnectionParameters::default(), + [], + boxed![ReachState::new(State::Confirmed)] + ), TailDrop::dsl_downlink(), Delay::new(ZERO..JITTER), + ConnectionNode::new_server( + ConnectionParameters::default(), + [], + boxed![ReachState::new(State::Confirmed)] + ), + TailDrop::dsl_uplink(), + Delay::new(ZERO..JITTER), ], ); simulate!( connect_taildrop, [ - ConnectionNode::default_client(boxed![ReachState::new(State::Confirmed)]), - TailDrop::dsl_uplink(), - ConnectionNode::default_server(boxed![ReachState::new(State::Confirmed)]), + ConnectionNode::new_client( + ConnectionParameters::default(), + [], + boxed![ReachState::new(State::Confirmed)] + ), TailDrop::dsl_downlink(), + ConnectionNode::new_server( + ConnectionParameters::default(), + [], + boxed![ReachState::new(State::Confirmed)] + ), + TailDrop::dsl_uplink(), ], ); @@ -139,9 +163,9 @@ simulate!( transfer_taildrop, [ ConnectionNode::default_client(boxed![SendData::new(TRANSFER_AMOUNT)]), - TailDrop::dsl_uplink(), - ConnectionNode::default_server(boxed![ReceiveData::new(TRANSFER_AMOUNT)]), TailDrop::dsl_downlink(), + ConnectionNode::default_server(boxed![ReceiveData::new(TRANSFER_AMOUNT)]), + TailDrop::dsl_uplink(), ], ); @@ -149,10 +173,10 @@ simulate!( transfer_taildrop_jitter, [ ConnectionNode::default_client(boxed![SendData::new(TRANSFER_AMOUNT)]), - TailDrop::dsl_uplink(), + TailDrop::dsl_downlink(), Delay::new(ZERO..JITTER), ConnectionNode::default_server(boxed![ReceiveData::new(TRANSFER_AMOUNT)]), - TailDrop::dsl_downlink(), + TailDrop::dsl_uplink(), Delay::new(ZERO..JITTER), ], ); diff --git a/test-fixture/Cargo.toml b/test-fixture/Cargo.toml index f0feace31d..6dfe8d7f4c 100644 --- a/test-fixture/Cargo.toml +++ b/test-fixture/Cargo.toml @@ -17,4 +17,4 @@ neqo-transport = { path = "../neqo-transport" } qlog = { git = "https://github.com/cloudflare/quiche", rev = "09ea4b244096a013071cfe2175bbf2945fb7f8d1" } [features] -deny-warnings = [] +deny-warnings = [] \ No newline at end of file diff --git a/test-fixture/src/lib.rs b/test-fixture/src/lib.rs index 8635e8a840..2c94767a97 100644 --- a/test-fixture/src/lib.rs +++ b/test-fixture/src/lib.rs @@ -35,6 +35,7 @@ use neqo_transport::{ use qlog::{events::EventImportance, streamer::QlogStreamer}; pub mod assertions; +pub mod sim; /// The path for the database used in tests. pub const NSS_DB_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/db"); diff --git a/neqo-transport/tests/sim/connection.rs b/test-fixture/src/sim/connection.rs similarity index 81% rename from neqo-transport/tests/sim/connection.rs rename to test-fixture/src/sim/connection.rs index 45a5234512..d05979cfca 100644 --- a/neqo-transport/tests/sim/connection.rs +++ b/test-fixture/src/sim/connection.rs @@ -12,13 +12,16 @@ use std::{ time::Instant, }; -use neqo_common::{event::Provider, qdebug, qtrace, Datagram}; +use neqo_common::{event::Provider, qdebug, qinfo, qtrace, Datagram}; use neqo_crypto::AuthenticationStatus; use neqo_transport::{ Connection, ConnectionEvent, ConnectionParameters, Output, State, StreamId, StreamType, }; -use super::{Node, Rng}; +use crate::{ + boxed, + sim::{Node, Rng}, +}; /// The status of the processing of an event. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -33,7 +36,7 @@ pub enum GoalStatus { /// A goal for the connection. /// Goals can be accomplished in any order. -pub trait ConnectionGoal { +pub trait ConnectionGoal: Debug { fn init(&mut self, _c: &mut Connection, _now: Instant) {} /// Perform some processing. fn process(&mut self, _c: &mut Connection, _now: Instant) -> GoalStatus { @@ -47,36 +50,49 @@ pub trait ConnectionGoal { pub struct ConnectionNode { c: Connection, + setup_goals: Vec>, goals: Vec>, } impl ConnectionNode { pub fn new_client( params: ConnectionParameters, + setup: impl IntoIterator>, goals: impl IntoIterator>, ) -> Self { Self { - c: test_fixture::new_client(params), + c: crate::new_client(params), + setup_goals: setup.into_iter().collect(), goals: goals.into_iter().collect(), } } pub fn new_server( params: ConnectionParameters, + setup: impl IntoIterator>, goals: impl IntoIterator>, ) -> Self { Self { - c: test_fixture::new_server(test_fixture::DEFAULT_ALPN, params), + c: crate::new_server(crate::DEFAULT_ALPN, params), + setup_goals: setup.into_iter().collect(), goals: goals.into_iter().collect(), } } pub fn default_client(goals: impl IntoIterator>) -> Self { - Self::new_client(ConnectionParameters::default(), goals) + Self::new_client( + ConnectionParameters::default(), + boxed![ReachState::new(State::Confirmed)], + goals, + ) } pub fn default_server(goals: impl IntoIterator>) -> Self { - Self::new_server(ConnectionParameters::default(), goals) + Self::new_server( + ConnectionParameters::default(), + boxed![ReachState::new(State::Confirmed)], + goals, + ) } #[allow(dead_code)] @@ -89,13 +105,20 @@ impl ConnectionNode { self.goals.push(goal); } + /// On the first call to this method, the setup goals will turn into the active goals. + /// On the second call, they will be swapped back and the main goals will run. + fn setup_goals(&mut self, now: Instant) { + std::mem::swap(&mut self.goals, &mut self.setup_goals); + for g in &mut self.goals { + g.init(&mut self.c, now); + } + } + /// Process all goals using the given closure and return whether any were active. fn process_goals(&mut self, mut f: F) -> bool where F: FnMut(&mut Box, &mut Connection) -> GoalStatus, { - // Waiting on drain_filter... - // self.goals.drain_filter(|g| f(g, &mut self.c, &e)).count(); let mut active = false; let mut i = 0; while i < self.goals.len() { @@ -114,15 +137,13 @@ impl ConnectionNode { impl Node for ConnectionNode { fn init(&mut self, _rng: Rng, now: Instant) { - for g in &mut self.goals { - g.init(&mut self.c, now); - } + self.setup_goals(now); } - fn process(&mut self, mut d: Option, now: Instant) -> Output { + fn process(&mut self, mut dgram: Option, now: Instant) -> Output { _ = self.process_goals(|goal, c| goal.process(c, now)); loop { - let res = self.c.process(d.take().as_ref(), now); + let res = self.c.process(dgram.take().as_ref(), now); let mut active = false; while let Some(e) = self.c.next_event() { @@ -145,12 +166,18 @@ impl Node for ConnectionNode { } } + fn prepare(&mut self, now: Instant) { + assert!(self.done(), "ConnectionNode::prepare: setup not complete"); + self.setup_goals(now); + assert!(!self.done(), "ConnectionNode::prepare: setup not complete"); + } + fn done(&self) -> bool { self.goals.is_empty() } fn print_summary(&self, test_name: &str) { - println!("{}: {:?}", test_name, self.c.stats()); + qinfo!("{}: {:?}", test_name, self.c.stats()); } } @@ -160,12 +187,15 @@ impl Debug for ConnectionNode { } } +/// A target for a connection that involves reaching a given connection state. #[derive(Debug, Clone)] pub struct ReachState { target: State, } impl ReachState { + /// Create a new instance that intends to reach the indicated state. + #[must_use] pub fn new(target: State) -> Self { Self { target } } @@ -186,13 +216,15 @@ impl ConnectionGoal for ReachState { } } -#[derive(Debug)] +/// A target for a connection that involves sending a given amount of data on the indicated stream. +#[derive(Debug, Clone)] pub struct SendData { remaining: usize, stream_id: Option, } impl SendData { + #[must_use] pub fn new(amount: usize) -> Self { Self { remaining: amount, @@ -248,9 +280,7 @@ impl ConnectionGoal for SendData { match e { ConnectionEvent::SendStreamCreatable { stream_type: StreamType::UniDi, - } - // TODO(mt): remove the second condition when #842 is fixed. - | ConnectionEvent::StateChange(_) => { + } => { self.make_stream(c); GoalStatus::Active } @@ -270,12 +300,13 @@ impl ConnectionGoal for SendData { } /// Receive a prescribed amount of data from any stream. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ReceiveData { remaining: usize, } impl ReceiveData { + #[must_use] pub fn new(amount: usize) -> Self { Self { remaining: amount } } diff --git a/neqo-transport/tests/sim/delay.rs b/test-fixture/src/sim/delay.rs similarity index 99% rename from neqo-transport/tests/sim/delay.rs rename to test-fixture/src/sim/delay.rs index 34cb923084..e66e65f9d8 100644 --- a/neqo-transport/tests/sim/delay.rs +++ b/test-fixture/src/sim/delay.rs @@ -58,6 +58,7 @@ pub struct Delay { } impl Delay { + #[must_use] pub fn new(bounds: Range) -> Self { Self { random: RandomDelay::new(bounds), diff --git a/neqo-transport/tests/sim/drop.rs b/test-fixture/src/sim/drop.rs similarity index 90% rename from neqo-transport/tests/sim/drop.rs rename to test-fixture/src/sim/drop.rs index 629fbf48d3..6529a95d04 100644 --- a/neqo-transport/tests/sim/drop.rs +++ b/test-fixture/src/sim/drop.rs @@ -27,6 +27,7 @@ impl Drop { /// Make a new random drop generator. Each `drop` is called, this generates a /// random value between 0 and `max` (exclusive). If this value is less than /// `threshold` a value of `true` is returned. + #[must_use] pub fn new(threshold: u64, max: u64) -> Self { Self { threshold, @@ -36,11 +37,16 @@ impl Drop { } /// Generate random drops with the given percentage. + #[must_use] pub fn percentage(pct: u8) -> Self { // Multiply by 10 so that the random number generator works more efficiently. Self::new(u64::from(pct) * 10, 1000) } + /// Determine whether or not to drop a packet. + /// # Panics + /// When this is invoked after test configuration has been torn down, + /// such that the RNG is no longer available. pub fn drop(&mut self) -> bool { let mut rng = self.rng.as_ref().unwrap().borrow_mut(); let r = rng.random_from(0..self.max); diff --git a/neqo-transport/tests/sim/mod.rs b/test-fixture/src/sim/mod.rs similarity index 72% rename from neqo-transport/tests/sim/mod.rs rename to test-fixture/src/sim/mod.rs index 9ab9d57a4a..f4b7a52739 100644 --- a/neqo-transport/tests/sim/mod.rs +++ b/test-fixture/src/sim/mod.rs @@ -4,10 +4,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -// Tests with simulated network -#![cfg_attr(feature = "deny-warnings", deny(warnings))] -#![warn(clippy::pedantic)] - +/// Tests with simulated network components. pub mod connection; mod delay; mod drop; @@ -19,6 +16,7 @@ use std::{ cmp::min, convert::TryFrom, fmt::Debug, + ops::{Deref, DerefMut}, rc::Rc, time::{Duration, Instant}, }; @@ -26,9 +24,10 @@ use std::{ use neqo_common::{qdebug, qinfo, qtrace, Datagram, Encoder}; use neqo_transport::Output; use rng::Random; -use test_fixture::{self, now}; use NodeState::{Active, Idle, Waiting}; +use crate::now; + pub mod network { pub use super::{delay::Delay, drop::Drop, taildrop::TailDrop}; } @@ -78,17 +77,21 @@ pub trait Node: Debug { /// Perform processing. This optionally takes a datagram and produces either /// another data, a time that the simulator needs to wait, or nothing. fn process(&mut self, d: Option, now: Instant) -> Output; + /// This is called after setup is complete and before the main processing starts. + fn prepare(&mut self, _now: Instant) {} /// An node can report when it considers itself "done". + /// Prior to calling `prepare`, this should return `true` if it is ready. fn done(&self) -> bool { true } + /// Print out a summary of the state of the node. fn print_summary(&self, _test_name: &str) {} } /// The state of a single node. Nodes will be activated if they are `Active` /// or if the previous node in the loop generated a datagram. Nodes that return /// `true` from `Node::done` will be activated as normal. -#[derive(Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] enum NodeState { /// The node just produced a datagram. It should be activated again as soon as possible. Active, @@ -114,6 +117,19 @@ impl NodeHolder { } } +impl Deref for NodeHolder { + type Target = dyn Node; + fn deref(&self) -> &Self::Target { + self.node.as_ref() + } +} + +impl DerefMut for NodeHolder { + fn deref_mut(&mut self) -> &mut Self::Target { + self.node.as_mut() + } +} + pub struct Simulator { name: String, nodes: Vec, @@ -146,7 +162,8 @@ impl Simulator { } /// Seed from a hex string. - /// Though this is convenient, it panics if this isn't a 64 character hex string. + /// # Panics + /// When the provided string is not 32 bytes of hex (64 characters). pub fn seed_str(&mut self, seed: impl AsRef) { let seed = Encoder::from_hex(seed); self.seed(<[u8; 32]>::try_from(seed.as_ref()).unwrap()); @@ -164,18 +181,8 @@ impl Simulator { next.expect("a node cannot be idle and not done") } - /// Runs the simulation. - pub fn run(mut self) -> Duration { - let start = now(); - let mut now = start; + fn process_loop(&mut self, start: Instant, mut now: Instant) -> Instant { let mut dgram = None; - - for n in &mut self.nodes { - n.node.init(self.rng.clone(), now); - } - println!("{}: seed {}", self.name, self.rng.borrow().seed_str()); - - let real_start = Instant::now(); loop { for n in &mut self.nodes { if dgram.is_none() && !n.ready(now) { @@ -184,7 +191,7 @@ impl Simulator { } qdebug!([self.name], "processing {:?}", n.node); - let res = n.node.process(dgram.take(), now); + let res = n.process(dgram.take(), now); n.state = match res { Output::Datagram(d) => { qtrace!([self.name], " => datagram {}", d.len()); @@ -198,21 +205,14 @@ impl Simulator { } Output::None => { qtrace!([self.name], " => nothing"); - assert!(n.node.done(), "nodes have to be done when they go idle"); + assert!(n.done(), "nodes should be done when they go idle"); Idle } }; } - if self.nodes.iter().all(|n| n.node.done()) { - let real_elapsed = real_start.elapsed(); - println!("{}: real elapsed time: {:?}", self.name, real_elapsed); - let elapsed = now - start; - println!("{}: simulated elapsed time: {:?}", self.name, elapsed); - for n in &self.nodes { - n.node.print_summary(&self.name); - } - return elapsed; + if self.nodes.iter().all(|n| n.done()) { + return now; } if dgram.is_none() { @@ -229,4 +229,66 @@ impl Simulator { } } } + + #[must_use] + pub fn setup(mut self) -> ReadySimulator { + let start = now(); + + qinfo!("{}: seed {}", self.name, self.rng.borrow().seed_str()); + for n in &mut self.nodes { + n.init(self.rng.clone(), start); + } + + let setup_start = Instant::now(); + let now = self.process_loop(start, start); + let setup_time = now - start; + qinfo!( + "{t}: Setup took {wall:?} (wall) {setup_time:?} (simulated)", + t = self.name, + wall = setup_start.elapsed(), + ); + + for n in &mut self.nodes { + n.prepare(now); + } + + ReadySimulator { + sim: self, + start, + now, + } + } + + /// Runs the simulation. + /// # Panics + /// When sanity checks fail in unexpected ways; this is a testing function after all. + pub fn run(self) { + self.setup().run(); + } + + fn print_summary(&self) { + for n in &self.nodes { + n.print_summary(&self.name); + } + } +} + +pub struct ReadySimulator { + sim: Simulator, + start: Instant, + now: Instant, +} + +impl ReadySimulator { + pub fn run(mut self) { + let real_start = Instant::now(); + let end = self.sim.process_loop(self.start, self.now); + let sim_time = end - self.now; + qinfo!( + "{t}: Simulation took {wall:?} (wall) {sim_time:?} (simulated)", + t = self.sim.name, + wall = real_start.elapsed(), + ); + self.sim.print_summary(); + } } diff --git a/neqo-transport/tests/sim/net.rs b/test-fixture/src/sim/net.rs similarity index 100% rename from neqo-transport/tests/sim/net.rs rename to test-fixture/src/sim/net.rs diff --git a/neqo-transport/tests/sim/rng.rs b/test-fixture/src/sim/rng.rs similarity index 92% rename from neqo-transport/tests/sim/rng.rs rename to test-fixture/src/sim/rng.rs index af4f70eb5f..094c5fd791 100644 --- a/neqo-transport/tests/sim/rng.rs +++ b/test-fixture/src/sim/rng.rs @@ -14,6 +14,8 @@ pub struct Random { } impl Random { + #[must_use] + #[allow(clippy::missing_panics_doc)] // These are impossible. pub fn new(seed: [u8; 32]) -> Self { assert!(seed.iter().any(|&x| x != 0)); let mut dec = Decoder::from(&seed); @@ -48,6 +50,7 @@ impl Random { /// Generate a random value from the range. /// If the range is empty or inverted (`range.start > range.end`), then /// this returns the value of `range.start` without generating any random values. + #[must_use] pub fn random_from(&mut self, range: Range) -> u64 { let max = range.end.saturating_sub(range.start); if max == 0 { @@ -55,7 +58,6 @@ impl Random { } let shift = (max - 1).leading_zeros(); - assert_ne!(max, 0); loop { let r = self.random() >> shift; if r < max { @@ -64,7 +66,8 @@ impl Random { } } - /// Get the seed necessary to continue from this point. + /// Get the seed necessary to continue from the current state of the RNG. + #[must_use] pub fn seed_str(&self) -> String { format!( "{:8x}{:8x}{:8x}{:8x}", diff --git a/neqo-transport/tests/sim/taildrop.rs b/test-fixture/src/sim/taildrop.rs similarity index 95% rename from neqo-transport/tests/sim/taildrop.rs rename to test-fixture/src/sim/taildrop.rs index 26813800c9..c23dae10c6 100644 --- a/neqo-transport/tests/sim/taildrop.rs +++ b/test-fixture/src/sim/taildrop.rs @@ -14,7 +14,7 @@ use std::{ time::{Duration, Instant}, }; -use neqo_common::{qtrace, Datagram}; +use neqo_common::{qinfo, qtrace, Datagram}; use neqo_transport::Output; use super::Node; @@ -23,6 +23,7 @@ use super::Node; const ONE_SECOND_NS: u128 = 1_000_000_000; /// This models a link with a tail drop router at the front of it. +#[derive(Clone)] pub struct TailDrop { /// An overhead associated with each entry. This accounts for /// layer 2, IP, and UDP overheads. @@ -60,6 +61,7 @@ pub struct TailDrop { impl TailDrop { /// Make a new taildrop node with the given rate, queue capacity, and link delay. + #[must_use] pub fn new(rate: usize, capacity: usize, delay: Duration) -> Self { Self { overhead: 64, @@ -80,12 +82,14 @@ impl TailDrop { /// A tail drop queue on a 10Mbps link (approximated to 1 million bytes per second) /// with a fat 32k buffer (about 30ms), and the default forward delay of 50ms. - pub fn dsl_uplink() -> Self { + #[must_use] + pub fn dsl_downlink() -> Self { TailDrop::new(1_000_000, 32_768, Duration::from_millis(50)) } - /// Cut downlink to one fifth of the uplink (2Mbps), and reduce the buffer to 1/4. - pub fn dsl_downlink() -> Self { + /// Cut uplink to one fifth of the downlink (2Mbps), and reduce the buffer to 1/4. + #[must_use] + pub fn dsl_uplink() -> Self { TailDrop::new(200_000, 8_192, Duration::from_millis(50)) } @@ -174,9 +178,13 @@ impl Node for TailDrop { } fn print_summary(&self, test_name: &str) { - println!( + qinfo!( "{}: taildrop: rx {} drop {} tx {} maxq {}", - test_name, self.received, self.dropped, self.delivered, self.maxq, + test_name, + self.received, + self.dropped, + self.delivered, + self.maxq, ); } }