#![cfg(not(miri))] #![expect(clippy::drop_non_drop, reason = "this is test code")]
use std::sync::atomic::{AtomicI32, Ordering};
use ohno::{Error, OhnoCore, enrich_err};
#[macro_use]
mod util;
#[derive(Error)]
struct AsyncTestError {
inner: OhnoCore,
}
#[tokio::test]
async fn simple_async_enrich_err() {
#[enrich_err("async operation failed")]
async fn simple_async_failure() -> Result<String, AsyncTestError> {
std::future::ready(()).await;
Err(AsyncTestError::caused_by("async error"))
}
let error = simple_async_failure().await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"async error"));
assert_enrichment!(error, "async operation failed");
}
#[tokio::test]
async fn async_enrich_err_with_params() {
#[enrich_err("async operation failed with {value}")]
async fn async_with_param(value: i32) -> Result<String, AsyncTestError> {
std::future::ready(()).await;
Err(AsyncTestError::caused_by(format!("value: {value}")))
}
let error = async_with_param(42).await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"value: 42"));
assert_enrichment!(error, "async operation failed with 42");
}
#[tokio::test]
async fn async_plus_impl_as_ref() {
#[enrich_err("async operation failed. Path: {}", path.as_ref().display())]
async fn simple_async_failure(path: impl AsRef<std::path::Path>) -> Result<String, AsyncTestError> {
std::future::ready(()).await;
Err(AsyncTestError::caused_by("async error"))
}
let future = simple_async_failure("test/path/1.txt");
let error = future.await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"async error"));
assert_enrichment!(error, "async operation failed. Path: test/path/1.txt");
}
struct AsyncService {
counter: i32,
atomic_counter: AtomicI32,
}
impl AsyncService {
const fn new() -> Self {
Self {
counter: 0,
atomic_counter: AtomicI32::new(0),
}
}
#[enrich_err("read-only method failed")]
async fn read_only(&self) -> Result<i32, AsyncTestError> {
self.atomic_counter.fetch_add(1, Ordering::SeqCst);
std::future::ready(()).await;
Err(AsyncTestError::caused_by("counter is zero"))
}
#[enrich_err("method with self field access, counter: {}", self.counter)]
async fn with_self_field(&self) -> Result<i32, AsyncTestError> {
self.atomic_counter.fetch_add(1, Ordering::SeqCst);
std::future::ready(()).await;
Err(AsyncTestError::caused_by("failed with field"))
}
#[enrich_err("service method failed with value {value}")]
async fn with_mut_self_and_copiable_value(&mut self, value: i32) -> Result<i32, AsyncTestError> {
self.counter += value;
self.atomic_counter.fetch_add(value, Ordering::SeqCst);
std::future::ready(()).await;
Err(AsyncTestError::caused_by("negative value"))
}
#[enrich_err("mutable method failed, atomic: {}", self.atomic_counter.load(Ordering::SeqCst))]
async fn with_mut_self_no_args(&mut self) -> Result<i32, AsyncTestError> {
self.counter += 1;
self.atomic_counter.fetch_add(1, Ordering::SeqCst);
std::future::ready(()).await;
Err(AsyncTestError::caused_by("mutation failed"))
}
#[enrich_err("method failed")] async fn with_self_and_string(&self, message: String) -> Result<i32, AsyncTestError> {
std::future::ready(()).await;
let e = AsyncTestError::caused_by(format!("message was: {message}"));
drop(message); Err(e)
}
#[enrich_err("method failed with string ref: {message}")]
async fn with_self_and_string_ref(&self, message: &String) -> Result<i32, AsyncTestError> {
std::future::ready(()).await;
Err(AsyncTestError::caused_by(format!("message was: {message}")))
}
#[enrich_err("consuming method failed")]
async fn consume_self(self) -> Result<i32, AsyncTestError> {
std::future::ready(()).await;
let counter = self.counter;
drop(self); Err(AsyncTestError::caused_by(format!("consumed with counter: {counter}")))
}
#[enrich_err("consuming method with arg failed, value: {value}")]
async fn consume_self_with_arg(self, value: i32) -> Result<i32, AsyncTestError> {
std::future::ready(()).await;
drop(self); Err(AsyncTestError::caused_by(format!("consumed with value: {value}")))
}
#[enrich_err("consuming mutable method failed")]
async fn consume_self_mut(mut self) -> Result<i32, AsyncTestError> {
self.counter += 1;
std::future::ready(()).await;
let counter = self.counter;
drop(self); Err(AsyncTestError::caused_by(format!("consumed mut with counter: {counter}")))
}
}
#[tokio::test]
async fn async_method_with_mut_self() {
let mut service = AsyncService::new();
let error = service.with_mut_self_and_copiable_value(-5).await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"negative value"));
assert_enrichment!(error, "service method failed with value -5");
}
#[tokio::test]
async fn async_method_with_self() {
let service = AsyncService::new();
let error = service.read_only().await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"counter is zero"));
assert_enrichment!(error, "read-only method failed");
}
#[tokio::test]
async fn async_method_with_self_field_access() {
let service = AsyncService::new();
let error = service.with_self_field().await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"failed with field"));
assert_enrichment!(error, "method with self field access, counter: 0");
}
#[tokio::test]
async fn async_method_with_mut_self_no_args() {
let mut service = AsyncService::new();
let error = service.with_mut_self_no_args().await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"mutation failed"));
assert_enrichment!(error, "mutable method failed, atomic: 1");
}
#[tokio::test]
async fn async_method_with_self_and_string() {
let service = AsyncService::new();
let message = String::from("test message");
let error = service.with_self_and_string(message).await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"message was: test message"));
assert_enrichment!(error, "method failed");
}
#[tokio::test]
async fn async_method_with_self_and_string_ref() {
let service = AsyncService::new();
let message = String::from("ref message");
let error = service.with_self_and_string_ref(&message).await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"message was: ref message"));
assert_enrichment!(error, "method failed with string ref: ref message");
}
#[tokio::test]
async fn async_method_consume_self() {
let service = AsyncService::new();
let error = service.consume_self().await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"consumed with counter: 0"));
assert_enrichment!(error, "consuming method failed");
}
#[tokio::test]
async fn async_method_consume_self_with_arg() {
let service = AsyncService::new();
let error = service.consume_self_with_arg(42).await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"consumed with value: 42"));
assert_enrichment!(error, "consuming method with arg failed, value: 42");
}
#[tokio::test]
async fn async_method_consume_self_mut() {
let service = AsyncService::new();
let error = service.consume_self_mut().await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"consumed mut with counter: 1"));
assert_enrichment!(error, "consuming mutable method failed");
}
struct CustomFuture;
impl std::future::Future for CustomFuture {
type Output = Result<i32, AsyncTestError>;
#[enrich_err("custom future poll failed")]
fn poll(self: std::pin::Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
std::task::Poll::Ready(Err(AsyncTestError::caused_by("poll error")))
}
}
#[tokio::test]
async fn enrich_err_on_future_poll() {
let future = CustomFuture;
let error = future.await.unwrap_err();
let error_display = format!("{error}");
let lines = error_display.lines().collect::<Vec<_>>();
assert_eq!(lines.first(), Some(&"poll error"));
assert_enrichment!(error, "custom future poll failed");
}