Skip to content
Open
Show file tree
Hide file tree
Changes from all 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