Skip to content

Commit 4a550b7

Browse files
authored
Add rsync_many (#1770)
1 parent a9f1b00 commit 4a550b7

5 files changed

Lines changed: 452 additions & 188 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
name = "dycw-utilities"
102102
readme = "README.md"
103103
requires-python = ">= 3.12"
104-
version = "0.174.16"
104+
version = "0.174.17"
105105

106106
[project.entry-points.pytest11]
107107
pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
@@ -135,7 +135,7 @@
135135
# bump-my-version
136136
[tool.bumpversion]
137137
allow_dirty = true
138-
current_version = "0.174.16"
138+
current_version = "0.174.17"
139139

140140
[[tool.bumpversion.files]]
141141
filename = "src/utilities/__init__.py"

src/tests/test_subprocess.py

Lines changed: 187 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
ChownCmdError,
2121
CpError,
2222
MvFileError,
23+
RsyncCmdNoSourcesError,
24+
RsyncCmdSourcesNotFoundError,
2325
apt_install_cmd,
2426
cat_cmd,
2527
cd_cmd,
@@ -43,6 +45,7 @@
4345
rm_cmd,
4446
rsync,
4547
rsync_cmd,
48+
rsync_many,
4649
run,
4750
set_hostname_cmd,
4851
ssh,
@@ -374,9 +377,12 @@ def test_main(self) -> None:
374377
class TestRsync:
375378
@skipif_ci
376379
@throttle(delta=5 * MINUTE)
377-
def test_main(self, *, ssh_user: str, ssh_hostname: str) -> None:
378-
with TemporaryFile() as src, yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp:
379-
dest = temp / src.name
380+
def test_file(self, *, ssh_user: str, ssh_hostname: str) -> None:
381+
with (
382+
TemporaryFile() as src,
383+
yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp_dest,
384+
):
385+
dest = temp_dest / src.name
380386
rsync(src, ssh_user, ssh_hostname, dest)
381387
ssh(
382388
ssh_user,
@@ -387,14 +393,38 @@ def test_main(self, *, ssh_user: str, ssh_hostname: str) -> None:
387393

388394
@skipif_ci
389395
@throttle(delta=5 * MINUTE)
390-
def test_dir(self, *, ssh_user: str, ssh_hostname: str) -> None:
396+
def test_dir_without_trailing_slash(
397+
self, *, ssh_user: str, ssh_hostname: str
398+
) -> None:
391399
with (
392400
TemporaryDirectory() as src,
393-
yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp,
401+
yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp_dest,
394402
):
395403
(src / "file.txt").touch()
396-
dest = temp / src.name
404+
name = src.name
405+
dest = temp_dest / name
397406
rsync(src, ssh_user, ssh_hostname, dest)
407+
ssh(
408+
ssh_user,
409+
ssh_hostname,
410+
*BASH_LS,
411+
input=strip_and_dedent(f"""
412+
if ! [ -d {dest} ]; then exit 1; fi
413+
if ! [ -d {dest}/{name} ]; then exit 1; fi
414+
if ! [ -f {dest}/{name}/file.txt ]; then exit 1; fi
415+
"""),
416+
)
417+
418+
@skipif_ci
419+
@throttle(delta=5 * MINUTE)
420+
def test_dir_with_trailing_slash(self, *, ssh_user: str, ssh_hostname: str) -> None:
421+
with (
422+
TemporaryDirectory() as src,
423+
yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp_dest,
424+
):
425+
(src / "file.txt").touch()
426+
dest = temp_dest / src.name
427+
rsync(f"{src}/", ssh_user, ssh_hostname, dest)
398428
ssh(
399429
ssh_user,
400430
ssh_hostname,
@@ -407,114 +437,146 @@ def test_dir(self, *, ssh_user: str, ssh_hostname: str) -> None:
407437

408438

409439
class TestRsyncCmd:
410-
def test_main(self) -> None:
411-
result = rsync_cmd("src", "user", "hostname", "dest")
412-
expected = [
440+
def test_main(self, *, tmp_path: Path) -> None:
441+
src = tmp_path / "file.txt"
442+
src.touch()
443+
result = rsync_cmd(src, "user", "hostname", "dest")
444+
expected: list[str] = [
413445
"rsync",
414446
"--checksum",
415447
"--compress",
416448
"--rsh",
417449
"ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T",
418-
"src",
450+
str(src),
419451
"user@hostname:dest",
420452
]
421453
assert result == expected
422454

423-
def test_multiple_sources(self) -> None:
424-
result = rsync_cmd(["src1", "src2"], "user", "hostname", "dest")
425-
expected = [
455+
def test_multiple_sources(self, *, tmp_path: Path) -> None:
456+
src1, src2 = [tmp_path / f"file{i}.txt" for i in [1, 2]]
457+
src1.touch()
458+
src2.touch()
459+
result = rsync_cmd([src1, src2], "user", "hostname", "dest")
460+
expected: list[str] = [
426461
"rsync",
427462
"--checksum",
428463
"--compress",
429464
"--rsh",
430465
"ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T",
431-
"src1",
432-
"src2",
466+
str(src1),
467+
str(src2),
433468
"user@hostname:dest",
434469
]
435470
assert result == expected
436471

437-
def test_archive(self) -> None:
438-
result = rsync_cmd("src", "user", "hostname", "dest", archive=True)
439-
expected = [
472+
def test_source_with_trailing_slash(self, *, tmp_path: Path) -> None:
473+
src = tmp_path / "src"
474+
src.mkdir()
475+
result = rsync_cmd(f"{src}/", "user", "hostname", "dest")
476+
expected: list[str] = [
477+
"rsync",
478+
"--checksum",
479+
"--compress",
480+
"--rsh",
481+
"ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T",
482+
f"{src}/",
483+
"user@hostname:dest",
484+
]
485+
assert result == expected
486+
487+
def test_archive(self, *, tmp_path: Path) -> None:
488+
src = tmp_path / "src"
489+
src.mkdir()
490+
result = rsync_cmd(src, "user", "hostname", "dest", archive=True)
491+
expected: list[str] = [
440492
"rsync",
441493
"--archive",
442494
"--checksum",
443495
"--compress",
444496
"--rsh",
445497
"ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T",
446-
"src",
498+
str(src),
447499
"user@hostname:dest",
448500
]
449501
assert result == expected
450502

451-
def test_chown_user(self) -> None:
452-
result = rsync_cmd("src", "user", "hostname", "dest", chown_user="user2")
453-
expected = [
503+
def test_chown_user(self, *, tmp_path: Path) -> None:
504+
src = tmp_path / "file.txt"
505+
src.touch()
506+
result = rsync_cmd(src, "user", "hostname", "dest", chown_user="user2")
507+
expected: list[str] = [
454508
"rsync",
455509
"--checksum",
456510
"--chown",
457511
"user2",
458512
"--compress",
459513
"--rsh",
460514
"ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T",
461-
"src",
515+
str(src),
462516
"user@hostname:dest",
463517
]
464518
assert result == expected
465519

466-
def test_chown_group(self) -> None:
467-
result = rsync_cmd("src", "user", "hostname", "dest", chown_group="group")
468-
expected = [
520+
def test_chown_group(self, *, tmp_path: Path) -> None:
521+
src = tmp_path / "file.txt"
522+
src.touch()
523+
result = rsync_cmd(src, "user", "hostname", "dest", chown_group="group")
524+
expected: list[str] = [
469525
"rsync",
470526
"--checksum",
471527
"--chown",
472528
":group",
473529
"--compress",
474530
"--rsh",
475531
"ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T",
476-
"src",
532+
str(src),
477533
"user@hostname:dest",
478534
]
479535
assert result == expected
480536

481-
def test_chown_user_and_group(self) -> None:
537+
def test_chown_user_and_group(self, *, tmp_path: Path) -> None:
538+
src = tmp_path / "file.txt"
539+
src.touch()
482540
result = rsync_cmd(
483-
"src", "user", "hostname", "dest", chown_user="user2", chown_group="group"
541+
src, "user", "hostname", "dest", chown_user="user2", chown_group="group"
484542
)
485-
expected = [
543+
expected: list[str] = [
486544
"rsync",
487545
"--checksum",
488546
"--chown",
489547
"user2:group",
490548
"--compress",
491549
"--rsh",
492550
"ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T",
493-
"src",
551+
str(src),
494552
"user@hostname:dest",
495553
]
496554
assert result == expected
497555

498-
def test_exclude(self) -> None:
499-
result = rsync_cmd("src", "user", "hostname", "dest", exclude="exclude")
500-
expected = [
556+
def test_exclude(self, *, tmp_path: Path) -> None:
557+
src = tmp_path / "file.txt"
558+
src.touch()
559+
result = rsync_cmd(src, "user", "hostname", "dest", exclude="exclude")
560+
expected: list[str] = [
501561
"rsync",
502562
"--checksum",
503563
"--compress",
504564
"--exclude",
505565
"exclude",
506566
"--rsh",
507567
"ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T",
508-
"src",
568+
str(src),
509569
"user@hostname:dest",
510570
]
511571
assert result == expected
512572

513-
def test_exclude_multiple(self) -> None:
573+
def test_exclude_multiple(self, *, tmp_path: Path) -> None:
574+
src = tmp_path / "file.txt"
575+
src.touch()
514576
result = rsync_cmd(
515-
"src", "user", "hostname", "dest", exclude=["exclude1", "exclude2"]
577+
src, "user", "hostname", "dest", exclude=["exclude1", "exclude2"]
516578
)
517-
expected = [
579+
expected: list[str] = [
518580
"rsync",
519581
"--checksum",
520582
"--compress",
@@ -524,38 +586,108 @@ def test_exclude_multiple(self) -> None:
524586
"exclude2",
525587
"--rsh",
526588
"ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T",
527-
"src",
589+
str(src),
528590
"user@hostname:dest",
529591
]
530592
assert result == expected
531593

532-
def test_sudo(self) -> None:
533-
result = rsync_cmd("src", "user", "hostname", "dest", sudo=True)
534-
expected = [
594+
def test_sudo(self, *, tmp_path: Path) -> None:
595+
src = tmp_path / "file.txt"
596+
src.touch()
597+
result = rsync_cmd(src, "user", "hostname", "dest", sudo=True)
598+
expected: list[str] = [
535599
"rsync",
536600
"--checksum",
537601
"--compress",
538602
"--rsh",
539603
"ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T",
540604
"--rsync-path",
541605
"sudo rsync",
542-
"src",
606+
str(src),
543607
"user@hostname:dest",
544608
]
545609
assert result == expected
546610

547-
def test_parent(self) -> None:
548-
result = rsync_cmd("src", "user", "hostname", "~/dest", parent=True)
549-
expected = [
550-
"rsync",
551-
"--checksum",
552-
"--compress",
553-
"--rsh",
554-
"ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T",
555-
"src",
556-
"user@hostname:~",
557-
]
558-
assert result == expected
611+
def test_error_no_sources(self) -> None:
612+
with raises(
613+
RsyncCmdNoSourcesError,
614+
match=r"No sources selected to send to user@hostname:dest",
615+
):
616+
_ = rsync_cmd([], "user", "hostname", "dest")
617+
618+
def test_error_sources_not_found(self, *, tmp_path: Path) -> None:
619+
src = tmp_path / "file.txt"
620+
with raises(
621+
RsyncCmdSourcesNotFoundError,
622+
match=r"Sources selected to send to user@hostname:dest but not found: '.*/file\.txt'",
623+
):
624+
_ = rsync_cmd(src, "user", "hostname", "dest")
625+
626+
627+
class TestRsyncMany:
628+
@skipif_ci
629+
@throttle(delta=5 * MINUTE)
630+
def test_single_file(self, *, ssh_user: str, ssh_hostname: str) -> None:
631+
with (
632+
TemporaryDirectory() as temp_src,
633+
yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp_dest,
634+
):
635+
src = temp_src / "file.txt"
636+
src.touch()
637+
dest = temp_dest / src.name
638+
rsync_many(ssh_user, ssh_hostname, (src, dest))
639+
ssh(
640+
ssh_user,
641+
ssh_hostname,
642+
*BASH_LS,
643+
input=f"if ! [ -f {dest} ]; then exit 1; fi",
644+
)
645+
646+
@skipif_ci
647+
@throttle(delta=5 * MINUTE)
648+
def test_multiple_files(self, *, ssh_user: str, ssh_hostname: str) -> None:
649+
with (
650+
TemporaryDirectory() as temp_src,
651+
yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp_dest,
652+
):
653+
src1, src2 = [temp_src / f"file{i}.txt" for i in [1, 2]]
654+
src1.touch()
655+
src2.touch()
656+
dest1, dest2 = [temp_dest / src.name for src in [src1, src2]]
657+
rsync_many(ssh_user, ssh_hostname, (src1, dest1), (src2, dest2))
658+
ssh(
659+
ssh_user,
660+
ssh_hostname,
661+
*BASH_LS,
662+
input=strip_and_dedent(f"""
663+
if ! [ -f {dest1} ]; then exit 1; fi
664+
if ! [ -f {dest2} ]; then exit 1; fi
665+
"""),
666+
)
667+
668+
@skipif_ci
669+
@throttle(delta=5 * MINUTE)
670+
def test_single_directory(self, *, ssh_user: str, ssh_hostname: str) -> None:
671+
with (
672+
TemporaryDirectory() as temp_src,
673+
yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp_dest,
674+
):
675+
src = temp_src / "dir"
676+
src.mkdir()
677+
(src / "file.txt").touch()
678+
dest = temp_dest / src.name
679+
rsync_many(ssh_user, ssh_hostname, (src, dest))
680+
ssh(
681+
ssh_user,
682+
ssh_hostname,
683+
*BASH_LS,
684+
input=(
685+
f"""
686+
if ! [ -d {dest} ]; then exit 1; fi
687+
if ! [ -f {dest}/file.txt ]; then exit 1; fi
688+
"""
689+
),
690+
)
559691

560692

561693
class TestRun:

0 commit comments

Comments
 (0)