2020 ChownCmdError ,
2121 CpError ,
2222 MvFileError ,
23+ RsyncCmdNoSourcesError ,
24+ RsyncCmdSourcesNotFoundError ,
2325 apt_install_cmd ,
2426 cat_cmd ,
2527 cd_cmd ,
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:
374377class 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
409439class 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
561693class TestRun :
0 commit comments