mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
fix(core): make import cancellation test deterministic
This commit is contained in:
@@ -1,10 +1,13 @@
|
|||||||
mod support;
|
mod support;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, Condvar, Mutex};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::Duration;
|
||||||
|
|
||||||
use support::{share_path, CoreGuard, RecordingSink, TestNode};
|
use support::{share_path, CoreGuard, RecordingSink, TestNode};
|
||||||
use vnidrop::{CoreLimits, ShareMetadataInput, ShareSource, SourceKind, TransferAccessMode};
|
use vnidrop::{
|
||||||
|
CoreEvent, CoreEventSink, CoreLimits, ShareMetadataInput, ShareSource, SourceKind,
|
||||||
|
TransferAccessMode,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn share_creation_persists_selected_access_mode_atomically() {
|
fn share_creation_persists_selected_access_mode_atomically() {
|
||||||
@@ -324,55 +327,87 @@ fn source_limit_rejection_creates_no_transfer_state() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn cancellation_during_import_is_durable() {
|
fn cancellation_during_import_is_durable() {
|
||||||
let source_dir = tempfile::tempdir().unwrap();
|
let source_dir = tempfile::tempdir().unwrap();
|
||||||
let source_path = source_dir.path().join("large.bin");
|
let source_path = source_dir.path().join("gated.bin");
|
||||||
std::fs::File::create(&source_path)
|
std::fs::write(&source_path, vec![0u8; 256 * 1024]).unwrap();
|
||||||
.unwrap()
|
let data_dir = tempfile::tempdir().unwrap();
|
||||||
.set_len(256 * 1024 * 1024)
|
let import_gate = Arc::new(ImportStartedGate::default());
|
||||||
.unwrap();
|
let sender = CoreGuard::start(data_dir.path(), import_gate.clone());
|
||||||
let sender = TestNode::new();
|
let core = sender.arc();
|
||||||
let core = sender.core.arc();
|
|
||||||
let worker = std::thread::spawn(move || {
|
let worker = std::thread::spawn(move || {
|
||||||
core.share_files(
|
core.share_files(
|
||||||
vec![ShareSource {
|
vec![ShareSource {
|
||||||
kind: SourceKind::Path,
|
kind: SourceKind::Path,
|
||||||
value: source_path.to_string_lossy().to_string(),
|
value: source_path.to_string_lossy().to_string(),
|
||||||
display_name: Some("large.bin".to_string()),
|
display_name: Some("gated.bin".to_string()),
|
||||||
is_directory: false,
|
is_directory: false,
|
||||||
}],
|
}],
|
||||||
ShareMetadataInput {
|
ShareMetadataInput {
|
||||||
transfer_id: 25,
|
transfer_id: 25,
|
||||||
transfer_name: Some("large".to_string()),
|
transfer_name: Some("gated".to_string()),
|
||||||
sender_name: None,
|
sender_name: None,
|
||||||
access_mode: TransferAccessMode::ApprovalRequired,
|
access_mode: TransferAccessMode::ApprovalRequired,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let started = Instant::now();
|
// The synchronous event callback holds the worker after active-transfer
|
||||||
loop {
|
// registration, so cancellation cannot race a fast import to completion.
|
||||||
if sender
|
import_gate.wait_until_blocked();
|
||||||
.core
|
let cancel_result = sender.cancel_transfer(25);
|
||||||
.list_transfers()
|
import_gate.release();
|
||||||
.unwrap()
|
cancel_result.unwrap();
|
||||||
.iter()
|
|
||||||
.any(|transfer| transfer.transfer_id == 25 && transfer.status == "importing")
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
assert!(started.elapsed() < Duration::from_secs(10));
|
|
||||||
std::thread::sleep(Duration::from_millis(10));
|
|
||||||
}
|
|
||||||
sender.core.cancel_transfer(25).unwrap();
|
|
||||||
assert!(worker.join().unwrap().is_err());
|
assert!(worker.join().unwrap().is_err());
|
||||||
|
|
||||||
let transfer = sender
|
let transfer = sender
|
||||||
.core
|
|
||||||
.list_transfers()
|
.list_transfers()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|transfer| transfer.transfer_id == 25)
|
.find(|transfer| transfer.transfer_id == 25)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(transfer.status, "cancelled");
|
assert_eq!(transfer.status, "cancelled");
|
||||||
assert_eq!(sender.core.status().active_transfers, 0);
|
assert_eq!(sender.status().active_transfers, 0);
|
||||||
assert_eq!(sender.core.status().active_shares, 0);
|
assert_eq!(sender.status().active_shares, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ImportStartedGate {
|
||||||
|
state: Mutex<ImportGateState>,
|
||||||
|
changed: Condvar,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ImportGateState {
|
||||||
|
blocked: bool,
|
||||||
|
released: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImportStartedGate {
|
||||||
|
fn wait_until_blocked(&self) {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
let (state, _) = self
|
||||||
|
.changed
|
||||||
|
.wait_timeout_while(state, Duration::from_secs(5), |state| !state.blocked)
|
||||||
|
.unwrap();
|
||||||
|
assert!(state.blocked, "import did not reach the start gate");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release(&self) {
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.released = true;
|
||||||
|
self.changed.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoreEventSink for ImportStartedGate {
|
||||||
|
fn on_event(&self, event: CoreEvent) {
|
||||||
|
if event.phase != "import" || event.kind != "started" {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.blocked = true;
|
||||||
|
self.changed.notify_all();
|
||||||
|
while !state.released {
|
||||||
|
state = self.changed.wait(state).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user