test: add proto and end-to-end transfer tests
This commit is contained in:
@@ -363,8 +363,31 @@ mod tests {
|
||||
#[test]
|
||||
fn rejects_data_larger_than_512() {
|
||||
let mut bytes = vec![0, 3, 0, 1];
|
||||
bytes.extend(std::iter::repeat(0x61).take(BLOCK_SIZE + 1));
|
||||
bytes.extend(std::iter::repeat_n(0x61, BLOCK_SIZE + 1));
|
||||
let err = Packet::decode(&bytes).unwrap_err();
|
||||
assert!(matches!(err, DecodeError::OversizeData(513)));
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[test]
|
||||
fn mode_is_case_insensitive() {
|
||||
let req = Packet::decode(b"\0\x01file\0NeTaScIi\0").unwrap();
|
||||
assert!(matches!(
|
||||
req,
|
||||
Packet::Rrq(Request {
|
||||
mode: Mode::NetAscii,
|
||||
..
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[test]
|
||||
fn error_roundtrip() {
|
||||
let pkt = Packet::Error {
|
||||
code: ErrorCode::AccessViolation,
|
||||
message: "nope".to_string(),
|
||||
};
|
||||
assert_eq!(Packet::decode(&pkt.encode()).unwrap(), pkt);
|
||||
}
|
||||
}
|
||||
|
||||
190
crates/pfs-tftp-sync/tests/end_to_end.rs
Normal file
190
crates/pfs-tftp-sync/tests/end_to_end.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
#![allow(clippy::expect_used, clippy::unwrap_used)]
|
||||
|
||||
use std::{
|
||||
fs,
|
||||
net::{SocketAddr, UdpSocket},
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
},
|
||||
thread::JoinHandle,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use pfs_tftp_sync::{Client, ClientConfig, Mode, Server, ServerConfig};
|
||||
|
||||
#[test]
|
||||
fn octet_put_and_get_roundtrip() {
|
||||
let server_root = TempDir::new("pfs_tftp_server_root");
|
||||
let local_root = TempDir::new("pfs_tftp_local_root");
|
||||
|
||||
let (addr, shutdown, handle) = start_server(server_root.path(), true);
|
||||
|
||||
let client = Client::new(addr, test_client_config());
|
||||
|
||||
let upload_local = local_root.path().join("upload.bin");
|
||||
let upload_remote = "upload.bin";
|
||||
let upload_data = vec![0xA5; 1500];
|
||||
fs::write(&upload_local, &upload_data).unwrap();
|
||||
client
|
||||
.put(&upload_local, upload_remote, Mode::Octet)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fs::read(server_root.path().join(upload_remote)).unwrap(),
|
||||
upload_data
|
||||
);
|
||||
|
||||
let download_remote = "download.bin";
|
||||
let download_data = (0u8..=200).cycle().take(2048).collect::<Vec<u8>>();
|
||||
fs::write(server_root.path().join(download_remote), &download_data).unwrap();
|
||||
|
||||
let download_local = local_root.path().join("download.bin");
|
||||
client
|
||||
.get(download_remote, &download_local, Mode::Octet)
|
||||
.unwrap();
|
||||
assert_eq!(fs::read(download_local).unwrap(), download_data);
|
||||
|
||||
stop_server(&shutdown, handle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn octet_exact_512_bytes_requires_final_empty_block() {
|
||||
let server_root = TempDir::new("pfs_tftp_server_root_512");
|
||||
let local_root = TempDir::new("pfs_tftp_local_root_512");
|
||||
|
||||
let (addr, shutdown, handle) = start_server(server_root.path(), true);
|
||||
let client = Client::new(addr, test_client_config());
|
||||
|
||||
let data = vec![0x11; 512];
|
||||
|
||||
fs::write(server_root.path().join("exact512.bin"), &data).unwrap();
|
||||
let local = local_root.path().join("exact512.bin");
|
||||
client.get("exact512.bin", &local, Mode::Octet).unwrap();
|
||||
assert_eq!(fs::read(local).unwrap(), data);
|
||||
|
||||
let local_up = local_root.path().join("exact512_upload.bin");
|
||||
fs::write(&local_up, &data).unwrap();
|
||||
client
|
||||
.put(&local_up, "exact512_upload.bin", Mode::Octet)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fs::read(server_root.path().join("exact512_upload.bin")).unwrap(),
|
||||
data
|
||||
);
|
||||
|
||||
stop_server(&shutdown, handle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn netascii_roundtrip_preserves_newlines_and_cr() {
|
||||
let server_root = TempDir::new("pfs_tftp_server_root_netascii");
|
||||
let local_root = TempDir::new("pfs_tftp_local_root_netascii");
|
||||
|
||||
let (addr, shutdown, handle) = start_server(server_root.path(), true);
|
||||
let client = Client::new(addr, test_client_config());
|
||||
|
||||
let data = b"line1\nline2\rline3\n".to_vec();
|
||||
|
||||
let local_up = local_root.path().join("text.txt");
|
||||
fs::write(&local_up, &data).unwrap();
|
||||
client.put(&local_up, "text.txt", Mode::NetAscii).unwrap();
|
||||
assert_eq!(fs::read(server_root.path().join("text.txt")).unwrap(), data);
|
||||
|
||||
let local_down = local_root.path().join("text_downloaded.txt");
|
||||
client.get("text.txt", &local_down, Mode::NetAscii).unwrap();
|
||||
assert_eq!(fs::read(local_down).unwrap(), data);
|
||||
|
||||
stop_server(&shutdown, handle);
|
||||
}
|
||||
|
||||
fn test_client_config() -> ClientConfig {
|
||||
ClientConfig {
|
||||
timeout: Duration::from_millis(200),
|
||||
retries: 20,
|
||||
dally_timeout: Duration::from_millis(200),
|
||||
dally_retries: 2,
|
||||
}
|
||||
}
|
||||
|
||||
fn start_server(root: &Path, allow_write: bool) -> (SocketAddr, Arc<AtomicBool>, JoinHandle<()>) {
|
||||
let addr = reserve_local_addr();
|
||||
|
||||
let cfg = ServerConfig {
|
||||
bind: addr,
|
||||
root: root.to_path_buf(),
|
||||
allow_write,
|
||||
overwrite: true,
|
||||
timeout: Duration::from_millis(200),
|
||||
retries: 20,
|
||||
dally_timeout: Duration::from_millis(200),
|
||||
dally_retries: 2,
|
||||
..ServerConfig::default()
|
||||
};
|
||||
|
||||
let shutdown = Arc::new(AtomicBool::new(false));
|
||||
let shutdown_for_thread = Arc::clone(&shutdown);
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
Server::new(cfg)
|
||||
.serve_until(shutdown_for_thread.as_ref())
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
(addr, shutdown, handle)
|
||||
}
|
||||
|
||||
fn stop_server(shutdown: &Arc<AtomicBool>, handle: JoinHandle<()>) {
|
||||
shutdown.store(true, Ordering::Relaxed);
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
fn reserve_local_addr() -> SocketAddr {
|
||||
let sock = UdpSocket::bind(SocketAddr::from(([127, 0, 0, 1], 0))).unwrap();
|
||||
sock.local_addr().unwrap()
|
||||
}
|
||||
|
||||
struct TempDir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl TempDir {
|
||||
fn new(prefix: &str) -> Self {
|
||||
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
let mut path = test_tmp_root();
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_nanos())
|
||||
.unwrap_or(0);
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
path.push(format!("{prefix}_{now}_{}_{}", std::process::id(), id));
|
||||
fs::create_dir_all(&path).unwrap();
|
||||
Self { path }
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TempDir {
|
||||
fn drop(&mut self) {
|
||||
let _ignored = fs::remove_dir_all(&self.path);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_tmp_root() -> PathBuf {
|
||||
// Keep temporary test artifacts inside the workspace, since the Codex CLI
|
||||
// sandbox does not allow writing to system temp directories.
|
||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let workspace_root = manifest_dir
|
||||
.parent()
|
||||
.and_then(Path::parent)
|
||||
.unwrap_or(&manifest_dir);
|
||||
workspace_root
|
||||
.join("target")
|
||||
.join("tmp")
|
||||
.join("pfs-tftp-sync-tests")
|
||||
}
|
||||
Reference in New Issue
Block a user