mirror of
https://github.com/borgbackup/borg.git
synced 2026-02-18 18:19:16 -05:00
remove handwritten bash/zsh shell completions, fixes #9178
Remove the handwritten bash and zsh shell completion scripts now that auto-generated completions via borg completion bash/zsh (powered by shtab, #9172) are tested and working. Fish completions are kept as shtab does not yet support fish. Replace string-matching tests with focused behavior tests: script size sanity, shell syntax validation (bash -n / zsh -n), and tests that invoke the custom preamble functions in bash (sortby key dedup, filescachemode mutual exclusivity, archive name and aid: prefix completion against a real repository).
This commit is contained in:
parent
a4bfc185df
commit
1c0bf36275
5 changed files with 179 additions and 1852 deletions
|
|
@ -167,10 +167,15 @@ Fixes:
|
|||
|
||||
Other changes:
|
||||
|
||||
- remove handwritten bash and zsh shell completions, #9178.
|
||||
these are now auto-generated via ``borg completion bash/zsh`` (using shtab).
|
||||
fish completions are kept until shtab gains fish support.
|
||||
- mount: warn about symlinks pointing outside of the mountpoint, #9254
|
||||
- Version: do not access private attributes, #9263
|
||||
- tests / CI:
|
||||
|
||||
- completion: focused tests for auto-generated shell completions
|
||||
(syntax validation, size sanity, borg-specific preamble behavior)
|
||||
- fix and re-enable Windows CI (some tests are skipped on Windows)
|
||||
- CI: faster with borg-dir/borg.exe, #9236
|
||||
- fix mismatch in xattr test, #9238
|
||||
|
|
|
|||
|
|
@ -1,223 +0,0 @@
|
|||
# Bash completions for Borg
|
||||
# https://www.borgbackup.org/
|
||||
# Note:
|
||||
# Listing archives works on password-protected repositories only if $BORG_PASSPHRASE is set.
|
||||
# Install:
|
||||
# Copy this file to /usr/share/bash-completion/completions/ or /etc/bash_completion.d/.
|
||||
|
||||
_borg()
|
||||
{
|
||||
compopt -o default
|
||||
COMPREPLY=()
|
||||
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
local prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
local prevprev="${COMP_WORDS[COMP_CWORD-2]}"
|
||||
local common_opts="-h --help --critical --error --warning --info -v --verbose --debug --debug-topic -p --progress --iec --log-json --lock-wait --show-version --show-rc --umask --remote-path --upload-ratelimit --upload-buffer --debug-profile --rsh -r --repo"
|
||||
local archive_filter_opts="--sort-by --first --last --oldest --newest --older --newer"
|
||||
local opts="${common_opts}"
|
||||
|
||||
# Commands
|
||||
if [[ ${COMP_CWORD} == 1 ]] ; then
|
||||
local borg_commands="analyze benchmark break-lock check compact create debug delete diff export-tar extract help import-tar info key list mount prune recreate rename repo-compress repo-create repo-delete repo-info repo-list repo-space serve tag transfer umount undelete version with-lock"
|
||||
COMPREPLY=( $(compgen -W "${borg_commands}" -- ${cur}) )
|
||||
compopt +o default
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "${prev}" in
|
||||
'key')
|
||||
COMPREPLY=( $(compgen -W "change-location change-passphrase export import" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
'benchmark')
|
||||
COMPREPLY=( $(compgen -W "cpu crud" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
'debug')
|
||||
COMPREPLY=( $(compgen -W "info dump-archive-items dump-archive dump-manifest dump-repo-objs search-repo-objs get-obj id-hash parse-obj format-obj put-obj delete-obj convert-profile" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
'help')
|
||||
COMPREPLY=( $(compgen -W "patterns placeholders compression" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
'--encryption' | '-e')
|
||||
local encryption_modes="authenticated authenticated-blake2 keyfile-aes-ocb keyfile-blake2-aes-ocb keyfile-blake2-chacha20-poly1305 keyfile-chacha20-poly1305 none repokey-aes-ocb repokey-blake2-aes-ocb repokey-blake2-chacha20-poly1305 repokey-chacha20-poly1305"
|
||||
COMPREPLY=( $(compgen -W "${encryption_modes}" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
'--files-cache')
|
||||
local files_cache_mode="ctime,size,inode mtime,size,inode ctime,size mtime,size rechunk,ctime rechunk,mtime size disabled"
|
||||
COMPREPLY=( $(compgen -W "${files_cache_mode}" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
'--compression' | '-C')
|
||||
local compression_methods="none auto lz4 zstd,1 zstd,2 zstd,3 zstd,4 zstd,5 zstd,6 zstd,7 zstd,8 zstd,9 zstd,10 zstd,11 zstd,12 zstd,13 zstd,14 zstd,15 zstd,16 zstd,17 zstd,18 zstd,19 zstd,20 zstd,21 zstd,22 zlib,1 zlib,2 zlib,3 zlib,4 zlib,5 zlib,6 zlib,7 zlib,8 zlib,9 lzma,0 lzma,1 lzma,2 lzma,3 lzma,4 lzma,5 lzma,6 lzma,7 lzma,8 lzma,9"
|
||||
COMPREPLY=( $(compgen -W "${compression_methods}" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
'--sort-by')
|
||||
local sort_keys="timestamp archive name id tags host user"
|
||||
COMPREPLY=( $(compgen -W "${sort_keys}" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
'-o')
|
||||
# FIXME This list is probably not complete, but I tried to pick only those that are relevant to borg mount -o:
|
||||
local fuse_options="ac_attr_timeout= allow_damaged_files allow_other allow_root attr_timeout= auto auto_cache auto_unmount default_permissions entry_timeout= gid= group_id= kernel_cache max_read= negative_timeout= noauto noforget remember= remount rootmode= uid= umask= user user_id= versions"
|
||||
COMPREPLY=( $(compgen -W "${fuse_options}" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
'--recompress')
|
||||
local recompress_when="if-different always never"
|
||||
COMPREPLY=( $(compgen -W "${recompress_when}" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
'--upgrader')
|
||||
local upgraders="From12To20 NoOp"
|
||||
COMPREPLY=( $(compgen -W "${upgraders}" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
case "${COMP_LINE}" in
|
||||
*' analyze '*)
|
||||
local opts="-a --match-archives ${archive_filter_opts} ${common_opts}"
|
||||
;;
|
||||
*' repo-create '*)
|
||||
local opts="--other-repo --from-borg1 -e --encryption --copy-crypt-key ${common_opts}"
|
||||
;;
|
||||
*' repo-list '*)
|
||||
local opts="--short --format --json ${common_opts} -a --match-archives ${archive_filter_opts} --deleted"
|
||||
;;
|
||||
*' repo-info '*)
|
||||
local opts="--json ${common_opts}"
|
||||
;;
|
||||
*' repo-compress '*)
|
||||
local opts="-C --compression -s --stats ${common_opts}"
|
||||
;;
|
||||
*' repo-delete '*)
|
||||
local opts="-n --dry-run --list --force --cache-only --keep-security-info ${common_opts}"
|
||||
;;
|
||||
*' repo-space '*)
|
||||
local opts="--reserve --free ${common_opts}"
|
||||
;;
|
||||
*' create '*)
|
||||
local opts="-n --dry-run -s --stats --list --filter --json --stdin-name --stdin-user --stdin-group --stdin-mode --content-from-command --paths-from-stdin --paths-from-command --paths-delimiter -e --exclude --exclude-from --pattern --patterns-from --exclude-caches --exclude-if-present --keep-exclude-tags --exclude-nodump -x --one-file-system --numeric-ids --atime --noctime --nobirthtime --noflags --noacls --noxattrs --sparse --files-cache --read-special --comment --timestamp --chunker-params -C --compression ${common_opts}"
|
||||
;;
|
||||
*' extract '*)
|
||||
local opts="--list -n --dry-run --numeric-ids --noflags --noacls --noxattrs --stdout --sparse -e --exclude --exclude-from --pattern --patterns-from --strip-components ${common_opts}"
|
||||
;;
|
||||
*' check '*)
|
||||
local opts="--repository-only --archives-only --verify-data --repair --find-lost-archives --max-duration -a --match-archives ${archive_filter_opts} ${common_opts}"
|
||||
;;
|
||||
# rename
|
||||
# no specific options
|
||||
*" list "*)
|
||||
local opts="--short --format --json-lines -e --exclude --exclude-from --pattern --patterns-from ${common_opts}"
|
||||
;;
|
||||
*' diff '*)
|
||||
local opts="--numeric-ids --same-chunker-params --sort --json-lines -e --exclude --exclude-from --pattern --patterns-from ${common_opts}"
|
||||
;;
|
||||
*' delete '*)
|
||||
local opts="-n --dry-run --list -s --stats --cache-only --force -a --match-archives ${archive_filter_opts} ${common_opts}"
|
||||
;;
|
||||
*' prune '*)
|
||||
local opts="-n --dry-run --force -s --stats --list --keep-within --keep-last --keep-secondly --keep-minutely -H --keep-hourly -d --keep-daily -w --keep-weekly -m --keep-monthly --keep-13weekly --keep-3monthly -y --keep-yearly -a --match-archives ${common_opts}"
|
||||
;;
|
||||
*' compact '*)
|
||||
local opts="-n --dry-run -s --stats ${common_opts}"
|
||||
;;
|
||||
*' info '*)
|
||||
local opts="--json -a --match-archives ${archive_filter_opts} ${common_opts}"
|
||||
;;
|
||||
*' mount '*)
|
||||
local opts="-f --foreground -o --numeric-ids -a --match-archives ${archive_filter_opts} -e --exclude --exclude-from --pattern --patterns-from --strip-components ${common_opts}"
|
||||
;;
|
||||
# umount
|
||||
# no specific options
|
||||
# key change-passphrase
|
||||
# no specific options
|
||||
*' change-location '*)
|
||||
local opts="${common_opts} keyfile repokey --keep"
|
||||
;;
|
||||
*' export '*)
|
||||
local opts="--paper --qr-html ${common_opts}"
|
||||
;;
|
||||
*' import '*)
|
||||
local opts="--paper ${common_opts}"
|
||||
;;
|
||||
*' recreate '*)
|
||||
local opts="--list --filter -n --dry-run -s --stats -e --exclude --exclude-from --pattern --patterns-from --exclude-caches --exclude-if-present --keep-exclude-tags -a --match-archives ${archive_filter_opts} --target --comment --timestamp -C --compression --chunker-params ${common_opts}"
|
||||
;;
|
||||
*' export-tar '*)
|
||||
local opts="--tar-filter --list --tar-format -e --exclude --exclude-from --pattern --patterns-from --strip-components ${common_opts}"
|
||||
;;
|
||||
*' import-tar '*)
|
||||
local opts="--tar-filter -s --stats --list --filter --json --ignore-zeros ${common_opts} --comment --timestamp --chunker-params -C --compression"
|
||||
;;
|
||||
*' transfer '*)
|
||||
local opts="-n --dry-run --other-repo --upgrader ${common_opts} -a --match-archives ${archive_filter_opts}"
|
||||
;;
|
||||
*' serve '*)
|
||||
local opts="--restrict-to-path --restrict-to-repository ${common_opts}"
|
||||
;;
|
||||
*' tag '*)
|
||||
local opts="--set --add --remove -a --match-archives ${archive_filter_opts} ${common_opts}"
|
||||
;;
|
||||
*' undelete '*)
|
||||
local opts="-n --dry-run --list -a --match-archives ${archive_filter_opts} ${common_opts}"
|
||||
;;
|
||||
# debug
|
||||
# has subcommands, handled separately
|
||||
# version
|
||||
# no specific options
|
||||
# with-lock
|
||||
# no specific options
|
||||
# break-lock
|
||||
# no specific options
|
||||
# benchmark crud
|
||||
# no specific options
|
||||
esac
|
||||
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Get the repository name if available
|
||||
# If there is a space before the "::" it means that no repository name was typed,
|
||||
# so probably $BORG_REPO was set and we can still list the archives.
|
||||
local repository_name="${COMP_LINE%%::*}"
|
||||
repository_name=${repository_name##* }
|
||||
|
||||
# Listing archives.
|
||||
# Since "::" is treated as separate word in Bash,
|
||||
# it is $cur when the cursor is right behind it
|
||||
# and $prev if the user has started to type an archive name.
|
||||
local typed_word=${cur}
|
||||
local -i list_archives=0
|
||||
if [[ ${cur} == "::" ]] ; then
|
||||
list_archives=1
|
||||
typed_word=""
|
||||
fi
|
||||
if [[ ${prev} == "::" ]] ; then
|
||||
list_archives=1
|
||||
fi
|
||||
# Second archive listing for borg diff
|
||||
if [[ ${COMP_LINE} =~ ^.*\ diff\ .*::[^\ ]+\ ${cur}$ ]] ; then
|
||||
list_archives=1
|
||||
fi
|
||||
# Additional archive listing for borg delete
|
||||
if [[ ${COMP_LINE} =~ ^.*\ delete\ .*::[^\ ]+.*${cur}$ ]] ; then
|
||||
list_archives=1
|
||||
fi
|
||||
if (( $list_archives )) ; then
|
||||
local archives=$(borg list --short "${repository_name}" 2>/dev/null)
|
||||
COMPREPLY=( $(compgen -W "${archives}" -- "${typed_word}" ) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _borg borg
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,21 +1,184 @@
|
|||
from . import cmd, generate_archiver_tests
|
||||
import functools
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
from . import cmd, generate_archiver_tests, RK_ENCRYPTION
|
||||
|
||||
pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local") # NOQA
|
||||
|
||||
|
||||
def test_bash_completion(archivers, request):
|
||||
"""Ensure the generated Bash completion includes our helper."""
|
||||
@functools.lru_cache
|
||||
def cmd_available(cmd):
|
||||
"""Check if a shell command is available."""
|
||||
try:
|
||||
subprocess.run(cmd.split(), capture_output=True, check=True)
|
||||
return True
|
||||
except (subprocess.SubprocessError, FileNotFoundError):
|
||||
return False
|
||||
|
||||
|
||||
needs_bash = pytest.mark.skipif(not cmd_available("bash --version"), reason="Bash not available")
|
||||
needs_zsh = pytest.mark.skipif(not cmd_available("zsh --version"), reason="Zsh not available")
|
||||
|
||||
|
||||
def _run_bash_completion_fn(completion_script, setup_code):
|
||||
"""Source the completion script in bash and run setup_code, return subprocess result."""
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".bash", delete=False) as f:
|
||||
f.write(completion_script)
|
||||
script_path = f.name
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["bash", "-c", f"source {script_path}\n{setup_code}"], capture_output=True, text=True, timeout=120
|
||||
)
|
||||
finally:
|
||||
os.unlink(script_path)
|
||||
return result
|
||||
|
||||
|
||||
# -- output sanity checks -----------------------------------------------------
|
||||
|
||||
|
||||
def test_bash_completion_nontrivial(archivers, request):
|
||||
"""Verify the generated Bash completion is non-trivially sized."""
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
output = cmd(archiver, "completion", "bash")
|
||||
assert "_borg_complete_archive() {" in output
|
||||
assert "_borg_complete_sortby() {" in output
|
||||
assert "_borg_complete_filescachemode() {" in output
|
||||
assert len(output) > 5000, f"Bash completion suspiciously small: {len(output)} chars"
|
||||
assert output.count("\n") > 100, f"Bash completion suspiciously few lines: {output.count(chr(10))}"
|
||||
|
||||
|
||||
def test_zsh_completion(archivers, request):
|
||||
"""Ensure the generated Zsh completion includes our helper."""
|
||||
def test_zsh_completion_nontrivial(archivers, request):
|
||||
"""Verify the generated Zsh completion is non-trivially sized."""
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
output = cmd(archiver, "completion", "zsh")
|
||||
assert "_borg_complete_archive() {" in output
|
||||
assert "_borg_complete_sortby() {" in output
|
||||
assert "_borg_complete_filescachemode() {" in output
|
||||
assert len(output) > 5000, f"Zsh completion suspiciously small: {len(output)} chars"
|
||||
assert output.count("\n") > 100, f"Zsh completion suspiciously few lines: {output.count(chr(10))}"
|
||||
|
||||
|
||||
# -- syntax validation --------------------------------------------------------
|
||||
|
||||
|
||||
def _check_shell_syntax(script_content, shell, suffix):
|
||||
"""Write script_content to a temp file and verify syntax with ``shell -n``."""
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=suffix, delete=False) as f:
|
||||
f.write(script_content)
|
||||
script_path = f.name
|
||||
try:
|
||||
result = subprocess.run([shell, "-n", script_path], capture_output=True)
|
||||
finally:
|
||||
os.unlink(script_path)
|
||||
return result
|
||||
|
||||
|
||||
@needs_bash
|
||||
def test_bash_completion_syntax(archivers, request):
|
||||
"""Verify the generated Bash completion script has valid syntax."""
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
output = cmd(archiver, "completion", "bash")
|
||||
result = _check_shell_syntax(output, "bash", ".bash")
|
||||
assert result.returncode == 0, f"Generated Bash completion has syntax errors: {result.stderr.decode()}"
|
||||
|
||||
|
||||
@needs_zsh
|
||||
def test_zsh_completion_syntax(archivers, request):
|
||||
"""Verify the generated Zsh completion script has valid syntax."""
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
output = cmd(archiver, "completion", "zsh")
|
||||
result = _check_shell_syntax(output, "zsh", ".zsh")
|
||||
assert result.returncode == 0, f"Generated Zsh completion has syntax errors: {result.stderr.decode()}"
|
||||
|
||||
|
||||
# -- borg-specific preamble function behavior (bash) --------------------------
|
||||
|
||||
|
||||
@needs_bash
|
||||
def test_bash_sortby_dedup(archivers, request):
|
||||
"""_borg_complete_sortby should not re-offer already-selected sort keys."""
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
script = cmd(archiver, "completion", "bash")
|
||||
|
||||
# Simulate: user typed "borg repo-list --sort-by timestamp,"
|
||||
# The function should offer remaining keys but NOT "timestamp" again.
|
||||
result = _run_bash_completion_fn(
|
||||
script, 'COMP_WORDS=(borg repo-list --sort-by "timestamp,")\n' "COMP_CWORD=3\n" "_borg_complete_sortby\n"
|
||||
)
|
||||
assert result.returncode == 0, f"stderr: {result.stderr}"
|
||||
lines = [line for line in result.stdout.strip().splitlines() if line.strip()]
|
||||
# "timestamp" must not appear as a standalone completion candidate
|
||||
bare_keys = [line.rsplit(",", 1)[-1] for line in lines]
|
||||
assert "timestamp" not in bare_keys, f"timestamp was re-offered: {lines}"
|
||||
# Other keys like "archive" should be offered
|
||||
assert any("archive" in line for line in lines), f"expected 'archive' in completions: {lines}"
|
||||
|
||||
|
||||
@needs_bash
|
||||
def test_bash_filescachemode_exclusivity(archivers, request):
|
||||
"""_borg_complete_filescachemode should enforce ctime/mtime and disabled mutual exclusion."""
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
script = cmd(archiver, "completion", "bash")
|
||||
|
||||
# After selecting "ctime,", mtime should not be offered
|
||||
result = _run_bash_completion_fn(
|
||||
script, 'COMP_WORDS=(borg create --files-cache "ctime,")\n' "COMP_CWORD=3\n" "_borg_complete_filescachemode\n"
|
||||
)
|
||||
assert result.returncode == 0, f"stderr: {result.stderr}"
|
||||
bare_keys = [line.rsplit(",", 1)[-1] for line in result.stdout.strip().splitlines() if line.strip()]
|
||||
assert "mtime" not in bare_keys, f"mtime offered after ctime: {bare_keys}"
|
||||
assert "disabled" not in bare_keys, f"disabled offered after ctime: {bare_keys}"
|
||||
|
||||
# After selecting "disabled,", nothing should be offered
|
||||
result2 = _run_bash_completion_fn(
|
||||
script,
|
||||
'COMP_WORDS=(borg create --files-cache "disabled,")\n' "COMP_CWORD=3\n" "_borg_complete_filescachemode\n",
|
||||
)
|
||||
assert result2.returncode == 0
|
||||
assert result2.stdout.strip() == "", f"completions offered after disabled: {result2.stdout}"
|
||||
|
||||
# After selecting "size,", disabled should not be offered
|
||||
result3 = _run_bash_completion_fn(
|
||||
script, 'COMP_WORDS=(borg create --files-cache "size,")\n' "COMP_CWORD=3\n" "_borg_complete_filescachemode\n"
|
||||
)
|
||||
assert result3.returncode == 0
|
||||
bare_keys3 = [line.rsplit(",", 1)[-1] for line in result3.stdout.strip().splitlines() if line.strip()]
|
||||
assert "disabled" not in bare_keys3, f"disabled offered after size: {bare_keys3}"
|
||||
|
||||
|
||||
@needs_bash
|
||||
def test_bash_archive_name_completion(archivers, request):
|
||||
"""_borg_complete_archive should complete archive names from a real repo."""
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
cmd(archiver, "repo-create", RK_ENCRYPTION)
|
||||
cmd(archiver, "create", "mybackup-2024", archiver.input_path)
|
||||
cmd(archiver, "create", "mybackup-2025", archiver.input_path)
|
||||
|
||||
script = cmd(archiver, "completion", "bash")
|
||||
repo = archiver.repository_path
|
||||
|
||||
result = _run_bash_completion_fn(
|
||||
script, f'COMP_WORDS=(borg delete --repo "{repo}" "mybackup")\n' f"COMP_CWORD=4\n" f"_borg_complete_archive\n"
|
||||
)
|
||||
assert result.returncode == 0, f"stderr: {result.stderr}"
|
||||
assert "mybackup-2024" in result.stdout, f"archive name missing: {result.stdout}"
|
||||
assert "mybackup-2025" in result.stdout, f"archive name missing: {result.stdout}"
|
||||
|
||||
|
||||
@needs_bash
|
||||
def test_bash_archive_aid_completion(archivers, request):
|
||||
"""_borg_complete_archive should complete aid: prefixed archive IDs."""
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
cmd(archiver, "repo-create", RK_ENCRYPTION)
|
||||
cmd(archiver, "create", "testarchive", archiver.input_path)
|
||||
|
||||
script = cmd(archiver, "completion", "bash")
|
||||
repo = archiver.repository_path
|
||||
|
||||
result = _run_bash_completion_fn(
|
||||
script, f'COMP_WORDS=(borg info --repo "{repo}" "aid:")\n' f"COMP_CWORD=4\n" f"_borg_complete_archive\n"
|
||||
)
|
||||
assert result.returncode == 0, f"stderr: {result.stderr}"
|
||||
lines = [line for line in result.stdout.strip().splitlines() if line.strip()]
|
||||
assert len(lines) >= 1, "Expected at least one archive ID completion"
|
||||
for line in lines:
|
||||
assert line.startswith("aid:"), f"Expected aid: prefix, got: {line}"
|
||||
|
|
|
|||
|
|
@ -6,22 +6,6 @@ import pytest
|
|||
SHELL_COMPLETIONS_DIR = Path(__file__).parent / ".." / ".." / ".." / "scripts" / "shell_completions"
|
||||
|
||||
|
||||
def test_bash_completion_is_valid():
|
||||
"""Test that the Bash completion file is valid Bash syntax."""
|
||||
bash_completion_file = SHELL_COMPLETIONS_DIR / "bash" / "borg"
|
||||
assert bash_completion_file.is_file()
|
||||
|
||||
# Check if Bash is available
|
||||
try:
|
||||
subprocess.run(["bash", "--version"], capture_output=True, check=True)
|
||||
except (subprocess.SubprocessError, FileNotFoundError):
|
||||
pytest.skip("Bash not available")
|
||||
|
||||
# Test whether the Bash completion file can be sourced without errors
|
||||
result = subprocess.run(["bash", "-n", str(bash_completion_file)], capture_output=True)
|
||||
assert result.returncode == 0, f"Bash completion file has syntax errors: {result.stderr.decode()}"
|
||||
|
||||
|
||||
def test_fish_completion_is_valid():
|
||||
"""Test that the Fish completion file is valid Fish syntax."""
|
||||
fish_completion_file = SHELL_COMPLETIONS_DIR / "fish" / "borg.fish"
|
||||
|
|
@ -36,19 +20,3 @@ def test_fish_completion_is_valid():
|
|||
# Test whether the Fish completion file can be sourced without errors
|
||||
result = subprocess.run(["fish", "-c", f"source {str(fish_completion_file)}"], capture_output=True)
|
||||
assert result.returncode == 0, f"Fish completion file has syntax errors: {result.stderr.decode()}"
|
||||
|
||||
|
||||
def test_zsh_completion_is_valid():
|
||||
"""Test that the Zsh completion file is valid Zsh syntax."""
|
||||
zsh_completion_file = SHELL_COMPLETIONS_DIR / "zsh" / "_borg"
|
||||
assert zsh_completion_file.is_file()
|
||||
|
||||
# Check if Zsh is available
|
||||
try:
|
||||
subprocess.run(["zsh", "--version"], capture_output=True, check=True)
|
||||
except (subprocess.SubprocessError, FileNotFoundError):
|
||||
pytest.skip("Zsh not available")
|
||||
|
||||
# Test whether the Zsh completion file can be sourced without errors
|
||||
result = subprocess.run(["zsh", "-n", str(zsh_completion_file)], capture_output=True)
|
||||
assert result.returncode == 0, f"Zsh completion file has syntax errors: {result.stderr.decode()}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue