From c11d1d34301099ad95e23bc6c278429033047be2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 26 Jul 2023 16:24:31 +0530 Subject: [PATCH] Add more integration testing for the transfer kitten --- kittens/transfer/main.py | 4 +- kittens/transfer/receive.go | 3 + kittens/transfer/send.go | 18 ++--- kitty/file_transmission.py | 4 +- kitty_tests/file_transmission.py | 123 ++++++++++++++++++++++--------- 5 files changed, 107 insertions(+), 45 deletions(-) diff --git a/kittens/transfer/main.py b/kittens/transfer/main.py index 857102291..5e1e946b6 100644 --- a/kittens/transfer/main.py +++ b/kittens/transfer/main.py @@ -48,7 +48,9 @@ This will put :code:`dir1` and all its contents into :code:`/path/to/dir/` on the local computer. Note that when copying multiple files or directories, the destination -must be an existing directory on the receiving computer. +must be an existing directory on the receiving computer. Relative file +paths are resolved with respect to the current directory on the computer +running the kitten and the home directory on the other computer. ''' diff --git a/kittens/transfer/receive.go b/kittens/transfer/receive.go index a58faaf65..8599bea92 100644 --- a/kittens/transfer/receive.go +++ b/kittens/transfer/receive.go @@ -26,6 +26,7 @@ import ( "kitty/tools/utils/humanize" "kitty/tools/wcswidth" + "golang.org/x/exp/slices" "golang.org/x/sys/unix" ) @@ -683,6 +684,8 @@ func files_for_receive(opts *Options, dest string, files []*remote_file, remote_ } spec_paths := make([]string, len(specs)) for i := range specs { + // use the shortest path as the path for the spec + slices.SortStableFunc(spec_map[i], func(a, b *remote_file) bool { return len(a.remote_path) < len(b.remote_path) }) spec_paths[i] = spec_map[i][0].remote_path } if opts.Mode == "mirror" { diff --git a/kittens/transfer/send.go b/kittens/transfer/send.go index 4ee11cd74..afe6dba85 100644 --- a/kittens/transfer/send.go +++ b/kittens/transfer/send.go @@ -177,14 +177,14 @@ func process(opts *Options, paths []string, remote_base string, counter *int) (a func process_mirrored_files(opts *Options, args []string) (ans []*File, err error) { paths := utils.Map(func(x string) string { return abspath(expand_home(x)) }, args) - common_path := utils.Commonpath(paths...) - home := strings.TrimRight(home_path(), string(filepath.Separator)) - if common_path != "" && strings.HasPrefix(common_path, home+string(filepath.Separator)) { - paths = utils.Map(func(x string) string { - r, _ := filepath.Rel(home, x) + home := strings.TrimRight(home_path(), string(filepath.Separator)) + string(filepath.Separator) + paths = utils.Map(func(path string) string { + if strings.HasPrefix(path, home) { + r, _ := filepath.Rel(home, path) return filepath.Join("~", r) - }, paths) - } + } + return path + }, paths) counter := 0 return process(opts, paths, "", &counter) } @@ -232,7 +232,7 @@ func files_for_send(opts *Options, args []string) (files []*File, err error) { // detect symlinks to other transferred files for i, f := range files { if f.file_type == FileType_symlink { - link_dest, err := os.Readlink(f.local_path) + link_dest, err := os.Readlink(f.expanded_local_path) if err != nil { remove = append(remove, i) continue @@ -241,7 +241,7 @@ func files_for_send(opts *Options, args []string) (files []*File, err error) { is_abs := filepath.IsAbs(link_dest) q := link_dest if !is_abs { - q = filepath.Join(filepath.Dir(f.local_path), link_dest) + q = filepath.Join(filepath.Dir(f.expanded_local_path), link_dest) } st, err := os.Stat(q) if err == nil { diff --git a/kitty/file_transmission.py b/kitty/file_transmission.py index 76822a66e..98b6294df 100644 --- a/kitty/file_transmission.py +++ b/kitty/file_transmission.py @@ -447,9 +447,9 @@ class DestFile: def __init__(self, ftc: FileTransmissionCommand) -> None: self.name = ftc.name if not os.path.isabs(self.name): - self.name = os.path.expanduser(self.name) + self.name = expand_home(self.name) if not os.path.isabs(self.name): - self.name = os.path.join(tempfile.gettempdir(), self.name) + self.name = abspath(self.name, use_home=True) try: self.existing_stat: Optional[os.stat_result] = os.stat(self.name, follow_symlinks=False) except OSError: diff --git a/kitty_tests/file_transmission.py b/kitty_tests/file_transmission.py index 61f4f09c6..8fcb3e76d 100644 --- a/kitty_tests/file_transmission.py +++ b/kitty_tests/file_transmission.py @@ -181,6 +181,7 @@ class TestFileTransmission(BaseTest): def setUp(self): self.direction_receive = False + self.kitty_home = self.kitty_cwd = self.kitten_home = self.kitten_cwd = '' super().setUp() self.tdir = os.path.realpath(tempfile.mkdtemp()) self.responses = [] @@ -196,8 +197,12 @@ class TestFileTransmission(BaseTest): super().tearDown() def clean_tdir(self): - shutil.rmtree(self.tdir) - self.tdir = os.path.realpath(tempfile.mkdtemp()) + for x in os.listdir(self.tdir): + x = os.path.join(self.tdir, x) + if os.path.isdir(x): + shutil.rmtree(x) + else: + os.remove(x) self.responses = [] def cr(self, a, b): @@ -324,22 +329,18 @@ class TestFileTransmission(BaseTest): self.assertEqual(h128.hexdigest(), '8d6b60383dfa90c21be79eecd1b1353d') @contextmanager - def run_kitten(self, cmd, home_dir='', allow=True): - cwd = os.path.realpath(tempfile.mkdtemp(suffix='-cwd', dir=self.tdir)) + def run_kitten(self, cmd, home_dir='', allow=True, cwd=''): + cwd = cwd or self.kitten_cwd or self.tdir cmd = [kitten_exe(), 'transfer'] + (['--direction=receive'] if self.direction_receive else []) + cmd env = {'PWD': cwd} - if home_dir: - env['HOME'] = home_dir - try: + env['HOME'] = home_dir or self.kitten_home or self.tdir + with set_paths(home=self.kitty_home, cwd=self.kitty_cwd): pty = TransferPTY(cmd, cwd=cwd, allow=allow, env=env) i = 10 while i > 0 and not pty.screen_contents().strip(): pty.process_input_from_child() i -= 1 yield pty - finally: - if os.path.exists(cwd): - shutil.rmtree(cwd) def basic_transfer_tests(self): src = os.path.join(self.tdir, 'src') @@ -445,19 +446,62 @@ class TestFileTransmission(BaseTest): multiple_files('--transmit-deltas') multiple_files('--transmit-deltas') + def setup_dirs(self): + self.clean_tdir() + self.kitty_home = os.path.join(self.tdir, 'kitty-home') + self.kitty_cwd = os.path.join(self.tdir, 'kitty-cwd') + self.kitten_home = os.path.join(self.tdir, 'kitten-home') + self.kitten_cwd = os.path.join(self.tdir, 'kitten-cwd') + tuple(map(os.mkdir, (self.kitty_home, self.kitty_cwd, self.kitten_home, self.kitten_cwd))) + + def create_src(self, base): + src = os.path.join(base, 'src') + with open(src, 'wb') as s: + s.write(self.src_data) + return src + + def mirror_test(self, src, dest, prefix=''): + self.create_src(src) + os.symlink('/', os.path.join(src, 'sym')) + os.mkdir(os.path.join(src, 'sub')) + os.link(os.path.join(src, 'src'), os.path.join(src, 'sub', 'hardlink')) + with self.run_kitten(['--mode=mirror', f'{prefix}src', f'{prefix}sym', f'{prefix}sub']) as pty: + pty.wait_till_child_exits(require_exit_code=0) + os.remove(os.path.join(dest, 'src')) + os.remove(os.path.join(dest, 'sym')) + shutil.rmtree(os.path.join(dest, 'sub')) + def test_transfer_receive(self): self.direction_receive = True self.basic_transfer_tests() - src = os.path.join(self.tdir, 'src') - with open(src, 'wb') as s: - s.write(self.src_data) - # home dir expansion - fname = 'tstest-file' - home = os.path.dirname(src) - with set_paths(home=home), self.run_kitten(['~/'+os.path.basename(src), f'~/{fname}'], home_dir=home) as pty: - pty.wait_till_child_exits(require_exit_code=0) - os.remove(os.path.join(home, fname)) + self.setup_dirs() + self.create_src(self.kitty_home) + + # dir expansion with single transfer + with self.run_kitten(['~/src', '~/src']) as pty: + pty.wait_till_child_exits(require_exit_code=0) + os.remove(os.path.join(self.kitten_home, 'src')) + + with self.run_kitten(['src', 'src']) as pty: + pty.wait_till_child_exits(require_exit_code=0) + os.remove(os.path.join(self.kitten_cwd, 'src')) + + # dir expansion with multiple transfers + os.symlink('/', os.path.join(self.kitty_home, 'sym')) + with self.run_kitten(['~/src', '~/sym', '~']) as pty: + pty.wait_till_child_exits(require_exit_code=0) + os.remove(os.path.join(self.kitten_home, 'src')) + os.remove(os.path.join(self.kitten_home, 'sym')) + + with self.run_kitten(['src', 'sym', '.']) as pty: + pty.wait_till_child_exits(require_exit_code=0) + os.remove(os.path.join(self.kitten_cwd, 'src')) + os.remove(os.path.join(self.kitten_cwd, 'sym')) + + # mirroring + self.setup_dirs() + self.mirror_test(self.kitty_home, self.kitten_home) def test_transfer_send(self): self.basic_transfer_tests() @@ -465,19 +509,32 @@ class TestFileTransmission(BaseTest): with open(src, 'wb') as s: s.write(self.src_data) - # home dir expansion - fname = 'tstest-file' - home = os.path.dirname(src) - with set_paths(home=home), self.run_kitten(['~/'+os.path.basename(src), '~/'+fname], home_dir=home) as pty: - pty.wait_till_child_exits(require_exit_code=0) - os.remove(os.path.expanduser('~/'+fname)) + self.setup_dirs() + self.create_src(self.kitten_home) - # mirror mode - src_home = os.path.join(self.tdir, 'misrc') - os.mkdir(src_home) - open(os.path.join(src_home, fname), 'w').close() - with self.run_kitten(['--mode=mirror', '~/'+fname], home_dir=src_home) as pty: + # dir expansion with single transfer + with self.run_kitten(['~/src', '~/src']) as pty: pty.wait_till_child_exits(require_exit_code=0) - with open(os.path.expanduser('~/'+fname)) as f: - self.assertEqual('', f.read()) - os.remove(f.name) + os.remove(os.path.join(self.kitty_home, 'src')) + + self.create_src(self.kitten_cwd) + with self.run_kitten(['src', 'src']) as pty: + pty.wait_till_child_exits(require_exit_code=0) + os.remove(os.path.join(self.kitty_home, 'src')) + + # dir expansion with multiple transfers + os.symlink('/', os.path.join(self.kitten_home, 'sym')) + with self.run_kitten(['~/src', '~/sym', '~']) as pty: + pty.wait_till_child_exits(require_exit_code=0) + os.remove(os.path.join(self.kitty_home, 'src')) + os.remove(os.path.join(self.kitty_home, 'sym')) + + os.symlink('/', os.path.join(self.kitten_cwd, 'sym')) + with self.run_kitten(['src', 'sym', '.']) as pty: + pty.wait_till_child_exits(require_exit_code=0) + os.remove(os.path.join(self.kitty_home, 'src')) + os.remove(os.path.join(self.kitty_home, 'sym')) + + # mirroring + self.setup_dirs() + self.mirror_test(self.kitten_home, self.kitty_home, prefix='~/')