influxdb/test_helpers/src/lib.rs

208 lines
6.9 KiB
Rust

#![deny(rustdoc::broken_intra_doc_links, rustdoc::bare_urls, rust_2018_idioms)]
#![warn(
missing_copy_implementations,
missing_debug_implementations,
clippy::explicit_iter_loop,
clippy::use_self,
clippy::clone_on_ref_ptr,
clippy::todo,
clippy::dbg_macro
)]
use std::{
env, f64,
sync::{Arc, Once},
};
pub use tempfile;
#[cfg(feature = "future_timeout")]
pub mod timeout;
pub mod tracing;
pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
pub type Result<T = (), E = Error> = std::result::Result<T, E>;
/// A test helper function for asserting floating point numbers are within the
/// machine epsilon because strict comparison of floating point numbers is
/// incorrect
pub fn approximately_equal(f1: f64, f2: f64) -> bool {
(f1 - f2).abs() < f64::EPSILON
}
pub fn all_approximately_equal(f1: &[f64], f2: &[f64]) -> bool {
f1.len() == f2.len() && f1.iter().zip(f2).all(|(&a, &b)| approximately_equal(a, b))
}
/// Return a temporary directory that is deleted when the object is dropped
pub fn tmp_dir() -> Result<tempfile::TempDir> {
let _ = dotenvy::dotenv();
let root = env::var_os("TEST_INFLUXDB_IOX_DB_DIR").unwrap_or_else(|| env::temp_dir().into());
Ok(tempfile::Builder::new()
.prefix("influxdb_iox")
.tempdir_in(root)?)
}
pub fn tmp_file() -> Result<tempfile::NamedTempFile> {
let _ = dotenvy::dotenv();
let root = env::var_os("TEST_INFLUXDB_IOX_DB_DIR").unwrap_or_else(|| env::temp_dir().into());
Ok(tempfile::Builder::new()
.prefix("influxdb_iox")
.tempfile_in(root)?)
}
/// Writes the specified string to a new temporary file, returning the Path to
/// the file
pub fn make_temp_file<C: AsRef<[u8]>>(contents: C) -> tempfile::NamedTempFile {
let file = tmp_file().expect("creating temp file");
std::fs::write(&file, contents).expect("writing data to temp file");
file
}
/// convert form that is easier to type in tests to what some code needs
pub fn str_vec_to_arc_vec(str_vec: &[&str]) -> Vec<Arc<str>> {
str_vec.iter().map(|s| Arc::from(*s)).collect()
}
/// convert form that is easier to type in tests to what some code needs
pub fn str_pair_vec_to_vec(str_vec: &[(&str, &str)]) -> Vec<(Arc<str>, Arc<str>)> {
str_vec
.iter()
.map(|(s1, s2)| (Arc::from(*s1), Arc::from(*s2)))
.collect()
}
static LOG_SETUP: Once = Once::new();
/// Enables debug logging regardless of the value of RUST_LOG
/// environment variable. If RUST_LOG isn't specifies, defaults to
/// "debug"
///
/// Hint: Try running your test with `--no-capture` if you don't see expected logs.
///
/// This is likely useful only when debugging a single tests or when running
/// with `--test-threads=1` , otherwise outputs will be interleaved because
/// test execution is multi-threaded.
pub fn start_logging() {
use tracing_log::LogTracer;
use tracing_subscriber::{filter::EnvFilter, FmtSubscriber};
// ensure the global has been initialized
LOG_SETUP.call_once(|| {
// honor any existing RUST_LOG level
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "debug");
}
LogTracer::init().expect("Cannot init log->trace integration");
let subscriber = FmtSubscriber::builder()
.with_env_filter(EnvFilter::from_default_env())
// Note `with_test_writer` allows libtest (used for all
// our tests and invoked by `cargo test`) to capture
// per-test logging. The captured data will only be
// shown for failed tests. Pass `--no-capture` to
// disable that feature (but only try to run a single
// test to prevent output interleaving).
.with_test_writer()
.finish();
observability_deps::tracing::subscriber::set_global_default(subscriber)
.expect("setting default subscriber failed");
})
}
/// Enables debug logging if the RUST_LOG environment variable is
/// set. Does nothing if RUST_LOG is not set. If enable_logging has
/// been set previously, does nothing
pub fn maybe_start_logging() {
if std::env::var("RUST_LOG").is_ok() {
start_logging()
}
}
#[macro_export]
/// A macro to assert that one string is contained within another with
/// a nice error message if they are not.
///
/// Usage: `assert_contains!(actual, expected)`
///
/// Is a macro so test error
/// messages are on the same line as the failure;
///
/// Both arguments must be convertable into `String`s (`Into<String>`)
macro_rules! assert_contains {
($ACTUAL: expr, $EXPECTED: expr) => {
let actual_value: String = $ACTUAL.into();
let expected_value: String = $EXPECTED.into();
assert!(
actual_value.contains(&expected_value),
"Can not find expected in actual.\n\nExpected:\n{}\n\nActual:\n{}",
expected_value,
actual_value
);
};
}
#[macro_export]
/// A macro to assert that one string is NOT contained within another with
/// a nice error message if that check fails. Is a macro so test error
/// messages are on the same line as the failure;
///
/// Both arguments must be convertable into `String`s (`Into<String>`)
macro_rules! assert_not_contains {
($ACTUAL: expr, $UNEXPECTED: expr) => {
let actual_value: String = $ACTUAL.into();
let unexpected_value: String = $UNEXPECTED.into();
assert!(
!actual_value.contains(&unexpected_value),
"Found unexpected value in actual.\n\nUnexpected:\n{}\n\nActual:\n{}",
unexpected_value,
actual_value
);
};
}
#[macro_export]
/// Assert that an operation fails with one particular error. Panics if the operation succeeds.
/// Prints debug format of the error value if it doesn't match the specified pattern.
macro_rules! assert_error {
($OPERATION: expr, $(|)? $( $ERROR_PATTERN:pat_param )|+ $( if $GUARD: expr )? $(,)?) => {
let err = $OPERATION.unwrap_err();
assert!(
matches!(err, $( $ERROR_PATTERN )|+ $( if $GUARD )?),
"Expected {}, but got {:?}",
stringify!($( $ERROR_PATTERN )|+ $( if $GUARD )?),
err
);
};
}
#[macro_export]
/// Assert that `actual` and `expected` values are within `epsilon` of each other. Used to compare
/// values that may fluctuate from run to run (e.g. because they encode timestamps)
///
/// Usage: `assert_close!(actual, expected, epsilon);`
macro_rules! assert_close {
($ACTUAL:expr, $EXPECTED:expr, $EPSILON:expr) => {{
{
let actual = $ACTUAL;
let expected = $EXPECTED;
let epsilon = $EPSILON;
// determine how far apart they actually are
let delta = actual.abs_diff(expected);
assert!(
delta <= epsilon,
"{} and {} differ by {}, which is more than allowed {}",
actual,
expected,
delta,
epsilon
)
}
}};
}