v0.3.6-pre.008
This commit is contained in:
168
crates/ksp-job-backfill-lib/unit_tests/checkpoint.rs
Normal file
168
crates/ksp-job-backfill-lib/unit_tests/checkpoint.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
// file: crates/ksp-job-backfill-lib/unit_tests/checkpoint.rs
|
||||
// version: 1
|
||||
|
||||
fn signature(character: char) -> std::option::Option<crate::BackfillSignature> {
|
||||
return crate::BackfillSignature::new(character.to_string().repeat(crate::MIN_BACKFILL_SIGNATURE_TEXT_BYTES)).ok();
|
||||
}
|
||||
|
||||
fn request(scope: crate::BackfillScope) -> std::option::Option<crate::BackfillRequest> {
|
||||
let job_id = match ksp_job_api::JobId::new("backfill:checkpoint-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,
|
||||
10,
|
||||
4,
|
||||
std::option::Option::None,
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn candidate(request: &crate::BackfillRequest, character: char) -> std::option::Option<crate::BackfillCandidate> {
|
||||
let signature = match signature(character) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::option::Option::None,
|
||||
};
|
||||
let identity = crate::BackfillCandidateIdentity::new(request.network().clone(), signature);
|
||||
return std::option::Option::Some(crate::BackfillCandidate::new(identity, std::option::Option::None));
|
||||
}
|
||||
|
||||
fn discovery(
|
||||
request: &crate::BackfillRequest,
|
||||
characters: &[char],
|
||||
boundary: crate::BackfillDiscoveryBoundary,
|
||||
) -> std::option::Option<crate::BackfillDiscovery> {
|
||||
let mut candidates = std::vec::Vec::with_capacity(characters.len());
|
||||
for character in characters {
|
||||
let value = match candidate(request, *character) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::option::Option::None,
|
||||
};
|
||||
candidates.push(value);
|
||||
}
|
||||
return std::option::Option::Some(crate::BackfillDiscovery::new(request.network().clone(), request.scope_fingerprint(), candidates, 1, boundary));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_008_frontier_advances_only_across_contiguous_durable_results() {
|
||||
let mut frontier = crate::CompletionFrontier::new(4);
|
||||
assert!(frontier.mark_durable(1).is_ok());
|
||||
assert_eq!(frontier.contiguous_completed(), 0);
|
||||
assert!(frontier.mark_durable(0).is_ok());
|
||||
assert_eq!(frontier.contiguous_completed(), 2);
|
||||
assert!(frontier.mark_durable(3).is_ok());
|
||||
assert_eq!(frontier.contiguous_completed(), 2);
|
||||
assert!(frontier.mark_durable(2).is_ok());
|
||||
assert_eq!(frontier.contiguous_completed(), 4);
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_008_checkpoint_is_bound_to_job_and_scope_fingerprint() {
|
||||
let scope = crate::BackfillScope::latest_address(ksp_core_lib::Pubkey::new_from_array([1_u8; 32]));
|
||||
let request = match request(scope) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let other_job = match ksp_job_api::JobId::new("backfill:other") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let wrong_job = crate::BackfillCheckpoint::new(other_job, request.scope_fingerprint(), 0, std::option::Option::None);
|
||||
let result = request.clone().with_checkpoint(wrong_job);
|
||||
assert!(result.is_err());
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_BACKFILL_CHECKPOINT_INVALID);
|
||||
}
|
||||
let other_scope = crate::BackfillScope::latest_address(ksp_core_lib::Pubkey::new_from_array([2_u8; 32]));
|
||||
let other_request = match request(other_scope) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let wrong_scope = crate::BackfillCheckpoint::new(request.job_id().clone(), other_request.scope_fingerprint(), 0, std::option::Option::None);
|
||||
assert!(request.with_checkpoint(wrong_scope).is_err());
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_008_before_checkpoint_is_cumulative_and_tracks_last_contiguous_candidate() {
|
||||
let anchor = match signature('8') {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let scope = crate::BackfillScope::before_address(ksp_core_lib::Pubkey::new_from_array([3_u8; 32]), anchor);
|
||||
let request = match request(scope) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let first_discovery = match discovery(&request, &['7', '6', '5'], crate::BackfillDiscoveryBoundary::RpcBoundary) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let mut first_frontier = crate::CompletionFrontier::new(3);
|
||||
assert!(first_frontier.mark_durable(1).is_ok());
|
||||
assert_eq!(first_frontier.contiguous_completed(), 0);
|
||||
assert!(first_frontier.mark_durable(0).is_ok());
|
||||
let first = match crate::checkpoint_from_frontier(&request, &first_discovery, &first_frontier) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(first.completed_prefix(), 2);
|
||||
let expected_first = "6".repeat(64);
|
||||
assert_eq!(first.resume_before().map(crate::BackfillSignature::as_str), std::option::Option::Some(expected_first.as_str()));
|
||||
let resumed = match request.with_checkpoint(first) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let second_discovery = match discovery(&resumed, &['5', '4'], crate::BackfillDiscoveryBoundary::RpcBoundary) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let mut second_frontier = crate::CompletionFrontier::new(2);
|
||||
assert!(second_frontier.mark_durable(0).is_ok());
|
||||
let second = match crate::checkpoint_from_frontier(&resumed, &second_discovery, &second_frontier) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(second.completed_prefix(), 3);
|
||||
let expected_second = "5".repeat(64);
|
||||
assert_eq!(second.resume_before().map(crate::BackfillSignature::as_str), std::option::Option::Some(expected_second.as_str()));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_008_after_anchor_not_reached_never_advances_checkpoint() {
|
||||
let anchor = match signature('1') {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let scope = crate::BackfillScope::after_address(ksp_core_lib::Pubkey::new_from_array([4_u8; 32]), anchor);
|
||||
let request = match request(scope) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let partial = match discovery(&request, &['7', '6', '5'], crate::BackfillDiscoveryBoundary::AfterAnchorNotReached) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let mut frontier = crate::CompletionFrontier::new(3);
|
||||
assert!(frontier.mark_durable(0).is_ok());
|
||||
assert!(frontier.mark_durable(1).is_ok());
|
||||
let checkpoint = match crate::checkpoint_from_frontier(&request, &partial, &frontier) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(checkpoint.completed_prefix(), 0);
|
||||
return;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-job-backfill-lib/unit_tests/discovery.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct PageCall {
|
||||
@@ -305,3 +305,53 @@ async fn pre_005_explicit_scope_never_calls_transport_and_preserves_network_scop
|
||||
assert!(source.calls().is_empty());
|
||||
return;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pre_008_before_resume_uses_checkpoint_cursor_instead_of_original_anchor() {
|
||||
let anchor = match signature('8') {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let resume = match signature('5') {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let scope = crate::BackfillScope::before_address(ksp_core_lib::Pubkey::new_from_array([8_u8; 32]), anchor);
|
||||
let request = match request(scope, 2, 2, 4) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let checkpoint = crate::BackfillCheckpoint::new(request.job_id().clone(), request.scope_fingerprint(), 2, std::option::Option::Some(resume.clone()));
|
||||
let request = match request.with_checkpoint(checkpoint) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let source = FakeSource::new(std::vec![std::vec![page_entry('4', 40)]]);
|
||||
let result = super::discover_with_source(&source, &request).await;
|
||||
assert!(result.is_ok());
|
||||
let calls = source.calls();
|
||||
assert_eq!(calls.len(), 1);
|
||||
assert_eq!(calls[0].before.as_deref(), std::option::Option::Some(resume.as_str()));
|
||||
return;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pre_008_latest_resume_restarts_from_current_latest_without_rpc_cursor() {
|
||||
let scope = crate::BackfillScope::latest_address(ksp_core_lib::Pubkey::new_from_array([9_u8; 32]));
|
||||
let request = match request(scope, 2, 2, 4) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let checkpoint = crate::BackfillCheckpoint::new(request.job_id().clone(), request.scope_fingerprint(), 2, std::option::Option::None);
|
||||
let request = match request.with_checkpoint(checkpoint) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let source = FakeSource::new(std::vec![std::vec![page_entry('7', 70)]]);
|
||||
let result = super::discover_with_source(&source, &request).await;
|
||||
assert!(result.is_ok());
|
||||
let calls = source.calls();
|
||||
assert_eq!(calls.len(), 1);
|
||||
assert_eq!(calls[0].before, std::option::Option::None);
|
||||
return;
|
||||
}
|
||||
|
||||
276
crates/ksp-job-backfill-lib/unit_tests/execution.rs
Normal file
276
crates/ksp-job-backfill-lib/unit_tests/execution.rs
Normal file
@@ -0,0 +1,276 @@
|
||||
// file: crates/ksp-job-backfill-lib/unit_tests/execution.rs
|
||||
// version: 1
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum FakeDisposition {
|
||||
Durable,
|
||||
Missing,
|
||||
Conflict,
|
||||
Failure,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct FakePlan {
|
||||
pending_polls: usize,
|
||||
disposition: FakeDisposition,
|
||||
}
|
||||
|
||||
struct FakeProcessor {
|
||||
plans: std::vec::Vec<FakePlan>,
|
||||
active: std::sync::atomic::AtomicUsize,
|
||||
maximum: std::sync::atomic::AtomicUsize,
|
||||
calls: std::sync::atomic::AtomicUsize,
|
||||
}
|
||||
|
||||
impl FakeProcessor {
|
||||
fn new(plans: std::vec::Vec<FakePlan>) -> Self {
|
||||
return Self {
|
||||
plans,
|
||||
active: std::sync::atomic::AtomicUsize::new(0),
|
||||
maximum: std::sync::atomic::AtomicUsize::new(0),
|
||||
calls: std::sync::atomic::AtomicUsize::new(0),
|
||||
};
|
||||
}
|
||||
|
||||
fn maximum(&self) -> usize {
|
||||
return self.maximum.load(std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
fn calls(&self) -> usize {
|
||||
return self.calls.load(std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl super::CandidateProcessor for FakeProcessor {
|
||||
fn process<'a>(&'a self, candidate: &'a crate::BackfillCandidate) -> super::CandidateProcessFuture<'a> {
|
||||
self.calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
let first_byte = candidate.identity().signature().as_str().as_bytes()[0];
|
||||
let plan_index = usize::from(first_byte.saturating_sub(b'1'));
|
||||
let plan = match self.plans.get(plan_index) {
|
||||
std::option::Option::Some(plan) => *plan,
|
||||
std::option::Option::None => FakePlan { pending_polls: 0, disposition: FakeDisposition::Failure },
|
||||
};
|
||||
return std::boxed::Box::pin(async move {
|
||||
let mut pending_polls = plan.pending_polls;
|
||||
let mut started = false;
|
||||
std::future::poll_fn(|context| {
|
||||
if !started {
|
||||
started = true;
|
||||
let active = self.active.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;
|
||||
self.maximum.fetch_max(active, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
if pending_polls != 0 {
|
||||
pending_polls -= 1;
|
||||
context.waker().wake_by_ref();
|
||||
return std::task::Poll::Pending;
|
||||
}
|
||||
self.active.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
|
||||
return std::task::Poll::Ready(());
|
||||
})
|
||||
.await;
|
||||
return planned_outcome(candidate, plan.disposition);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn planned_outcome(candidate: &crate::BackfillCandidate, disposition: FakeDisposition) -> ksp_core_lib::Result<crate::BackfillPersistenceOutcome> {
|
||||
if matches!(disposition, FakeDisposition::Failure) {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_BACKFILL_RAW_CONVERSION_INVALID, "planned candidate failure"));
|
||||
}
|
||||
let fill = candidate.identity().signature().as_str().as_bytes()[0];
|
||||
let reference =
|
||||
ksp_store_lib::RawTransactionReference::new(candidate.identity().network().clone(), ksp_store_lib::RawTransactionSignature::new([fill; 64]));
|
||||
let (entity, observation) = match disposition {
|
||||
FakeDisposition::Durable => (crate::BackfillEntityPersistence::AlreadyPresent, crate::BackfillObservationPersistence::AlreadyPresent),
|
||||
FakeDisposition::Missing => (crate::BackfillEntityPersistence::Missing, crate::BackfillObservationPersistence::NotApplicable),
|
||||
FakeDisposition::Conflict => (crate::BackfillEntityPersistence::Conflict, crate::BackfillObservationPersistence::NotRecorded),
|
||||
FakeDisposition::Failure => return std::result::Result::Err(super::execution_error("test.disposition")),
|
||||
};
|
||||
return std::result::Result::Ok(crate::BackfillPersistenceOutcome::new(reference, entity, observation));
|
||||
}
|
||||
|
||||
fn signature(character: char) -> std::option::Option<crate::BackfillSignature> {
|
||||
return crate::BackfillSignature::new(character.to_string().repeat(crate::MIN_BACKFILL_SIGNATURE_TEXT_BYTES)).ok();
|
||||
}
|
||||
|
||||
fn explicit_request(characters: &[char], concurrency: usize) -> std::option::Option<crate::BackfillRequest> {
|
||||
let mut signatures = std::vec::Vec::with_capacity(characters.len());
|
||||
for character in characters {
|
||||
let value = match signature(*character) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::option::Option::None,
|
||||
};
|
||||
signatures.push(value);
|
||||
}
|
||||
let scope = match crate::BackfillScope::explicit_signatures(signatures) {
|
||||
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:execution-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,
|
||||
characters.len(),
|
||||
concurrency,
|
||||
std::option::Option::None,
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn discovery(request: &crate::BackfillRequest, characters: &[char]) -> std::option::Option<crate::BackfillDiscovery> {
|
||||
let mut candidates = std::vec::Vec::with_capacity(characters.len());
|
||||
for character in characters {
|
||||
let signature = match signature(*character) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::option::Option::None,
|
||||
};
|
||||
let identity = crate::BackfillCandidateIdentity::new(request.network().clone(), signature);
|
||||
candidates.push(crate::BackfillCandidate::new(identity, std::option::Option::None));
|
||||
}
|
||||
return std::option::Option::Some(crate::BackfillDiscovery::new(
|
||||
request.network().clone(),
|
||||
request.scope_fingerprint(),
|
||||
candidates,
|
||||
0,
|
||||
crate::BackfillDiscoveryBoundary::ExplicitInput,
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pre_008_execution_is_bounded_and_reconciles_out_of_order_durable_completions() {
|
||||
let request = match explicit_request(&['1', '2', '3', '4'], 2) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let discovery = match discovery(&request, &['1', '2', '3', '4']) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let processor = FakeProcessor::new(std::vec![
|
||||
FakePlan { pending_polls: 6, disposition: FakeDisposition::Durable },
|
||||
FakePlan { pending_polls: 0, disposition: FakeDisposition::Durable },
|
||||
FakePlan { pending_polls: 2, disposition: FakeDisposition::Durable },
|
||||
FakePlan { pending_polls: 0, disposition: FakeDisposition::Durable },
|
||||
]);
|
||||
let result = super::execute_with_processor(&processor, &request, &discovery).await;
|
||||
let batch = match result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(batch.admitted_count(), 4);
|
||||
assert_eq!(batch.finished_count(), 4);
|
||||
assert_eq!(batch.durable_count(), 4);
|
||||
assert_eq!(batch.hole_count(), 0);
|
||||
assert_eq!(batch.maximum_in_flight(), 2);
|
||||
assert_eq!(processor.maximum(), 2);
|
||||
assert_eq!(batch.local_contiguous_completed(), 4);
|
||||
assert_eq!(batch.checkpoint().completed_prefix(), 4);
|
||||
assert!(!batch.is_partial());
|
||||
return;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pre_008_missing_is_non_fatal_but_blocks_frontier_while_later_candidates_continue() {
|
||||
let request = match explicit_request(&['1', '2', '3'], 2) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let discovery = match discovery(&request, &['1', '2', '3']) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let processor = FakeProcessor::new(std::vec![
|
||||
FakePlan { pending_polls: 1, disposition: FakeDisposition::Durable },
|
||||
FakePlan { pending_polls: 0, disposition: FakeDisposition::Missing },
|
||||
FakePlan { pending_polls: 0, disposition: FakeDisposition::Durable },
|
||||
]);
|
||||
let batch = match super::execute_with_processor(&processor, &request, &discovery).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(batch.admitted_count(), 3);
|
||||
assert_eq!(batch.finished_count(), 3);
|
||||
assert_eq!(batch.durable_count(), 2);
|
||||
assert_eq!(batch.hole_count(), 1);
|
||||
assert_eq!(batch.local_contiguous_completed(), 1);
|
||||
assert_eq!(batch.checkpoint().completed_prefix(), 1);
|
||||
assert_eq!(batch.failure_code(), std::option::Option::None);
|
||||
assert!(batch.is_partial());
|
||||
return;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pre_008_conflict_stops_new_admissions_and_drains_already_in_flight_work() {
|
||||
let request = match explicit_request(&['1', '2', '3', '4'], 2) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let discovery = match discovery(&request, &['1', '2', '3', '4']) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let processor = FakeProcessor::new(std::vec![
|
||||
FakePlan { pending_polls: 6, disposition: FakeDisposition::Durable },
|
||||
FakePlan { pending_polls: 0, disposition: FakeDisposition::Conflict },
|
||||
FakePlan { pending_polls: 0, disposition: FakeDisposition::Durable },
|
||||
FakePlan { pending_polls: 0, disposition: FakeDisposition::Durable },
|
||||
]);
|
||||
let batch = match super::execute_with_processor(&processor, &request, &discovery).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(processor.calls(), 2);
|
||||
assert_eq!(batch.admitted_count(), 2);
|
||||
assert_eq!(batch.finished_count(), 2);
|
||||
assert_eq!(batch.durable_count(), 1);
|
||||
assert_eq!(batch.hole_count(), 1);
|
||||
assert_eq!(batch.local_contiguous_completed(), 1);
|
||||
assert_eq!(batch.failure_code(), std::option::Option::Some(ksp_store_lib::ERROR_CODE_RAW_CONFLICT));
|
||||
assert!(batch.is_partial());
|
||||
return;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pre_008_explicit_resume_skips_only_the_checkpointed_contiguous_prefix() {
|
||||
let request = match explicit_request(&['1', '2', '3', '4'], 2) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let checkpoint = crate::BackfillCheckpoint::new(request.job_id().clone(), request.scope_fingerprint(), 2, std::option::Option::None);
|
||||
let request = match request.with_checkpoint(checkpoint) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let discovery = match discovery(&request, &['1', '2', '3', '4']) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let processor = FakeProcessor::new(std::vec![
|
||||
FakePlan { pending_polls: 0, disposition: FakeDisposition::Failure },
|
||||
FakePlan { pending_polls: 0, disposition: FakeDisposition::Failure },
|
||||
FakePlan { pending_polls: 0, disposition: FakeDisposition::Durable },
|
||||
FakePlan { pending_polls: 0, disposition: FakeDisposition::Durable },
|
||||
]);
|
||||
let batch = match super::execute_with_processor(&processor, &request, &discovery).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(processor.calls(), 2);
|
||||
assert_eq!(batch.admitted_count(), 2);
|
||||
assert_eq!(batch.local_contiguous_completed(), 4);
|
||||
assert_eq!(batch.checkpoint().completed_prefix(), 4);
|
||||
assert_eq!(batch.failure_code(), std::option::Option::None);
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user