From 439eaa46c0c759d6f0226704240cfc73dd9eeb7a Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Fri, 13 Feb 2026 19:04:01 +0200 Subject: [PATCH] test: add unit tests for config, errors, and records --- Cargo.lock | 20 +++-- Cargo.toml | 17 +++-- common/Cargo.toml | 1 + common/src/lib.rs | 72 +++++++++++++----- runner/Cargo.toml | 1 + runner/src/config/mod.rs | 153 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 233 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90ff8ed..800b145 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,6 +215,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "claims" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba18ee93d577a8428902687bcc2b6b45a56b1981a1f6d779731c86cc4c5db18" + [[package]] name = "clap" version = "4.5.54" @@ -275,6 +281,7 @@ name = "common" version = "0.1.0" dependencies = [ "cargo-husky", + "claims", "miette", "rcgen", "rustls", @@ -769,6 +776,7 @@ dependencies = [ name = "runner" version = "0.1.0" dependencies = [ + "claims", "clap", "common", "miette", @@ -1164,9 +1172,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "1.0.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "bbe30f93627849fa362d4a602212d41bb237dc2bd0f8ba0b2ce785012e124220" dependencies = [ "indexmap", "serde_core", @@ -1179,18 +1187,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.0.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" dependencies = [ "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 696d2e0..5588420 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,17 +9,13 @@ edition = "2024" license = "MIT OR Apache-2.0" [workspace.dependencies] +common = { path = "common" } + aws-lc-rs = "1" base64 = "0.22" -cargo-husky = { version = "1", default-features = false, features = [ - "user-hooks", -] } -claims = "0.8" clap = { version = "4.5", features = ["derive"] } -common = { path = "common" } miette = { version = "7", features = ["fancy"] } rcgen = "0.14" -rstest = "0.26" rustls = { version = "0.23", default-features = false, features = [ "std", "tls12", @@ -29,15 +25,22 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" strum = { version = "0.27", features = ["derive"] } thiserror = "2" -toml = "0.9" tokio = { version = "1", features = ["full"] } tokio-rustls = { version = "0.26", default-features = false, features = [ "tls12", ] } +toml = "1.0" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } uuid = { version = "1", features = ["v4"] } +# dev +cargo-husky = { version = "1", default-features = false, features = [ + "user-hooks", +] } +claims = "0.8" +rstest = "0.26" + [workspace.lints.clippy] nursery = "warn" pedantic = "warn" diff --git a/common/Cargo.toml b/common/Cargo.toml index c7ac8b0..f3d6f0e 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -5,6 +5,7 @@ authors.workspace = true edition.workspace = true [dependencies] +claims.workspace = true miette.workspace = true rcgen.workspace = true rustls.workspace = true diff --git a/common/src/lib.rs b/common/src/lib.rs index d5faf15..32fface 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,4 +1,4 @@ -//! Common types and utilities for the TLS benchmark harness. +//! Common types and utilities for the TLS benchmark harness pub mod cert; pub mod error; @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use std::fmt; use strum::{Display, EnumString}; -/// TLS key exchange mode. +/// TLS key exchange mode #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, EnumString, Display)] #[strum(serialize_all = "lowercase")] #[serde(rename_all = "lowercase")] @@ -20,18 +20,18 @@ pub enum KeyExchangeMode { X25519Mlkem768, } -/// A single benchmark measurement record, output as NDJSON. +/// A single benchmark measurement record, output as NDJSON #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BenchRecord { - /// Iteration number (0-indexed, excludes warmup). + /// Iteration number (0-indexed, excludes warmup) pub iteration: u64, - /// Key exchange mode used. + /// Key exchange mode used pub mode: KeyExchangeMode, - /// Payload size in bytes. + /// Payload size in bytes pub payload_bytes: u64, - /// Handshake latency in nanoseconds. + /// Handshake latency in nanoseconds pub handshake_ns: u64, - /// Time-to-last-byte in nanoseconds (from connection start). + /// Time-to-last-byte in nanoseconds (from connection start) pub ttlb_ns: u64, } @@ -39,7 +39,7 @@ impl BenchRecord { /// Serialize this record as a single NDJSON line (no trailing newline). /// /// # Errors - /// Returns an error if serialization fails. + /// Returns an error if serialization fails pub fn to_ndjson(&self) -> Result { serde_json::to_string(self) } @@ -56,6 +56,9 @@ impl fmt::Display for BenchRecord { #[cfg(test)] mod tests { + use claims::{assert_err, assert_ok}; + use serde_json::Value; + use super::*; use std::str::FromStr; @@ -68,21 +71,54 @@ mod tests { handshake_ns: 1_000_000, ttlb_ns: 2_000_000, }; - let json = record.to_ndjson().expect("serialization should succeed"); + let json = assert_ok!(record.to_ndjson()); assert!(json.contains(r#""iteration":0"#)); assert!(json.contains(r#""mode":"x25519""#)); assert!(json.contains(r#""payload_bytes":1024"#)); } + #[test] + fn bench_record_roundtrip() { + let original = BenchRecord { + iteration: 42, + mode: KeyExchangeMode::X25519Mlkem768, + payload_bytes: 4096, + handshake_ns: 5_000_000, + ttlb_ns: 10_000_000, + }; + let json = assert_ok!(original.to_ndjson()); + let deserialized = assert_ok!(serde_json::from_str::(&json)); + + assert_eq!(original.iteration, deserialized.iteration); + assert_eq!(original.mode, deserialized.mode); + assert_eq!(original.payload_bytes, deserialized.payload_bytes); + assert_eq!(original.handshake_ns, deserialized.handshake_ns); + assert_eq!(original.ttlb_ns, deserialized.ttlb_ns); + } + #[test] fn key_exchange_mode_from_str() { - assert_eq!( - KeyExchangeMode::from_str("x25519").expect("should parse"), - KeyExchangeMode::X25519 - ); - assert_eq!( - KeyExchangeMode::from_str("x25519mlkem768").expect("should parse"), - KeyExchangeMode::X25519Mlkem768 - ); + let mode = assert_ok!(KeyExchangeMode::from_str("x25519")); + assert_eq!(mode, KeyExchangeMode::X25519); + + let mode = assert_ok!(KeyExchangeMode::from_str("x25519mlkem768")); + assert_eq!(mode, KeyExchangeMode::X25519Mlkem768); + } + + #[test] + fn key_exchange_mode_parse_error() { + assert_err!(KeyExchangeMode::from_str("invalid")); + assert_err!(KeyExchangeMode::from_str("x25519invalid")); + assert_err!(KeyExchangeMode::from_str("")); + } + + #[test] + fn key_exchange_mode_serde() { + let json = r#"{"mode":"x25519mlkem768"}"#; + let value = assert_ok!(serde_json::from_str::(json)); + let mode = assert_ok!(serde_json::from_value::( + value["mode"].clone() + )); + assert_eq!(mode, KeyExchangeMode::X25519Mlkem768); } } diff --git a/runner/Cargo.toml b/runner/Cargo.toml index 16db164..a10ab48 100644 --- a/runner/Cargo.toml +++ b/runner/Cargo.toml @@ -17,6 +17,7 @@ toml.workspace = true tracing-subscriber.workspace = true tracing.workspace = true uuid.workspace = true +claims.workspace = true [lints] workspace = true diff --git a/runner/src/config/mod.rs b/runner/src/config/mod.rs index 9d266ee..0e625b7 100644 --- a/runner/src/config/mod.rs +++ b/runner/src/config/mod.rs @@ -83,3 +83,156 @@ impl Config { .unwrap_or(KeyExchangeMode::X25519) } } + +#[cfg(test)] +mod tests { + use super::*; + use claims::assert_ok; + + const VALID_CONFIG: &str = r#" +[[benchmarks]] +mode = "x25519" +payload = 1024 +iters = 100 +warmup = 10 +concurrency = 1 +server = "127.0.0.1:4433" + +[[benchmarks]] +mode = "x25519mlkem768" +payload = 4096 +iters = 50 +warmup = 5 +concurrency = 4 +server = "127.0.0.1:4433" +"#; + + fn get_config_from_str(toml: &str) -> Config { + assert_ok!(toml::from_str::(toml)) + } + + #[test] + fn valid_single_benchmark() { + let toml = r#" +[[benchmarks]] +mode = "x25519" +payload = 1024 +iters = 100 +warmup = 10 +concurrency = 1 +server = "127.0.0.1:4433" +"#; + let config = get_config_from_str(toml); + assert_eq!(config.benchmarks.len(), 1); + assert_eq!(config.benchmarks[0].mode, "x25519"); + assert_eq!(config.benchmarks[0].payload, 1024); + } + + #[test] + fn valid_multiple_benchmarks() { + let config = get_config_from_str(VALID_CONFIG); + assert_eq!(config.benchmarks.len(), 2); + assert_eq!(config.benchmarks[0].mode, "x25519"); + assert_eq!(config.benchmarks[1].mode, "x25519mlkem768"); + } + + #[test] + fn invalid_mode() { + let toml = r#" +[[benchmarks]] +mode = "invalid_mode" +payload = 1024 +iters = 100 +warmup = 10 +concurrency = 1 +server = "127.0.0.1:4433" +"#; + let config = get_config_from_str(toml); + assert!(config.server_mode() == KeyExchangeMode::X25519); // fallback + } + + #[test] + fn payload_zero_validation() { + let toml = r#" +[[benchmarks]] +mode = "x25519" +payload = 0 +iters = 100 +warmup = 10 +concurrency = 1 +server = "127.0.0.1:4433" +"#; + let config = get_config_from_str(toml); + let result = validate_config(&config, toml, std::path::Path::new("test.toml")); + assert!(result.is_err()); + } + + #[test] + fn iters_zero_validation() { + let toml = r#" +[[benchmarks]] +mode = "x25519" +payload = 1024 +iters = 0 +warmup = 10 +concurrency = 1 +server = "127.0.0.1:4433" +"#; + let config = get_config_from_str(toml); + let result = validate_config(&config, toml, std::path::Path::new("test.toml")); + assert!(result.is_err()); + } + + #[test] + fn concurrency_zero_validation() { + let toml = r#" +[[benchmarks]] +mode = "x25519" +payload = 1024 +iters = 100 +warmup = 10 +concurrency = 0 +server = "127.0.0.1:4433" +"#; + let config = get_config_from_str(toml); + let result = validate_config(&config, toml, std::path::Path::new("test.toml")); + assert!(result.is_err()); + } + + #[test] + fn empty_benchmarks() { + let toml = "benchmarks = []"; + let config = get_config_from_str(toml); + assert!(config.benchmarks.is_empty()); + } + + #[test] + fn server_mode_fallback() { + let toml = r#" +[[benchmarks]] +mode = "x25519" +payload = 1024 +iters = 100 +warmup = 10 +concurrency = 1 +server = "127.0.0.1:4433" +"#; + let config = get_config_from_str(toml); + assert_eq!(config.server_mode(), KeyExchangeMode::X25519); + } + + #[test] + fn server_mode_mlkem() { + let toml = r#" +[[benchmarks]] +mode = "x25519mlkem768" +payload = 1024 +iters = 100 +warmup = 10 +concurrency = 1 +server = "127.0.0.1:4433" +"#; + let config = get_config_from_str(toml); + assert_eq!(config.server_mode(), KeyExchangeMode::X25519Mlkem768); + } +}