/
githubmirror
/
bash-completion
Обзор
Документация
Войти
/
githubmirror
/
bash-completion
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
test/t/test_scp.py
317 строк
11 KB
Yedaya Katsman
fix(scp): use less remote filename escaping where supported
02 авг 2026, 10:55
02 авг 2026, 10:55
d6ad3ce
Код
Авторство
О чём код?
import os import re import sys from itertools import chain import pytest from conftest import ( assert_bash_exec, assert_complete, bash_env_saved, get_testdir, prepare_fixture_dir, ) LIVE_HOST = os.environ.get( "BASH_COMPLETION_TEST_LIVE_SSH_HOST", default="bash_completion" ) class TestScp: @pytest.mark.complete("scp -F config ", cwd="scp") def test_basic(self, hosts_from_hostfile, completion): expected = sorted( chain( ( "%s:" % x for x in chain( hosts_from_hostfile, # From fixtures/scp/config "gee hut".split(), # From fixtures/scp/known_hosts "blah doo ike".split(), ) ), # Local filenames ["config", "known_hosts", r"spaced\ \ conf"], ) ) assert completion == expected @pytest.mark.complete("scp -F 'spaced conf' ", cwd="scp") def test_basic_spaced_conf(self, hosts_from_hostfile, completion): expected = sorted( chain( ( "%s:" % x for x in chain( hosts_from_hostfile, # From "fixtures/scp/spaced conf" "gee jar".split(), # From fixtures/scp/known_hosts "blah doo ike".split(), ) ), # Local filenames ["config", "known_hosts", r"spaced\ \ conf"], ) ) assert completion == expected @pytest.mark.complete("scp -F") def test_capital_f_without_space(self, completion): assert completion assert not any( "option requires an argument -- F" in x for x in completion ) @pytest.mark.complete("scp -Fconf", cwd="scp") def test_capital_f_without_space_2(self, completion): assert completion == "ig" @pytest.mark.complete("scp -Fempty", cwd="shared") def test_capital_f_without_space_3(self, completion): assert completion == "_dir/" @pytest.fixture(scope="class") def live_pwd(self, bash): try: return assert_bash_exec( bash, "ssh -o 'Batchmode yes' -o 'ConnectTimeout 1' " "%s pwd 2>/dev/null" % LIVE_HOST, want_output=True, ).strip() except AssertionError: pytest.skip("Live host %s not available" % LIVE_HOST) @pytest.mark.complete("scp %s:" % LIVE_HOST, sleep_after_tab=2) def test_live(self, live_pwd, completion): """ To support this test, configure a HostName entry for LIVE_HOST in ssh's configs, e.g. ~/.ssh/config or /etc/ssh/ssh_config. Connection to it must open sufficiently quickly for the ConnectTimeout and sleep_after_tab settings. """ assert completion == f"{live_pwd}/" @pytest.mark.complete("scp -o Foo=") def test_option_arg(self, completion): assert not completion # and no errors either @pytest.mark.complete( "scp hostname-not-expected-to-exist-in-known-hosts:", shopt=dict(nullglob=True), ) def test_remote_path_with_nullglob(self, completion): assert not completion @pytest.mark.complete( "scp hostname-not-expected-to-exist-in-known-hosts:", shopt=dict(failglob=True), ) def test_remote_path_with_failglob(self, completion): assert not completion @pytest.fixture(scope="class") def needs_extra_escaping(self, bash): usage = assert_bash_exec( bash, "scp --usage 2>&1 || :", want_output=True ) expr = re.compile(r"scp \[-[^[]+O") if expr.search(usage): return False return True def test_explicit_legacy_proto(self, bash): assert_bash_exec( bash, r"ssh() { printf '%s\n' 'abc def.txt' 'abc\ def.txt'; }" ) abc_completion = assert_complete(bash, "scp -O remote_host:abc\\") assert abc_completion == sorted( [r"abc\\\ def.txt", r"abc\\\\\\\ def.txt"] ) assert_bash_exec(bash, r"ssh() { printf '%s\n' 'ends_with_slash\'; }") slash_end_completion = assert_complete( bash, "scp -O remote_host:ends_w" ) assert slash_end_completion.output == r"ith_slash\\\\ " assert_bash_exec(bash, "unset -f ssh") def test_remote_path_with_spaces(self, bash, needs_extra_escaping): assert_bash_exec(bash, "ssh() { echo 'spaces in filename.txt'; }") completion = assert_complete(bash, "scp remote_host:spaces") assert_bash_exec(bash, "unset -f ssh") if needs_extra_escaping: assert completion == r"\\\ in\\\ filename.txt" else: assert completion == r"\ in\ filename.txt" def test_remote_path_with_backslash(self, bash, needs_extra_escaping): assert_bash_exec( bash, r"ssh() { printf '%s\n' 'abc def.txt' 'abc\ def.txt'; }" ) completion = assert_complete(bash, "scp remote_host:abc\\") assert_bash_exec(bash, "unset -f ssh") if needs_extra_escaping: assert completion == sorted( [r"abc\\\ def.txt", r"abc\\\\\\\ def.txt"] ) else: assert completion == sorted([r"abc\ def.txt", r"abc\\\ def.txt"]) def test_remote_path_with_backslash_2(self, bash, needs_extra_escaping): assert_bash_exec( bash, r"ssh() { [[ $1 == abc ]]; printf '%s\n' 'abc OK'; }" ) if needs_extra_escaping: completion = assert_complete(bash, r"scp remote_host:abc\\\ ") else: completion = assert_complete(bash, r"scp remote_host:abc\ ") assert_bash_exec(bash, "unset -f ssh") assert completion == "OK" def test_xfunc_remote_files(self, live_pwd, bash): def prefix_paths(prefix, paths): return [f"{prefix}{path}" for path in paths] with bash_env_saved(bash) as bash_env: bash_env.save_variable("COMPREPLY") bash_env.write_variable( "cur", f"{LIVE_HOST}:{get_testdir()}/fixtures/shared/default/" ) completions_regular_escape = ( assert_bash_exec( bash, r'_comp_compgen -x scp remote_files; printf "%s\n" "${COMPREPLY[@]}"', want_output=True, ) .strip() .splitlines() ) completions_less_escape = ( assert_bash_exec( bash, r'_comp_compgen -x scp remote_files -l; printf "%s\n" "${COMPREPLY[@]}"', want_output=True, ) .strip() .splitlines() ) assert completions_regular_escape == prefix_paths( f"{get_testdir()}/fixtures/shared/default/", [ "bar ", r"bar\\\ bar.d/", "foo ", "foo.d/", ], ) assert completions_less_escape == prefix_paths( f"{get_testdir()}/fixtures/shared/default/", [ "bar ", r"bar\ bar.d/", "foo ", "foo.d/", ], ) @pytest.fixture def tmpdir_backslash(self, bash, tmp_path_factory): if sys.platform.startswith("win"): pytest.skip("Filenames not allowed on Windows") tmpdir = prepare_fixture_dir( tmp_path_factory, files=["local_path-file\\"], dirs=[] ) return tmpdir def test_local_path_ending_with_backslash(self, bash, tmpdir_backslash): completion = assert_complete( bash, "scp local_path-", cwd=tmpdir_backslash ) assert completion.output == r"file\\ " def test_remote_path_ending_with_backslash( self, bash, needs_extra_escaping ): assert_bash_exec(bash, "ssh() { echo 'hypothetical\\'; }") completion = assert_complete(bash, "scp remote_host:hypo") assert_bash_exec(bash, "unset -f ssh") if needs_extra_escaping: assert completion.output == r"thetical\\\\ " else: assert completion.output == r"thetical\\ " @pytest.fixture def tmpdir_mkfifo(self, bash, tmp_path_factory): # We prepare two files: 1) a named pipe and 2) a regular file ending # with the same name but an extra special character "|". tmpdir = prepare_fixture_dir( tmp_path_factory, files=["local_path_2-pipe|"], dirs=[], ) # If the system allows creating a named pipe, we create it in a # temporary directory and returns the path. We cannot check the # availability of the named pipe simply by the existence of the command # "mkfifo" Even if the command "mkfifo" exists in the system, the # current operating system or the filesystem may not allow creating a # named pipe. try: assert_bash_exec(bash, "mkfifo '%s/local_path_1-pipe'" % tmpdir) except Exception: pytest.skip( "The present system does not allow creating a named pipe through 'mkfifo'." ) return tmpdir def test_local_path_mark_1(self, bash, tmpdir_mkfifo): completion = assert_complete( bash, "scp local_path_1-", cwd=tmpdir_mkfifo ) assert completion == "pipe" # FIXME: This test currently fails. # def test_local_path_mark_2(self, bash, tmpdir_mkfifo): # completion = assert_complete( # bash, "scp local_path_2-", cwd=tmpdir_mkfifo # ) # assert completion == "pipe\\|" @pytest.mark.complete("scp spa", cwd="scp") def test_local_path_with_spaces_1(self, completion): assert completion == r"ced\ \ conf" @pytest.mark.complete(r"scp spaced\ ", cwd="scp") def test_local_path_with_spaces_2(self, completion): assert completion == r"\ conf" @pytest.fixture def tmpdir_backslash_2(self, bash, tmp_path_factory): if sys.platform.startswith("win"): pytest.skip("Filenames not allowed on Windows") tmpdir = prepare_fixture_dir( tmp_path_factory, files=["backslash-a b.txt", r"backslash-a\ b.txt"], dirs=[], ) return tmpdir @pytest.mark.complete def test_local_path_backslash(self, bash, tmpdir_backslash_2): completion = assert_complete( bash, "scp backslash-a\\", cwd=tmpdir_backslash_2 ) assert completion == sorted( [r"backslash-a\ b.txt", r"backslash-a\\\ b.txt"] )