test: add serve-all integration tests (push, fs-sync, mixed protocol)
Three new subprocess tests exercise the serve-all dispatch layer: - serve_all_push_revisions: onion revisions dispatched correctly - serve_all_fs_sync_cold_copy: FsDirManifest dispatch path - serve_all_mixed_protocol_push_then_fs_sync: both protocols on same server without interference Also adds ServeAllServer harness and imports Path in subprocess.rs. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
50001b64ac
commit
dcd2106da4
@@ -16,7 +16,7 @@
|
||||
//! synchronisation point before issuing client commands.
|
||||
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use clawhdf5_onion::writer::OnionFile;
|
||||
@@ -683,7 +683,7 @@ fn ssh_push_cold_copy() {
|
||||
let fake_ssh = tmp.path().join("fake-ssh");
|
||||
write_fake_ssh_onion(&fake_ssh);
|
||||
|
||||
let (src_dir, src_h5, _) = make_versioned_h5(10);
|
||||
let (_src_dir, src_h5, _) = make_versioned_h5(10);
|
||||
let dst_dir = TempDir::new().unwrap();
|
||||
let dst_h5 = dst_dir.path().join("data.h5");
|
||||
std::fs::write(&dst_h5, H5_MAGIC).unwrap();
|
||||
@@ -750,3 +750,208 @@ fn ssh_pull_cold_copy() {
|
||||
// Suppress unused-variable warnings.
|
||||
drop(dst_dir);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// serve-all integration tests
|
||||
//
|
||||
// serve-all listens on one port and dispatches incoming connections to the
|
||||
// correct handler based on the first SyncMessage variant received:
|
||||
// - IbltRequest / ManifestRequest → onion revision handler
|
||||
// - FsDirManifest → FS general-file handler
|
||||
// - Hdf5ManifestRequest → HDF5 dataset handler
|
||||
//
|
||||
// These tests verify that all three dispatch paths work correctly.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// A running `clawsync serve-all` process.
|
||||
struct ServeAllServer {
|
||||
child: std::process::Child,
|
||||
/// Bound address ("127.0.0.1:PORT").
|
||||
pub addr: String,
|
||||
}
|
||||
|
||||
impl ServeAllServer {
|
||||
/// Start `clawsync serve-all <dir> --bind 127.0.0.1:0`.
|
||||
fn start(dir: &Path) -> Self {
|
||||
let mut child = Command::new(BIN)
|
||||
.args([
|
||||
"serve-all",
|
||||
dir.to_str().unwrap(),
|
||||
"--bind",
|
||||
"127.0.0.1:0",
|
||||
])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.expect("failed to spawn clawsync serve-all");
|
||||
|
||||
let stdout = child.stdout.take().unwrap();
|
||||
let mut reader = BufReader::new(stdout);
|
||||
let mut line = String::new();
|
||||
reader
|
||||
.read_line(&mut line)
|
||||
.expect("serve-all did not print a startup line");
|
||||
|
||||
// "ClawSync server listening on 127.0.0.1:PORT"
|
||||
let addr = line
|
||||
.trim()
|
||||
.strip_prefix("ClawSync server listening on ")
|
||||
.unwrap_or_else(|| panic!("unexpected serve-all startup line: {line:?}"))
|
||||
.to_string();
|
||||
|
||||
// Drain stdout so the server doesn't get SIGPIPE.
|
||||
std::thread::spawn(move || {
|
||||
let mut buf = String::new();
|
||||
while reader.read_line(&mut buf).unwrap_or(0) > 0 {
|
||||
buf.clear();
|
||||
}
|
||||
});
|
||||
|
||||
Self { child, addr }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ServeAllServer {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.child.kill();
|
||||
let _ = self.child.wait();
|
||||
}
|
||||
}
|
||||
|
||||
/// `serve-all` dispatch: onion push → push 5 revisions to a serve-all instance
|
||||
/// (via the ManifestRequest / IbltRequest dispatch path).
|
||||
#[test]
|
||||
fn serve_all_push_revisions() {
|
||||
let root = TempDir::new().unwrap();
|
||||
let server = ServeAllServer::start(root.path());
|
||||
|
||||
let (src_dir, src_h5, _) = make_versioned_h5(5);
|
||||
let agent_path = format!("{}/agent.claws", server.addr);
|
||||
|
||||
let out = Command::new(BIN)
|
||||
.args(["push", src_h5.to_str().unwrap(), &agent_path])
|
||||
.output()
|
||||
.expect("failed to spawn clawsync push");
|
||||
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"serve-all push failed:\nstdout: {}\nstderr: {}",
|
||||
String::from_utf8_lossy(&out.stdout),
|
||||
String::from_utf8_lossy(&out.stderr),
|
||||
);
|
||||
|
||||
let count = parse_push_revision_count(&String::from_utf8_lossy(&out.stdout));
|
||||
assert_eq!(count, Some(5), "expected 5 revisions pushed via serve-all");
|
||||
|
||||
// Verify the onion file was written under the serve-all root.
|
||||
let written = await_revisions(&root.path().join("agent.claws"), 5);
|
||||
assert_eq!(written, 5, "serve-all did not persist 5 revisions");
|
||||
|
||||
drop(src_dir);
|
||||
}
|
||||
|
||||
/// `serve-all` dispatch: FS sync → sync a directory tree via the FsDirManifest
|
||||
/// dispatch path.
|
||||
#[test]
|
||||
fn serve_all_fs_sync_cold_copy() {
|
||||
use std::fs;
|
||||
|
||||
let root = TempDir::new().unwrap();
|
||||
let server = ServeAllServer::start(root.path());
|
||||
|
||||
// Build a small source tree.
|
||||
let src = TempDir::new().unwrap();
|
||||
fs::write(src.path().join("alpha.bin"), vec![0xA0u8; 4096]).unwrap();
|
||||
fs::write(src.path().join("beta.bin"), vec![0xB0u8; 8192]).unwrap();
|
||||
let sub = src.path().join("sub");
|
||||
fs::create_dir(&sub).unwrap();
|
||||
fs::write(sub.join("gamma.bin"), vec![0xC0u8; 2048]).unwrap();
|
||||
|
||||
// FsSyncServer always writes relative paths into its serve_root; the path
|
||||
// component of the remote URL is not forwarded — files land directly in root.
|
||||
let remote = format!("{}/", server.addr);
|
||||
|
||||
let out = Command::new(BIN)
|
||||
.args(["sync", src.path().to_str().unwrap(), &remote])
|
||||
.output()
|
||||
.expect("failed to spawn clawsync sync");
|
||||
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"serve-all fs sync failed:\nstdout: {}\nstderr: {}",
|
||||
String::from_utf8_lossy(&out.stdout),
|
||||
String::from_utf8_lossy(&out.stderr),
|
||||
);
|
||||
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
assert!(
|
||||
stdout.contains("3 added"),
|
||||
"expected 3 added files; got: {stdout}"
|
||||
);
|
||||
|
||||
// Verify files landed directly under root (serve_root = serve-all root).
|
||||
let check = |rel: &str| {
|
||||
let p = root.path().join(rel);
|
||||
assert!(p.exists(), "missing: {rel}");
|
||||
};
|
||||
check("alpha.bin");
|
||||
check("beta.bin");
|
||||
check("sub/gamma.bin");
|
||||
}
|
||||
|
||||
/// `serve-all` dispatch: multiple protocol types on the same server — push
|
||||
/// revisions then FS-sync files, verifying that both dispatch paths co-exist
|
||||
/// without interfering.
|
||||
#[test]
|
||||
fn serve_all_mixed_protocol_push_then_fs_sync() {
|
||||
use std::fs;
|
||||
|
||||
let root = TempDir::new().unwrap();
|
||||
let server = ServeAllServer::start(root.path());
|
||||
|
||||
// 1) Push 3 onion revisions.
|
||||
let (_src_dir, src_h5, _) = make_versioned_h5(3);
|
||||
let agent_path = format!("{}/memory.claws", server.addr);
|
||||
|
||||
let push_out = Command::new(BIN)
|
||||
.args(["push", src_h5.to_str().unwrap(), &agent_path])
|
||||
.output()
|
||||
.expect("failed to spawn clawsync push");
|
||||
|
||||
assert!(
|
||||
push_out.status.success(),
|
||||
"push failed:\nstdout: {}\nstderr: {}",
|
||||
String::from_utf8_lossy(&push_out.stdout),
|
||||
String::from_utf8_lossy(&push_out.stderr),
|
||||
);
|
||||
let push_count = parse_push_revision_count(&String::from_utf8_lossy(&push_out.stdout));
|
||||
assert_eq!(push_count, Some(3));
|
||||
|
||||
// 2) FS-sync 2 files (separate sub-directory so paths don't collide).
|
||||
let src_fs = TempDir::new().unwrap();
|
||||
fs::write(src_fs.path().join("x.bin"), vec![0xAAu8; 1024]).unwrap();
|
||||
fs::write(src_fs.path().join("y.bin"), vec![0xBBu8; 2048]).unwrap();
|
||||
|
||||
// Path component is ignored by FsSyncServer; sync to root directly.
|
||||
let remote_fs = format!("{}/", server.addr);
|
||||
let sync_out = Command::new(BIN)
|
||||
.args(["sync", src_fs.path().to_str().unwrap(), &remote_fs])
|
||||
.output()
|
||||
.expect("failed to spawn clawsync sync");
|
||||
|
||||
assert!(
|
||||
sync_out.status.success(),
|
||||
"fs sync failed:\nstdout: {}\nstderr: {}",
|
||||
String::from_utf8_lossy(&sync_out.stdout),
|
||||
String::from_utf8_lossy(&sync_out.stderr),
|
||||
);
|
||||
let sync_stdout = String::from_utf8_lossy(&sync_out.stdout);
|
||||
assert!(
|
||||
sync_stdout.contains("2 added"),
|
||||
"expected 2 added; got: {sync_stdout}"
|
||||
);
|
||||
|
||||
// Verify onion revisions survived the second connection.
|
||||
let written = await_revisions(&root.path().join("memory.claws"), 3);
|
||||
assert_eq!(written, 3, "onion revisions lost after FS sync on same server");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user