Skip to content

Commit 695172f

Browse files
THardy98stephanos
andauthored
Antithesis instrumentation (#1049)
* gate compilation at module and call site, use dbg_panic macro at state machines instead of direct calls to antithesis assert_always (dbg_panic just logs in non-release builds with no antithesis_assertions feature flag anyways) * add assert_sometimes * instrument retries * sometimes assertions for timers * add sometimes assertions to activities * add assert_unreachable * nondeterminism! and fatal! * fmt * fmt II --------- Co-authored-by: Stephan Behnke <stephanos@users.noreply.github.com>
1 parent 060e46b commit 695172f

26 files changed

+542
-253
lines changed

crates/sdk-core-c-bridge/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ thiserror = { workspace = true }
5555
cbindgen = { version = "0.29", default-features = false }
5656

5757
[features]
58+
antithesis_assertions = ["temporalio-sdk-core/antithesis_assertions"]
5859
xz2-static = ["xz2/static"]

crates/sdk-core/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ tokio-console = ["console-subscriber"]
2727
ephemeral-server = ["dep:flate2", "dep:reqwest", "dep:tar", "dep:zip"]
2828
debug-plugin = ["dep:reqwest"]
2929
test-utilities = ["dep:assert_matches", "dep:bimap"]
30+
antithesis_assertions = ["dep:antithesis_sdk"]
3031

3132
[dependencies]
3233
anyhow = "1.0"
34+
antithesis_sdk = { version = "0.2.1", optional = true, default-features = false, features = ["full"] }
3335
assert_matches = { version = "1.5", optional = true }
3436
bimap = { version = "0.6.3", optional = true }
3537
async-trait = "0.1"

crates/sdk-core/src/abstractions.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -423,10 +423,23 @@ impl<SK: SlotKind> OwnedMeteredSemPermit<SK> {
423423
pub(crate) struct UsedMeteredSemPermit<SK: SlotKind>(#[allow(dead_code)] OwnedMeteredSemPermit<SK>);
424424

425425
macro_rules! dbg_panic {
426-
($($arg:tt)*) => {
427-
error!($($arg)*);
428-
debug_assert!(false, $($arg)*);
429-
};
426+
($($arg:tt)*) => {{
427+
let message = format!($($arg)*);
428+
error!("{}", message);
429+
430+
#[cfg(feature = "antithesis_assertions")]
431+
crate::antithesis::assert_always!(
432+
false,
433+
"dbg_panic invariant triggered",
434+
::serde_json::json!({
435+
"message": message,
436+
"file": file!(),
437+
"line": line!(),
438+
"module": module_path!(),
439+
})
440+
);
441+
debug_assert!(false, "{}", message);
442+
}};
430443
}
431444
pub(crate) use dbg_panic;
432445

crates/sdk-core/src/antithesis.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//! Antithesis SDK integration for invariant testing.
2+
//!
3+
//! This module provides assertion macros that integrate with the Antithesis
4+
//! testing platform to detect invariant violations during fuzz testing.
5+
6+
use std::sync::OnceLock;
7+
8+
/// Ensure Antithesis is initialized exactly once.
9+
pub(crate) fn ensure_init() {
10+
static INIT: OnceLock<()> = OnceLock::new();
11+
INIT.get_or_init(|| {
12+
::antithesis_sdk::antithesis_init();
13+
});
14+
}
15+
16+
/// Assert that a condition is always true during Antithesis fuzz testing.
17+
/// Use `false` as the condition to log an invariant violation.
18+
macro_rules! assert_always {
19+
($condition:expr, $message:literal, $details:expr) => {{
20+
$crate::antithesis::ensure_init();
21+
let details: ::serde_json::Value = $details;
22+
::antithesis_sdk::assert_always!($condition, $message, &details);
23+
}};
24+
($condition:expr, $message:literal) => {{
25+
$crate::antithesis::ensure_init();
26+
::antithesis_sdk::assert_always!($condition, $message);
27+
}};
28+
}
29+
30+
/// Assert that a condition is sometimes true during Antithesis fuzz testing.
31+
/// This checks that the condition occurs at least once across the entire test session.
32+
macro_rules! assert_sometimes {
33+
($condition:expr, $message:literal, $details:expr) => {{
34+
$crate::antithesis::ensure_init();
35+
let details: ::serde_json::Value = $details;
36+
::antithesis_sdk::assert_sometimes!($condition, $message, &details);
37+
}};
38+
($condition:expr, $message:literal) => {{
39+
$crate::antithesis::ensure_init();
40+
::antithesis_sdk::assert_sometimes!($condition, $message);
41+
}};
42+
}
43+
44+
/// Assert that a code location is unreachable during Antithesis fuzz testing.
45+
/// Use this for code paths that should never be reached (bugs, invariant violations).
46+
macro_rules! assert_unreachable {
47+
($message:literal, $details:expr) => {{
48+
$crate::antithesis::ensure_init();
49+
let details: ::serde_json::Value = $details;
50+
::antithesis_sdk::assert_unreachable!($message, &details);
51+
}};
52+
($message:literal) => {{
53+
$crate::antithesis::ensure_init();
54+
::antithesis_sdk::assert_unreachable!($message);
55+
}};
56+
}
57+
58+
pub(crate) use assert_always;
59+
pub(crate) use assert_sometimes;
60+
pub(crate) use assert_unreachable;

crates/sdk-core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ extern crate tracing;
1212
extern crate core;
1313

1414
mod abstractions;
15+
#[cfg(feature = "antithesis_assertions")]
16+
mod antithesis;
1517
#[cfg(feature = "debug-plugin")]
1618
pub mod debug_client;
1719
#[cfg(feature = "ephemeral-server")]

crates/sdk-core/src/retry_logic.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,31 @@ impl ValidatedRetryPolicy {
7171
application_failure: Option<&ApplicationFailureInfo>,
7272
) -> Option<Duration> {
7373
if self.maximum_attempts > 0 && attempt_number.get() >= self.maximum_attempts {
74+
#[cfg(feature = "antithesis_assertions")]
75+
crate::antithesis::assert_sometimes!(
76+
true,
77+
"Retry maximum_attempts limit reached",
78+
::serde_json::json!({
79+
"attempt": attempt_number.get(),
80+
"maximum_attempts": self.maximum_attempts
81+
})
82+
);
7483
return None;
7584
}
7685

7786
let non_retryable = application_failure
7887
.map(|f| f.non_retryable)
7988
.unwrap_or_default();
8089
if non_retryable {
90+
#[cfg(feature = "antithesis_assertions")]
91+
crate::antithesis::assert_sometimes!(
92+
true,
93+
"Non-retryable application failure encountered",
94+
::serde_json::json!({
95+
"attempt": attempt_number.get(),
96+
"error_type": application_failure.map(|f| &f.r#type)
97+
})
98+
);
8199
return None;
82100
}
83101

0 commit comments

Comments
 (0)