// file: crates/ksp-job-backfill-lib/unit_tests/runtime.rs // version: 3 use ksp_job_api::JobSnapshotSource; // rust-rules: trait-import fn request() -> std::option::Option { 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(), super::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 = super::BackfillRuntimeControl::new(cancel_sender); assert!(cancellation_first.request_cancellation()); assert_eq!(cancellation_first.claim_normal_terminal(), super::TerminalClaim::Cancelled); assert!(!cancellation_first.request_cancellation()); let (cancel_sender, _) = tokio::sync::watch::channel(false); let completion_first = super::BackfillRuntimeControl::new(cancel_sender); assert_eq!(completion_first.claim_normal_terminal(), super::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 = super::BackfillRuntimeControl::new(cancel_sender); assert!(control.request_cancellation()); control.claim_failed(); assert_eq!(control.claim_normal_terminal(), super::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 = super::BackfillRuntimeControl::new(cancel_sender); let signal = crate::BackfillCancellationSignal::new(control.token(), cancel_receiver); let operation = std::future::pending::>(); 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; }