use tracing::Instrument;
use tracing_subscriber::prelude::*;
#[tokio::test]
async fn async_ctx_push_pop_scope() {
let snap = dcontext::ContextSnapshot::empty();
dcontext::async_ctx::with_context(snap, async {
let chain = dcontext::async_ctx::scope_chain();
assert!(chain.is_empty());
let _guard = dcontext::async_ctx::push_scope("test_scope");
let chain = dcontext::async_ctx::scope_chain();
assert_eq!(chain, vec!["test_scope"]);
})
.await;
}
#[tokio::test]
async fn async_ctx_scope_function() {
let snap = dcontext::ContextSnapshot::empty();
dcontext::async_ctx::with_context(snap, async {
dcontext::async_ctx::scope("outer", async {
let chain = dcontext::async_ctx::scope_chain();
assert_eq!(chain, vec!["outer"]);
dcontext::async_ctx::scope("inner", async {
let chain = dcontext::async_ctx::scope_chain();
assert_eq!(chain, vec!["outer", "inner"]);
})
.await;
let chain = dcontext::async_ctx::scope_chain();
assert_eq!(chain, vec!["outer"]);
})
.await;
let chain = dcontext::async_ctx::scope_chain();
assert!(chain.is_empty());
})
.await;
}
#[tokio::test]
async fn async_ctx_set_get_context() {
let snap = dcontext::ContextSnapshot::empty();
dcontext::async_ctx::with_context(snap, async {
dcontext::async_ctx::set_context("test_key", "hello".to_string());
let val: Option<String> = dcontext::async_ctx::get_context("test_key");
assert_eq!(val, Some("hello".to_string()));
})
.await;
}
#[tokio::test]
async fn async_ctx_snapshot_and_propagate() {
let snap = dcontext::ContextSnapshot::empty();
dcontext::async_ctx::with_context(snap, async {
dcontext::async_ctx::set_context("parent_key", 42u64);
let _guard = dcontext::async_ctx::push_scope("parent_scope");
let child_snap = dcontext::async_ctx::snapshot();
let handle = tokio::spawn(async move {
dcontext::async_ctx::with_context(child_snap, async {
let val: Option<u64> = dcontext::async_ctx::get_context("parent_key");
assert_eq!(val, Some(42u64));
let chain = dcontext::async_ctx::scope_chain();
assert_eq!(chain, vec!["parent_scope"]);
})
.await;
});
handle.await.unwrap();
})
.await;
}
#[test]
fn sync_ctx_push_pop_scope() {
dcontext::sync_ctx::clear();
let _guard = dcontext::sync_ctx::push_scope("sync_scope");
let chain = dcontext::sync_ctx::scope_chain();
assert_eq!(chain, vec!["sync_scope"]);
drop(_guard);
let chain = dcontext::sync_ctx::scope_chain();
assert!(chain.is_empty());
}
#[test]
fn sync_ctx_set_get_context() {
dcontext::sync_ctx::clear();
dcontext::sync_ctx::set_context("sync_key", "world".to_string());
let val: Option<String> = dcontext::sync_ctx::get_context("sync_key");
assert_eq!(val, Some("world".to_string()));
dcontext::sync_ctx::clear();
}
#[test]
fn sync_ctx_restore_from_snapshot() {
dcontext::sync_ctx::clear();
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let snap = rt.block_on(async {
let empty = dcontext::ContextSnapshot::empty();
dcontext::async_ctx::with_context(empty, async {
let _guard = dcontext::async_ctx::push_scope("remote_scope");
dcontext::async_ctx::snapshot()
})
.await
});
dcontext::sync_ctx::restore(snap);
let chain = dcontext::sync_ctx::scope_chain();
assert_eq!(chain, vec!["remote_scope"]);
dcontext::sync_ctx::clear();
}
#[tokio::test]
async fn async_and_sync_stores_are_independent() {
dcontext::sync_ctx::clear();
let snap = dcontext::ContextSnapshot::empty();
dcontext::async_ctx::with_context(snap, async {
dcontext::async_ctx::set_context("shared_name", "async_value".to_string());
let _guard = dcontext::async_ctx::push_scope("async_scope");
dcontext::sync_ctx::set_context("shared_name", "sync_value".to_string());
let _sync_guard = dcontext::sync_ctx::push_scope("sync_scope");
let async_val: Option<String> = dcontext::async_ctx::get_context("shared_name");
let sync_val: Option<String> = dcontext::sync_ctx::get_context("shared_name");
assert_eq!(async_val, Some("async_value".to_string()));
assert_eq!(sync_val, Some("sync_value".to_string()));
let async_chain = dcontext::async_ctx::scope_chain();
let sync_chain = dcontext::sync_ctx::scope_chain();
assert_eq!(async_chain, vec!["async_scope"]);
assert_eq!(sync_chain, vec!["sync_scope"]);
})
.await;
dcontext::sync_ctx::clear();
}
#[tokio::test]
async fn scope_chain_does_not_leak_on_yield() {
let subscriber =
tracing_subscriber::registry().with(dcontext_tracing::AsyncDcontextLayer::new());
let _guard = tracing::subscriber::set_default(subscriber);
let snap = dcontext::ContextSnapshot::empty();
dcontext::async_ctx::with_context(snap, async {
let span = tracing::info_span!("handler");
async {
dcontext::async_ctx::scope("inner", async {
tokio::task::yield_now().await; })
.await;
}
.instrument(span)
.await;
let chain = dcontext::async_ctx::scope_chain();
assert!(chain.is_empty(), "Leaked scopes: {:?}", chain);
})
.await;
}
#[tokio::test]
async fn repeated_yields_do_not_grow_scope_chain() {
let subscriber =
tracing_subscriber::registry().with(dcontext_tracing::AsyncDcontextLayer::new());
let _guard = tracing::subscriber::set_default(subscriber);
let snap = dcontext::ContextSnapshot::empty();
dcontext::async_ctx::with_context(snap, async {
let span = tracing::info_span!("request_handler");
async {
for _ in 0..100 {
dcontext::async_ctx::scope("process_item", async {
tokio::task::yield_now().await;
})
.await;
}
}
.instrument(span)
.await;
let chain = dcontext::async_ctx::scope_chain();
assert!(
chain.is_empty(),
"Scope chain leaked after 100 iterations: {:?} (len={})",
chain,
chain.len()
);
})
.await;
}
#[test]
fn sync_layer_manages_thread_local_scopes() {
dcontext::sync_ctx::clear();
let subscriber =
tracing_subscriber::registry().with(dcontext_tracing::SyncDcontextLayer::new());
let _guard = tracing::subscriber::set_default(subscriber);
{
let span = tracing::info_span!("sync_handler");
let _entered = span.enter();
let chain = dcontext::sync_ctx::scope_chain();
assert_eq!(chain, vec!["sync_handler"]);
}
let chain = dcontext::sync_ctx::scope_chain();
assert!(chain.is_empty(), "Leaked: {:?}", chain);
dcontext::sync_ctx::clear();
}
#[tokio::test]
async fn bridge_async_to_sync_via_spawn_blocking() {
let snap = dcontext::ContextSnapshot::empty();
dcontext::async_ctx::with_context(snap, async {
dcontext::async_ctx::set_context("request_id", "req-abc".to_string());
let _guard = dcontext::async_ctx::push_scope("handler");
let async_snap = dcontext::async_ctx::snapshot();
let result = tokio::task::spawn_blocking(move || {
dcontext::sync_ctx::restore(async_snap);
let val: Option<String> = dcontext::sync_ctx::get_context("request_id");
let chain = dcontext::sync_ctx::scope_chain();
(val, chain)
})
.await
.unwrap();
assert_eq!(result.0, Some("req-abc".to_string()));
assert_eq!(result.1, vec!["handler"]);
})
.await;
}
#[test]
fn async_ctx_outside_task_returns_empty() {
let chain = dcontext::async_ctx::scope_chain();
assert!(chain.is_empty());
let val: Option<String> = dcontext::async_ctx::get_context("any_key");
assert_eq!(val, None);
let snap = dcontext::async_ctx::snapshot();
assert!(snap.scope_chain().is_empty());
}