v0.3.6-pre.009
This commit is contained in:
158
crates/ksp-job-backfill-lib/unit_tests/runtime.rs
Normal file
158
crates/ksp-job-backfill-lib/unit_tests/runtime.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
// file: crates/ksp-job-backfill-lib/unit_tests/runtime.rs
|
||||
// version: 2
|
||||
|
||||
use ksp_job_api::JobSnapshotSource; // rust-rules: trait-import
|
||||
|
||||
fn request() -> std::option::Option<crate::BackfillRequest> {
|
||||
let signature = match crate::BackfillSignature::new("1".repeat(crate::MIN_BACKFILL_SIGNATURE_TEXT_BYTES)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
let scope = match crate::BackfillScope::explicit_signatures(std::vec![signature]) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
let job_id = match ksp_job_api::JobId::new("backfill:runtime-test") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
let network = match ksp_store_lib::RawNetworkId::new("devnet") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
return crate::BackfillRequest::new(
|
||||
job_id,
|
||||
network,
|
||||
ksp_onchain_transport_lib::HttpRoleName::new("history"),
|
||||
crate::BackfillCommitment::Confirmed,
|
||||
scope,
|
||||
100,
|
||||
10,
|
||||
1,
|
||||
1,
|
||||
std::option::Option::None,
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pre_009_latest_value_source_coalesces_progress_for_slow_independent_listeners() {
|
||||
let request = match request() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let runtime = match crate::BackfillJobRuntime::new(request) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let handle = runtime.handle();
|
||||
let listener_a = handle.snapshots();
|
||||
let listener_b = handle.snapshots();
|
||||
let initial = listener_a.current();
|
||||
assert_eq!(initial.sequence().value(), 0);
|
||||
assert_eq!(initial.state(), ksp_job_api::JobState::Created);
|
||||
let first = runtime.publisher.publish_running(crate::BackfillJobPhase::Discovering);
|
||||
assert!(first.is_ok());
|
||||
let second = runtime.publisher.publish_running(crate::BackfillJobPhase::Executing);
|
||||
assert!(second.is_ok());
|
||||
let coalesced = listener_a.wait_for_change(initial.sequence()).await;
|
||||
assert_eq!(coalesced.sequence().value(), 2);
|
||||
assert_eq!(coalesced.snapshot().phase(), crate::BackfillJobPhase::Executing);
|
||||
let listener_b_current = listener_b.current();
|
||||
assert_eq!(listener_b_current.sequence().value(), 2);
|
||||
assert_eq!(listener_b_current.snapshot().phase(), crate::BackfillJobPhase::Executing);
|
||||
return;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pre_009_terminal_snapshot_is_retained_and_late_cancellation_is_rejected() {
|
||||
let request = match request() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let runtime = match crate::BackfillJobRuntime::new(request) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let handle = runtime.handle();
|
||||
let listener = handle.snapshots();
|
||||
let started = runtime.publisher.publish_running(crate::BackfillJobPhase::Discovering);
|
||||
assert!(started.is_ok());
|
||||
assert_eq!(runtime.control.claim_normal_terminal(), crate::TerminalClaim::Completed);
|
||||
let terminal = runtime.publisher.publish_terminal(ksp_job_api::JobState::Completed(ksp_job_api::JobCompletion::Complete), std::option::Option::None);
|
||||
assert!(terminal.is_ok());
|
||||
assert!(!handle.cancel());
|
||||
let current = listener.current();
|
||||
assert!(current.state().is_terminal());
|
||||
assert_eq!(current.snapshot().phase(), crate::BackfillJobPhase::Finished);
|
||||
let retained = listener.wait_for_change(current.sequence()).await;
|
||||
assert_eq!(retained.sequence(), current.sequence());
|
||||
assert_eq!(retained.state(), current.state());
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_009_terminal_race_is_first_decision_wins_for_cancellation_vs_completion() {
|
||||
let (cancel_sender, _) = tokio::sync::watch::channel(false);
|
||||
let cancellation_first = crate::BackfillRuntimeControl::new(cancel_sender);
|
||||
assert!(cancellation_first.request_cancellation());
|
||||
assert_eq!(cancellation_first.claim_normal_terminal(), crate::TerminalClaim::Cancelled);
|
||||
assert!(!cancellation_first.request_cancellation());
|
||||
let (cancel_sender, _) = tokio::sync::watch::channel(false);
|
||||
let completion_first = crate::BackfillRuntimeControl::new(cancel_sender);
|
||||
assert_eq!(completion_first.claim_normal_terminal(), crate::TerminalClaim::Completed);
|
||||
assert!(!completion_first.request_cancellation());
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_009_fatal_failure_overrides_pending_cancellation_before_terminal_publication() {
|
||||
let (cancel_sender, _) = tokio::sync::watch::channel(false);
|
||||
let control = crate::BackfillRuntimeControl::new(cancel_sender);
|
||||
assert!(control.request_cancellation());
|
||||
control.claim_failed();
|
||||
assert_eq!(control.claim_normal_terminal(), crate::TerminalClaim::Failed);
|
||||
assert!(!control.request_cancellation());
|
||||
return;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pre_009_long_running_pre_store_future_is_cancelled_cooperatively() {
|
||||
let (cancel_sender, cancel_receiver) = tokio::sync::watch::channel(false);
|
||||
let control = crate::BackfillRuntimeControl::new(cancel_sender);
|
||||
let signal = crate::BackfillCancellationSignal::new(control.token(), cancel_receiver);
|
||||
let operation = std::future::pending::<ksp_core_lib::Result<usize>>();
|
||||
let wait = signal.run_cancellable(operation);
|
||||
let cancel = async {
|
||||
tokio::task::yield_now().await;
|
||||
assert!(control.request_cancellation());
|
||||
};
|
||||
let (result, ()) = tokio::join!(wait, cancel);
|
||||
let error = match result {
|
||||
std::result::Result::Ok(_) => return,
|
||||
std::result::Result::Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_BACKFILL_CANCELLED);
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_009_snapshot_debug_and_public_shape_do_not_include_transport_or_raw_payloads() {
|
||||
let request = match request() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let runtime = match crate::BackfillJobRuntime::new(request) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let snapshot = runtime.handle().snapshots().current();
|
||||
let debug = format!("{:?}", snapshot.snapshot());
|
||||
for forbidden in ["http://", "https://", "endpoint", "provider", "raw_payload", "transaction_data"] {
|
||||
assert!(!debug.contains(forbidden), "unsafe snapshot diagnostic leaked: {forbidden}");
|
||||
}
|
||||
assert_eq!(snapshot.snapshot().scope_kind(), crate::BackfillScopeKind::ExplicitSignatures);
|
||||
assert_eq!(snapshot.snapshot().candidates_selected(), 0);
|
||||
assert!(snapshot.snapshot().failure_code().is_none());
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user