#![allow(clippy::all)]
use crate::cx::Cx;
use crate::lab::{LabConfig, LabRuntime};
use crate::runtime::RuntimeBuilder;
pub use crate::test_logging::{
ARTIFACT_SCHEMA_VERSION, AllocatedPort, DockerFixtureService, EnvironmentMetadata,
FixtureService, InProcessService, NoOpFixtureService, PortAllocator, ReproManifest,
TempDirFixture, TestContext, TestEnvironment, derive_component_seed, derive_entropy_seed,
derive_scenario_seed, wait_until_healthy,
};
pub use crate::test_ndjson::{
NDJSON_SCHEMA_VERSION, NdjsonEvent, NdjsonLogger, artifact_base_dir, artifact_bundle_dir,
ndjson_file_name, trace_file_name, write_artifact_bundle,
};
use crate::time::timeout;
use parking_lot::Mutex;
use std::future::Future;
use std::sync::Once;
use std::time::Duration;
use tracing_subscriber::fmt::format::FmtSpan;
static INIT_LOGGING: Once = Once::new();
#[allow(dead_code)] static ENV_LOCK: Mutex<()> = Mutex::new(());
pub const DEFAULT_TEST_SEED: u64 = 0xDEAD_BEEF;
pub fn init_test_logging() {
init_test_logging_with_level(tracing::Level::TRACE);
}
pub fn init_test_logging_with_level(level: tracing::Level) {
INIT_LOGGING.call_once(|| {
let _ = tracing_subscriber::fmt()
.with_max_level(level)
.with_test_writer()
.with_file(true)
.with_line_number(true)
.with_target(true)
.with_thread_ids(true)
.with_span_events(FmtSpan::CLOSE)
.with_ansi(false)
.try_init();
});
}
#[allow(dead_code)] pub(crate) fn env_lock() -> parking_lot::MutexGuard<'static, ()> {
ENV_LOCK.lock()
}
#[must_use]
pub fn test_lab() -> LabRuntime {
LabRuntime::new(LabConfig::new(DEFAULT_TEST_SEED))
}
#[must_use]
pub fn test_lab_with_seed(seed: u64) -> LabRuntime {
LabRuntime::new(LabConfig::new(seed))
}
#[must_use]
pub fn test_lab_with_tracing() -> LabRuntime {
LabRuntime::new(LabConfig::new(DEFAULT_TEST_SEED).trace_capacity(64 * 1024))
}
#[must_use]
pub fn test_lab_from_context(ctx: &TestContext) -> LabRuntime {
LabRuntime::new(LabConfig::new(ctx.seed))
}
pub fn lab_with_config<F, Fut, R>(_f: F) -> R
where
F: FnOnce(LabRuntime) -> Fut,
Fut: Future<Output = R>,
{
init_test_logging();
let _lab = test_lab();
todo!("LabRuntime block_on method needs to be implemented")
}
#[must_use]
pub fn test_context(test_id: &str) -> TestContext {
TestContext::new(test_id, DEFAULT_TEST_SEED)
}
#[must_use]
pub fn test_context_with_seed(test_id: &str, seed: u64) -> TestContext {
TestContext::new(test_id, seed)
}
pub fn run_test<F, Fut>(f: F)
where
F: FnOnce() -> Fut,
Fut: Future<Output = ()>,
{
init_test_logging();
let runtime = RuntimeBuilder::current_thread()
.build()
.expect("failed to build test runtime");
runtime.block_on(f());
}
pub fn run_test_with_cx<F, Fut>(f: F)
where
F: FnOnce(Cx) -> Fut,
Fut: Future<Output = ()>,
{
init_test_logging();
let cx: Cx = Cx::for_testing();
let runtime = RuntimeBuilder::current_thread()
.build()
.expect("failed to build test runtime");
runtime.block_on(f(cx));
}
pub async fn assert_completes_within<F, Fut, T>(
timeout_duration: Duration,
description: &str,
f: F,
) -> T
where
F: FnOnce() -> Fut,
Fut: Future<Output = T> + Unpin,
{
let now = Cx::current()
.and_then(|cx| cx.timer_driver())
.map_or_else(crate::time::wall_now, |driver| driver.now());
let Ok(value) = timeout(now, timeout_duration, f()).await else {
unreachable!("operation '{description}' did not complete within {timeout_duration:?}");
};
tracing::debug!(
description = %description,
timeout_ms = timeout_duration.as_millis(),
"operation completed within timeout"
);
value
}
#[cfg(test)]
mod tests {
use super::*;
use futures_lite::future;
#[test]
fn assert_completes_within_uses_wall_time_when_no_runtime_is_active() {
let _t0 = crate::time::wall_now();
std::thread::sleep(Duration::from_millis(50));
let value = future::block_on(assert_completes_within(
Duration::from_millis(10),
"standalone immediate future",
|| std::future::ready(7_u8),
));
assert_eq!(value, 7);
}
}
#[macro_export]
macro_rules! test_phase {
($name:expr) => {
tracing::info!(phase = %$name, "========================================");
tracing::info!(phase = %$name, "TEST PHASE: {}", $name);
tracing::info!(phase = %$name, "========================================");
};
}
#[macro_export]
macro_rules! test_section {
($name:expr) => {
tracing::debug!(section = %$name, "--- {} ---", $name);
};
}
#[macro_export]
macro_rules! test_complete {
($name:expr) => {
tracing::info!(test = %$name, "test completed successfully: {}", $name);
};
($name:expr, $($key:ident = $value:expr),* $(,)?) => {
tracing::info!(
test = %$name,
$($key = %$value,)*
"test completed successfully: {}",
$name
);
};
}
#[macro_export]
macro_rules! assert_with_log {
($cond:expr, $msg:expr, $expected:expr, $actual:expr) => {{
tracing::debug!(
expected = ?$expected,
actual = ?$actual,
"Asserting: {}",
$msg
);
assert!($cond, "{}: expected {:?}, got {:?}", $msg, $expected, $actual);
}};
}
#[macro_export]
macro_rules! assert_outcome_ok {
($outcome:expr, $expected:expr) => {
match $outcome {
$crate::types::Outcome::Ok(v) => assert_eq!(v, $expected),
other => unreachable!("expected Outcome::Ok({:?}), got {:?}", $expected, other),
}
};
}
#[macro_export]
macro_rules! assert_outcome_cancelled {
($outcome:expr) => {
match $outcome {
$crate::types::Outcome::Cancelled(_) => {}
other => unreachable!("expected Outcome::Cancelled, got {:?}", other),
}
};
}
#[macro_export]
macro_rules! assert_outcome_err {
($outcome:expr) => {
match $outcome {
$crate::types::Outcome::Err(_) => {}
other => unreachable!("expected Outcome::Err, got {:?}", other),
}
};
}
#[macro_export]
macro_rules! assert_outcome_panicked {
($outcome:expr) => {
match $outcome {
$crate::types::Outcome::Panicked(_) => {}
other => unreachable!("expected Outcome::Panicked, got {:?}", other),
}
};
}
#[derive(Debug)]
pub struct TestConnection {
id: usize,
query_count: std::sync::atomic::AtomicUsize,
}
impl TestConnection {
#[must_use]
pub fn new(id: usize) -> Self {
Self {
id,
query_count: std::sync::atomic::AtomicUsize::new(0),
}
}
#[must_use]
pub const fn id(&self) -> usize {
self.id
}
#[must_use]
pub fn query_count(&self) -> usize {
self.query_count.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn query(&self, _sql: &str) -> Result<(), TestError> {
self.query_count
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TestError(pub String);
impl std::error::Error for TestError {}
impl std::fmt::Display for TestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TestError: {}", self.0)
}
}