use std::borrow::Cow;
use std::path::Path;
use std::sync::Arc;
use bytes::{Bytes, BytesMut};
use futures_lite::future::{self, block_on};
use pi_async_fs::{
DetachableWriteBuffer, FileAccessMode, FileIo, FileNamespace, LocalFile,
LocalFileNamespace, OverwriteTargetEvidence, TransferProgress,
};
use pi_result::ErrorKind;
fn overwrite_and_assert<B>(
file: &mut LocalFile,
path: &Path,
buffer: B,
expected: &[u8],
)
where
B: DetachableWriteBuffer + Send,
B::Detached: Send,
B::Recovery: Send,
{
block_on(file.overwrite_all(buffer)).expect("该 buffer 形态应完整覆盖成功");
assert_eq!(std::fs::read(path).unwrap(), expected);
}
#[test]
fn test_local_file_overwrite_vec_replaces_content_and_removes_old_tail() {
let temporary_directory = tempfile::tempdir().expect("应能建立真实临时目录");
let path = temporary_directory.path().join("target");
std::fs::write(&path, b"old-content-with-tail")
.expect("应能建立真实测试文件");
let namespace = LocalFileNamespace::new();
let mut file = block_on(namespace.open(&path, FileAccessMode::Overwrite))
.expect("覆盖资源应能打开且不提前修改文件");
block_on(file.overwrite_all(b"new".to_vec()))
.expect("完整 Vec 应覆盖成功");
assert_eq!(std::fs::read(&path).unwrap(), b"new");
assert_eq!(block_on(file.byte_len()).unwrap(), 3);
}
#[test]
fn test_local_file_overwrite_empty_buffer_clears_file() {
let temporary_directory = tempfile::tempdir().expect("应能建立真实临时目录");
let path = temporary_directory.path().join("target");
std::fs::write(&path, b"must-be-removed").expect("应能建立真实测试文件");
let namespace = LocalFileNamespace::new();
let mut file = block_on(namespace.open(&path, FileAccessMode::Overwrite))
.expect("覆盖资源应能打开");
block_on(file.overwrite_all(Vec::<u8>::new()))
.expect("空输入应把目标覆盖为空文件");
assert_eq!(std::fs::read(&path).unwrap(), b"");
assert_eq!(block_on(file.byte_len()).unwrap(), 0);
}
#[test]
fn test_local_file_overwrite_supports_every_builtin_write_buffer_shape() {
let temporary_directory = tempfile::tempdir().expect("应能建立真实临时目录");
let path = temporary_directory.path().join("target");
std::fs::write(&path, b"initial").expect("应能建立真实测试文件");
let namespace = LocalFileNamespace::new();
let mut file = block_on(namespace.open(&path, FileAccessMode::Overwrite))
.expect("覆盖资源应能打开");
overwrite_and_assert(&mut file, &path, b"vec".to_vec(), b"vec");
overwrite_and_assert(
&mut file,
&path,
Vec::from(&b"box"[..]).into_boxed_slice(),
b"box",
);
overwrite_and_assert(
&mut file,
&path,
Arc::<[u8]>::from(&b"arc"[..]),
b"arc",
);
let cow_owned: Cow<'static, [u8]> = Cow::Owned(b"cow-owned".to_vec());
overwrite_and_assert(&mut file, &path, cow_owned, b"cow-owned");
let cow_borrowed: Cow<'_, [u8]> = Cow::Borrowed(b"cow-borrowed");
overwrite_and_assert(&mut file, &path, cow_borrowed, b"cow-borrowed");
overwrite_and_assert(
&mut file,
&path,
Bytes::from_static(b"bytes"),
b"bytes",
);
overwrite_and_assert(
&mut file,
&path,
BytesMut::from(&b"bytes-mut"[..]),
b"bytes-mut",
);
let slice = &b"slice-ref"[..];
overwrite_and_assert(&mut file, &path, slice, b"slice-ref");
let vec_buffer = b"vec-ref".to_vec();
overwrite_and_assert(&mut file, &path, &vec_buffer, b"vec-ref");
let box_buffer = Vec::from(&b"box-ref"[..]).into_boxed_slice();
overwrite_and_assert(&mut file, &path, &box_buffer, b"box-ref");
let arc_buffer = Arc::<[u8]>::from(&b"arc-ref"[..]);
overwrite_and_assert(&mut file, &path, &arc_buffer, b"arc-ref");
let cow_buffer: Cow<'_, [u8]> = Cow::Borrowed(b"cow-ref");
overwrite_and_assert(&mut file, &path, &cow_buffer, b"cow-ref");
let bytes_buffer = Bytes::from_static(b"bytes-ref");
overwrite_and_assert(&mut file, &path, &bytes_buffer, b"bytes-ref");
let bytes_mut_buffer = BytesMut::from(&b"bytes-mut-ref"[..]);
overwrite_and_assert(
&mut file,
&path,
&bytes_mut_buffer,
b"bytes-mut-ref",
);
}
#[test]
fn test_local_file_overwrite_rejects_every_other_role_and_restores_original_vec() {
let temporary_directory = tempfile::tempdir().expect("应能建立真实临时目录");
let path = temporary_directory.path().join("target");
let original = b"unchanged";
std::fs::write(&path, original).expect("应能建立真实测试文件");
let namespace = LocalFileNamespace::new();
for access in [
FileAccessMode::Read,
FileAccessMode::Append,
FileAccessMode::ReadMmap,
FileAccessMode::ReadWriteMmap,
FileAccessMode::Truncate,
] {
let mut file = block_on(namespace.open(&path, access))
.expect("每种非覆盖角色都应能独立打开");
let mut buffer = Vec::with_capacity(32);
buffer.extend_from_slice(b"payload");
let pointer = buffer.as_ptr();
let capacity = buffer.capacity();
let failure = block_on(file.overwrite_all(buffer))
.expect_err("非覆盖角色必须在任何副作用前失败");
let (error, buffer, progress, evidence) = failure.into_parts();
assert_eq!(error.current_context(), &ErrorKind::InvalidState);
assert_eq!(buffer.as_ptr(), pointer);
assert_eq!(buffer.capacity(), capacity);
assert_eq!(buffer, b"payload");
assert_eq!(progress, TransferProgress::Exact { bytes: 0 });
assert_eq!(evidence, OverwriteTargetEvidence::Unchanged);
assert_eq!(std::fs::read(&path).unwrap(), original);
}
}
#[test]
fn test_local_file_cancelled_overwrite_keeps_exclusive_lease_until_actor_finishes() {
const PAYLOAD_LEN: usize = 64 * 1024 * 1024;
let temporary_directory = tempfile::tempdir().expect("应能建立真实临时目录");
let path = temporary_directory.path().join("target");
std::fs::write(&path, b"old").expect("应能建立真实测试文件");
let namespace = LocalFileNamespace::new();
let mut overwrite_resource = block_on(
namespace.open(&path, FileAccessMode::Overwrite),
)
.expect("覆盖资源应能打开");
let read_resource = block_on(namespace.open(&path, FileAccessMode::Read))
.expect("读取资源应能独立打开");
let mut append_resource = block_on(namespace.open(&path, FileAccessMode::Append))
.expect("追加资源应能独立打开");
let mut competing_overwrite = block_on(
namespace.open(&path, FileAccessMode::Overwrite),
)
.expect("第二个覆盖资源应能独立打开");
let overwrite = overwrite_resource.overwrite_all(vec![b'D'; PAYLOAD_LEN]);
let first_poll = block_on(future::poll_once(overwrite));
assert!(first_poll.is_none(), "大覆盖第一次轮询应仍在执行中");
let mut read_buffer = [0xaa; 4];
let read_error = block_on(read_resource.read_at(
0,
&mut read_buffer,
pi_async_fs::ReadTargetRegion::full(),
))
.expect_err("活动覆盖期间普通读取必须立即冲突");
assert_eq!(read_error.current_context(), &ErrorKind::Conflict);
assert_eq!(read_buffer, [0xaa; 4]);
let length_error = block_on(read_resource.byte_len())
.expect_err("活动覆盖期间长度查询必须立即冲突");
assert_eq!(length_error.current_context(), &ErrorKind::Conflict);
let append_failure = block_on(append_resource.append(b"append".to_vec()))
.expect_err("活动覆盖期间追加必须立即冲突");
let (append_error, append_buffer, append_progress) =
append_failure.into_parts();
assert_eq!(append_error.current_context(), &ErrorKind::Conflict);
assert_eq!(append_buffer, b"append");
assert_eq!(append_progress, TransferProgress::Exact { bytes: 0 });
let overwrite_failure = block_on(
competing_overwrite.overwrite_all(b"other".to_vec()),
)
.expect_err("活动覆盖期间其它覆盖必须立即冲突");
let (error, buffer, progress, evidence) = overwrite_failure.into_parts();
assert_eq!(error.current_context(), &ErrorKind::Conflict);
assert_eq!(buffer, b"other");
assert_eq!(progress, TransferProgress::Exact { bytes: 0 });
assert_eq!(evidence, OverwriteTargetEvidence::Unchanged);
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
let final_len = loop {
match block_on(overwrite_resource.byte_len()) {
Ok(length) => break length,
Err(error) if error.current_context() == &ErrorKind::Conflict => {
assert!(
std::time::Instant::now() < deadline,
"覆盖内部任务应在截止前完成"
);
std::thread::sleep(std::time::Duration::from_millis(1));
}
Err(error) => panic!("覆盖完成后的长度查询异常失败: {error:?}"),
}
};
assert_eq!(final_len, PAYLOAD_LEN as u64);
let content = std::fs::read(&path).expect("应能读取覆盖后的文件");
assert_eq!(content.len(), PAYLOAD_LEN);
assert!(content.iter().all(|byte| *byte == b'D'));
}