Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/sdk-core-c-bridge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ thiserror = { workspace = true }
cbindgen = { version = "0.29", default-features = false }

[features]
antithesis_assertions = ["temporalio-sdk-core/antithesis_assertions"]
xz2-static = ["xz2/static"]
2 changes: 2 additions & 0 deletions crates/sdk-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ tokio-console = ["console-subscriber"]
ephemeral-server = ["dep:flate2", "dep:reqwest", "dep:tar", "dep:zip"]
debug-plugin = ["dep:reqwest"]
test-utilities = ["dep:assert_matches", "dep:bimap"]
antithesis_assertions = ["dep:antithesis_sdk"]

[dependencies]
anyhow = "1.0"
antithesis_sdk = { version = "0.2.1", optional = true, default-features = false, features = ["full"] }
assert_matches = { version = "1.5", optional = true }
bimap = { version = "0.6.3", optional = true }
async-trait = "0.1"
Expand Down
21 changes: 17 additions & 4 deletions crates/sdk-core/src/abstractions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,10 +423,23 @@ impl<SK: SlotKind> OwnedMeteredSemPermit<SK> {
pub(crate) struct UsedMeteredSemPermit<SK: SlotKind>(#[allow(dead_code)] OwnedMeteredSemPermit<SK>);

macro_rules! dbg_panic {
($($arg:tt)*) => {
error!($($arg)*);
debug_assert!(false, $($arg)*);
};
($($arg:tt)*) => {{
let message = format!($($arg)*);
error!("{}", message);

#[cfg(feature = "antithesis_assertions")]
crate::antithesis::assert_always!(
false,
"dbg_panic invariant triggered",
::serde_json::json!({
"message": message,
"file": file!(),
"line": line!(),
"module": module_path!(),
})
);
debug_assert!(false, "{}", message);
}};
}
pub(crate) use dbg_panic;

Expand Down
60 changes: 60 additions & 0 deletions crates/sdk-core/src/antithesis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Antithesis SDK integration for invariant testing.
//!
//! This module provides assertion macros that integrate with the Antithesis
//! testing platform to detect invariant violations during fuzz testing.

use std::sync::OnceLock;

/// Ensure Antithesis is initialized exactly once.
pub(crate) fn ensure_init() {
static INIT: OnceLock<()> = OnceLock::new();
INIT.get_or_init(|| {
::antithesis_sdk::antithesis_init();
});
}

/// Assert that a condition is always true during Antithesis fuzz testing.
/// Use `false` as the condition to log an invariant violation.
macro_rules! assert_always {
($condition:expr, $message:literal, $details:expr) => {{
$crate::antithesis::ensure_init();
let details: ::serde_json::Value = $details;
::antithesis_sdk::assert_always!($condition, $message, &details);
}};
($condition:expr, $message:literal) => {{
$crate::antithesis::ensure_init();
::antithesis_sdk::assert_always!($condition, $message);
}};
}

/// Assert that a condition is sometimes true during Antithesis fuzz testing.
/// This checks that the condition occurs at least once across the entire test session.
macro_rules! assert_sometimes {
($condition:expr, $message:literal, $details:expr) => {{
$crate::antithesis::ensure_init();
let details: ::serde_json::Value = $details;
::antithesis_sdk::assert_sometimes!($condition, $message, &details);
}};
($condition:expr, $message:literal) => {{
$crate::antithesis::ensure_init();
::antithesis_sdk::assert_sometimes!($condition, $message);
}};
}

/// Assert that a code location is unreachable during Antithesis fuzz testing.
/// Use this for code paths that should never be reached (bugs, invariant violations).
macro_rules! assert_unreachable {
($message:literal, $details:expr) => {{
$crate::antithesis::ensure_init();
let details: ::serde_json::Value = $details;
::antithesis_sdk::assert_unreachable!($message, &details);
}};
($message:literal) => {{
$crate::antithesis::ensure_init();
::antithesis_sdk::assert_unreachable!($message);
}};
}

pub(crate) use assert_always;
pub(crate) use assert_sometimes;
pub(crate) use assert_unreachable;
2 changes: 2 additions & 0 deletions crates/sdk-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ extern crate tracing;
extern crate core;

mod abstractions;
#[cfg(feature = "antithesis_assertions")]
mod antithesis;
#[cfg(feature = "debug-plugin")]
pub mod debug_client;
#[cfg(feature = "ephemeral-server")]
Expand Down
18 changes: 18 additions & 0 deletions crates/sdk-core/src/retry_logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,31 @@ impl ValidatedRetryPolicy {
application_failure: Option<&ApplicationFailureInfo>,
) -> Option<Duration> {
if self.maximum_attempts > 0 && attempt_number.get() >= self.maximum_attempts {
#[cfg(feature = "antithesis_assertions")]
crate::antithesis::assert_sometimes!(
true,
"Retry maximum_attempts limit reached",
::serde_json::json!({
"attempt": attempt_number.get(),
"maximum_attempts": self.maximum_attempts
})
);
return None;
}

let non_retryable = application_failure
.map(|f| f.non_retryable)
.unwrap_or_default();
if non_retryable {
#[cfg(feature = "antithesis_assertions")]
crate::antithesis::assert_sometimes!(
true,
"Non-retryable application failure encountered",
::serde_json::json!({
"attempt": attempt_number.get(),
"error_type": application_failure.map(|f| &f.r#type)
})
);
return None;
}

Expand Down
Loading
Loading