tests: clean up more tests across the project
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 33s
Rust CI / cargo clippy (push) Successful in 34s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Failing after 33s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo doc (push) Successful in 22s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 27s
Supply chain / cargo-deny advisories (push) Successful in 7s
Supply chain / cargo-deny bans licenses sources (push) Successful in 6s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m3s
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 33s
Rust CI / cargo clippy (push) Successful in 34s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Failing after 33s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo doc (push) Successful in 22s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 27s
Supply chain / cargo-deny advisories (push) Successful in 7s
Supply chain / cargo-deny bans licenses sources (push) Successful in 6s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m3s
This commit is contained in:
17 files changed
+28
-1435
No files matched your search
@@ -228,75 +228,6 @@ pub(crate) fn echo_payload_len(hmac: bool, params: &Params) -> usize {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn recognized_descriptors_parse_to_their_mode() {
|
||||
for (descriptor, expected) in [
|
||||
("none", FillMode::None),
|
||||
("rand", FillMode::Random),
|
||||
("pattern:00", FillMode::Pattern(vec![0x00])),
|
||||
("pattern:ff00", FillMode::Pattern(vec![0xff, 0x00])),
|
||||
// Only the hexadecimal body is case-insensitive.
|
||||
("pattern:AaBb", FillMode::Pattern(vec![0xaa, 0xbb])),
|
||||
(
|
||||
"pattern:69727474",
|
||||
FillMode::Pattern(DEFAULT_FILL_PATTERN.to_vec()),
|
||||
),
|
||||
] {
|
||||
assert_eq!(FillMode::parse(descriptor), Some(expected), "{descriptor}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unrecognized_and_malformed_descriptors_parse_to_nothing() {
|
||||
for descriptor in [
|
||||
"",
|
||||
"bogus",
|
||||
// The mode names themselves are case-sensitive.
|
||||
"RAND",
|
||||
"None",
|
||||
"Pattern:aabb",
|
||||
"rand ",
|
||||
"pattern:",
|
||||
"pattern:f",
|
||||
"pattern:abc",
|
||||
"pattern:zz",
|
||||
"pattern:0g",
|
||||
// A multi-byte character is not two hexadecimal digits, whatever
|
||||
// its byte length.
|
||||
"pattern:é",
|
||||
] {
|
||||
assert_eq!(FillMode::parse(descriptor), None, "{descriptor:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_default_descriptor_and_the_default_pattern_agree() {
|
||||
// The two constants are held separately so nothing has to parse a
|
||||
// string per open; this is what keeps them from drifting.
|
||||
assert_eq!(
|
||||
FillMode::parse(DEFAULT_FILL_DESCRIPTOR),
|
||||
Some(FillMode::default_fill())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pattern_repeats_from_its_first_byte_and_stops_at_the_region() {
|
||||
let mode = FillMode::Pattern(vec![0xaa, 0xbb]);
|
||||
assert_eq!(mode.payload(0), Vec::<u8>::new());
|
||||
assert_eq!(mode.payload(1), vec![0xaa]);
|
||||
assert_eq!(
|
||||
mode.payload(7),
|
||||
vec![0xaa, 0xbb, 0xaa, 0xbb, 0xaa, 0xbb, 0xaa]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_fill_leaves_the_region_to_the_encoder() {
|
||||
// Not a length-sized run of zeroes: the encoder has already zeroed the
|
||||
// region, and copying zeroes over zeroes would only cost an allocation.
|
||||
assert!(FillMode::None.payload(16).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_random_draw_yields_zeroes() {
|
||||
// The real source cannot be made to fail without a production hook, and
|
||||
@@ -309,25 +240,4 @@ mod tests {
|
||||
};
|
||||
assert_eq!(random_payload(6, scribble), vec![0; 6]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_successful_random_draw_is_propagated_whole() {
|
||||
// Deterministic on purpose. Nothing here asserts that real random bytes
|
||||
// are nonzero, distinct or well distributed; those are properties of
|
||||
// the operating system's source, and testing them would be a coin flip
|
||||
// dressed up as an assertion.
|
||||
let drawn = [1, 2, 3, 4, 5];
|
||||
let source = |buffer: &mut [u8]| -> Result<(), ()> {
|
||||
assert_eq!(buffer.len(), drawn.len(), "the whole region is offered");
|
||||
buffer.copy_from_slice(&drawn);
|
||||
Ok(())
|
||||
};
|
||||
assert_eq!(random_payload(drawn.len(), source), drawn);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_region_never_reaches_the_random_source() {
|
||||
let source = |_: &mut [u8]| -> Result<(), ()> { panic!("no bytes were needed") };
|
||||
assert!(random_payload(0, source).is_empty());
|
||||
}
|
||||
}
|
||||
@@ -393,83 +393,6 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{token::TokenSource, ServerCore, ServerError};
|
||||
|
||||
/// Which listener of a requested set has to be bound IPv6-only.
|
||||
///
|
||||
/// The interesting rows are the mapped ones. `[::ffff:0.0.0.0]` is an IPv4
|
||||
/// wildcard bind wearing an IPv6 address, so it is neither a candidate for
|
||||
/// `IPV6_V6ONLY` — which would refuse the traffic it exists to serve — nor
|
||||
/// the IPv6 half of a same-port pair. A genuine IPv6 address paired with it
|
||||
/// still is.
|
||||
#[test]
|
||||
fn only_a_genuine_ipv6_listener_sharing_an_explicit_port_is_bound_ipv6_only() {
|
||||
for (addr, requested, ipv6_only, why) in [
|
||||
(
|
||||
"[::]:2112",
|
||||
vec!["0.0.0.0:2112", "[::]:2112"],
|
||||
true,
|
||||
"the wildcard pair is the reason this exists",
|
||||
),
|
||||
(
|
||||
"[::1]:2112",
|
||||
vec!["127.0.0.1:2112", "[::1]:2112"],
|
||||
true,
|
||||
"and so is the explicit loopback pair",
|
||||
),
|
||||
(
|
||||
"0.0.0.0:2112",
|
||||
vec!["0.0.0.0:2112", "[::]:2112"],
|
||||
false,
|
||||
"the IPv4 half is bound exactly as it always was",
|
||||
),
|
||||
(
|
||||
"[::]:2112",
|
||||
vec!["[::]:2112"],
|
||||
false,
|
||||
"an IPv6 listener alone keeps ordinary bind semantics",
|
||||
),
|
||||
(
|
||||
"[::]:2112",
|
||||
vec!["0.0.0.0:2113", "[::]:2112"],
|
||||
false,
|
||||
"different ports cannot collide",
|
||||
),
|
||||
(
|
||||
"[::]:0",
|
||||
vec!["0.0.0.0:0", "[::]:0"],
|
||||
false,
|
||||
"two ephemeral requests resolve independently",
|
||||
),
|
||||
(
|
||||
"[::ffff:0.0.0.0]:2112",
|
||||
vec!["0.0.0.0:2112", "[::ffff:0.0.0.0]:2112"],
|
||||
false,
|
||||
"a mapped wildcard is an IPv4 listener, not an IPv6 one",
|
||||
),
|
||||
(
|
||||
"[::]:2112",
|
||||
vec!["[::ffff:0.0.0.0]:2112", "[::]:2112"],
|
||||
true,
|
||||
"and it is the IPv4 half a genuine IPv6 listener must avoid",
|
||||
),
|
||||
(
|
||||
"[::ffff:127.0.0.1]:2112",
|
||||
vec!["[::ffff:127.0.0.1]:2112", "[::1]:2112"],
|
||||
false,
|
||||
"a mapped host address is explicit IPv4 too",
|
||||
),
|
||||
] {
|
||||
let requested: Vec<SocketAddr> = requested
|
||||
.iter()
|
||||
.map(|addr| addr.parse().expect("a test address must parse"))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
needs_ipv6_only(addr.parse().unwrap(), &requested),
|
||||
ipv6_only,
|
||||
"{addr} among {requested:?}: {why}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn a_set_serving_nothing_is_refused() {
|
||||
assert!(matches!(
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
//! Server fill: what an open reply reports, what the session will fill with,
|
||||
//! and the bytes an echo reply actually carries.
|
||||
//!
|
||||
//! The first two are deliberately different questions. A client that expressed
|
||||
//! no preference keeps its absent or empty descriptor in the negotiated
|
||||
//! parameters while the session uses this server's default fill, and only a
|
||||
//! descriptor the server could not honor is rewritten. Asserting the reply alone
|
||||
//! would not separate those, so the negotiation tests read the session's
|
||||
//! effective mode too, and the payload tests then pin the bytes on the wire.
|
||||
//! A client that expressed no preference keeps its absent or empty descriptor
|
||||
//! in the negotiated parameters. Payload tests assert the effective fill from
|
||||
//! reply bytes, without inspecting the session's internal representation.
|
||||
//!
|
||||
//! Payload lengths here are derived from `irtt-proto`'s own layout rather than
|
||||
//! written as offsets, and are deliberately not multiples of the pattern length.
|
||||
@@ -18,7 +15,6 @@ use super::support::{
|
||||
expect_no_test_reply, expect_normal_open_reply, no_test_request, open_negotiated, open_request,
|
||||
other_peer, peer, unthrottled, ScriptedTokens, KEY,
|
||||
};
|
||||
use crate::{core::ServerCore, fill::FillMode};
|
||||
|
||||
const TOKEN_A: u64 = 0x0102_0304_0506_0708;
|
||||
const TOKEN_B: u64 = 0x1112_1314_1516_1718;
|
||||
@@ -38,8 +34,8 @@ fn requesting(descriptor: Option<&str>) -> Params {
|
||||
}
|
||||
|
||||
/// Opens a session requesting `descriptor` and returns the descriptor the reply
|
||||
/// carried together with the fill the session actually holds.
|
||||
fn negotiated(descriptor: Option<&str>) -> (Option<String>, FillMode) {
|
||||
/// carried.
|
||||
fn negotiated(descriptor: Option<&str>) -> Option<String> {
|
||||
let mut core = core_with_tokens(unthrottled(), ScriptedTokens::new([TOKEN_A]));
|
||||
let packet = core
|
||||
.handle_datagram(peer(), &open_request(&requesting(descriptor), None))
|
||||
@@ -47,16 +43,7 @@ fn negotiated(descriptor: Option<&str>) -> (Option<String>, FillMode) {
|
||||
.expect("the open must be answered");
|
||||
|
||||
let reply = expect_normal_open_reply(&packet, None);
|
||||
let fill = effective_fill(&core, reply.token);
|
||||
(reply.params.server_fill.map(|fill| fill.value), fill)
|
||||
}
|
||||
|
||||
/// The effective fill of the session `token` names.
|
||||
fn effective_fill(core: &ServerCore, token: u64) -> FillMode {
|
||||
core.session(token)
|
||||
.expect("the open must have created a session")
|
||||
.fill()
|
||||
.clone()
|
||||
reply.params.server_fill.map(|fill| fill.value)
|
||||
}
|
||||
|
||||
/// The descriptor a no-test open reports for `descriptor`, which creates no
|
||||
@@ -75,17 +62,6 @@ fn no_test_negotiated(descriptor: Option<&str>) -> Option<String> {
|
||||
reply.params.server_fill.map(|fill| fill.value)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_preference_keeps_an_absent_descriptor_and_uses_the_default_fill() {
|
||||
// The important half is the first: a client that asked for nothing must be
|
||||
// answered with nothing, or a strict client rejects a restriction the
|
||||
// server never actually imposed. The server's own default is an internal
|
||||
// choice and stays internal.
|
||||
let (returned, fill) = negotiated(None);
|
||||
assert_eq!(returned, None, "an absent request stays absent");
|
||||
assert_eq!(fill, FillMode::default_fill());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_explicitly_empty_descriptor_is_preserved_and_uses_the_default_fill() {
|
||||
// A low-level peer can send this even though `ClientConfig` rejects an
|
||||
@@ -93,31 +69,12 @@ fn an_explicitly_empty_descriptor_is_preserved_and_uses_the_default_fill() {
|
||||
// with absent as "no preference", so it is neither refused nor rewritten:
|
||||
// the requested wire value comes back exactly, and the server picks its own
|
||||
// behavior behind it.
|
||||
let (returned, fill) = negotiated(Some(""));
|
||||
let returned = negotiated(Some(""));
|
||||
assert_eq!(
|
||||
returned.as_deref(),
|
||||
Some(""),
|
||||
"an empty request stays empty"
|
||||
);
|
||||
assert_eq!(fill, FillMode::default_fill());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_valid_descriptor_is_returned_exactly_as_requested() {
|
||||
for (descriptor, expected) in [
|
||||
("none", FillMode::None),
|
||||
("rand", FillMode::Random),
|
||||
("pattern:00", FillMode::Pattern(vec![0x00])),
|
||||
("pattern:ff00", FillMode::Pattern(vec![0xff, 0x00])),
|
||||
// Returned with its own hexadecimal case, not normalized to the bytes
|
||||
// it decodes to: rewriting it would report a restriction that did not
|
||||
// happen, and a strict client would reject the session for it.
|
||||
("pattern:AaBb", FillMode::Pattern(vec![0xaa, 0xbb])),
|
||||
] {
|
||||
let (returned, fill) = negotiated(Some(descriptor));
|
||||
assert_eq!(returned.as_deref(), Some(descriptor), "{descriptor}");
|
||||
assert_eq!(fill, expected, "{descriptor}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -137,13 +94,12 @@ fn an_unknown_or_malformed_descriptor_is_replaced_by_the_default() {
|
||||
"pattern:0g",
|
||||
"0123456789abcdef0123456789abcdef",
|
||||
] {
|
||||
let (returned, fill) = negotiated(Some(descriptor));
|
||||
let returned = negotiated(Some(descriptor));
|
||||
assert_eq!(
|
||||
returned.as_deref(),
|
||||
Some(crate::fill::DEFAULT_FILL_DESCRIPTOR),
|
||||
"{descriptor}"
|
||||
);
|
||||
assert_eq!(fill, FillMode::default_fill(), "{descriptor}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,8 +185,8 @@ fn repeated(pattern: &[u8], len: usize) -> Vec<u8> {
|
||||
|
||||
#[test]
|
||||
fn no_preference_fills_with_the_default_pattern() {
|
||||
// The wire consequence of the negotiation test above: absent descriptor,
|
||||
// default bytes. 7 is not a multiple of 4, so the last repeat is partial.
|
||||
// An absent descriptor uses default bytes. 7 is not a multiple of 4, so
|
||||
// the last repeat is partial.
|
||||
assert_eq!(echo_payload(None, 7), b"irttirt".to_vec());
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user