From be0feb2b0d0070f23bc9cd348c877aa3ad6c8b7b Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 6 Jun 2026 11:28:45 -0500 Subject: fix: build route-specific rsync specs and defer both-remote to Phase 6 duet--rsync-command built one direct rsync argv for every pair, rendering each remote endpoint as host:path. That is right when at most one endpoint is remote, but rsync refuses a source and destination that are both remote, so the argv was unexecutable for every remote-to-remote route, not just the round-trip. Now rsync builds a single argv only for :local and :local-remote, the routes it can run in one invocation. A both-remote pair returns a deferred spec: nil argv, an :exec-mode marker, and the route. Phase 6 reads the route to run rsync on a host (same-host), route through this machine (round-trip), or go direct host-to-host when the override is set. transfer-spec now carries the classified :src-endpoint and :dst-endpoint alongside the route, so execution has the structured endpoints and never has to reinterpret an argv string. The runnable-shape check from the in-process-mode fix extends to accept :exec-mode, so a deferred spec reads as a real plan rather than a broken nil-argv command. Tests cover local-to-remote and remote-to-local argv shapes including a port, and the same-host, round-trip, and direct routes each producing a deferred spec. --- tests/test-duet-transfer.el | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) (limited to 'tests') diff --git a/tests/test-duet-transfer.el b/tests/test-duet-transfer.el index ebe5a2c..84cf678 100644 --- a/tests/test-duet-transfer.el +++ b/tests/test-duet-transfer.el @@ -128,6 +128,58 @@ (should (eq t (plist-get spec :tramp))) (should (null (plist-get spec :argv)))))) +;;; Route-specific rsync specs (a single argv only when <=1 endpoint is remote) + +(ert-deftest test-duet-transfer-spec-local-to-remote-argv () + "local->remote builds one rsync argv with an ssh transport and a remote dest." + (test-duet-transfer--with-builtins + (let ((spec (duet--transfer-spec '("/tmp/a/f") "/ssh:user@host:/b" nil))) + (should (eq :local-remote (plist-get spec :route))) + (should (member "-e" (plist-get spec :argv))) + (should (member "user@host:/b" (plist-get spec :argv))) + (should (member "/tmp/a/f" (plist-get spec :argv)))))) + +(ert-deftest test-duet-transfer-spec-local-to-remote-honors-port () + "A remote endpoint port travels in the rsync ssh transport flag." + (test-duet-transfer--with-builtins + (let ((spec (duet--transfer-spec '("/tmp/a/f") "/ssh:host#2222:/b" nil))) + (should (member "ssh -p 2222" (plist-get spec :argv)))))) + +(ert-deftest test-duet-transfer-spec-remote-to-local-argv () + "remote->local builds one rsync argv with the remote source." + (test-duet-transfer--with-builtins + (let ((spec (duet--transfer-spec '("/ssh:host:/a/f") "/tmp/b" nil))) + (should (eq :local-remote (plist-get spec :route))) + (should (member "host:/a/f" (plist-get spec :argv))) + (should (member "/tmp/b" (plist-get spec :argv)))))) + +(ert-deftest test-duet-transfer-spec-same-host-remote-is-deferred () + "A same-host remote pair yields a deferred spec, not a direct rsync argv." + (test-duet-transfer--with-builtins + (let ((spec (duet--transfer-spec '("/ssh:user@host:/a/f") "/ssh:user@host:/b" nil))) + (should (eq :remote-same-host (plist-get spec :route))) + (should (null (plist-get spec :argv))) + (should (eq 'rsync-remote-to-remote (plist-get spec :exec-mode))) + (should (plist-get spec :src-endpoint)) + (should (plist-get spec :dst-endpoint))))) + +(ert-deftest test-duet-transfer-spec-different-host-roundtrip-is-deferred () + "Different remote hosts default to a deferred round-trip, no direct argv." + (test-duet-transfer--with-builtins + (let ((spec (duet--transfer-spec '("/ssh:hostA:/a/f") "/ssh:hostB:/b" nil))) + (should (eq :remote-roundtrip (plist-get spec :route))) + (should (null (plist-get spec :argv))) + (should (eq 'rsync-remote-to-remote (plist-get spec :exec-mode)))))) + +(ert-deftest test-duet-transfer-spec-direct-override-route () + "The direct override is recorded in the route; the spec stays deferred." + (test-duet-transfer--with-builtins + (let ((spec (duet--transfer-spec '("/ssh:hostA:/a/f") "/ssh:hostB:/b" + '(:direct-remote-to-remote t)))) + (should (eq :remote-direct (plist-get spec :route))) + (should (null (plist-get spec :argv))) + (should (eq 'rsync-remote-to-remote (plist-get spec :exec-mode)))))) + ;;; Conflict planning — pure, prompt-free (ert-deftest test-duet-plan-conflicts-no-collisions-all-copy () -- cgit v1.2.3