Introduced new test cases for CLI commands to cover verbose and simple listing functionalities, exception handling, and edge cases. This includes tests for missing files, decompression errors, keyboard interrupts, and advanced scenarios to ensure robust behavior.
2291 lines
83 KiB
Python
2291 lines
83 KiB
Python
"""Comprehensive tests for tzst CLI interface.
|
|
|
|
This file consolidates all CLI-related tests to eliminate duplication
|
|
and provide a single source of truth for CLI testing.
|
|
"""
|
|
|
|
import argparse
|
|
|
|
import pytest
|
|
|
|
from tzst.cli import (
|
|
create_parser,
|
|
format_size,
|
|
main,
|
|
print_banner,
|
|
validate_compression_level,
|
|
)
|
|
|
|
|
|
class TestUtilityFunctions:
|
|
"""Test CLI utility functions."""
|
|
|
|
def test_format_size_bytes(self):
|
|
"""Test format_size with byte values."""
|
|
assert format_size(0) == " 0.0 B"
|
|
assert format_size(1) == " 1.0 B"
|
|
assert format_size(999) == " 999.0 B"
|
|
|
|
def test_format_size_kilobytes(self):
|
|
"""Test format_size with kilobyte values."""
|
|
assert format_size(1024) == " 1.0 KB"
|
|
assert format_size(1536) == " 1.5 KB"
|
|
assert format_size(2048) == " 2.0 KB"
|
|
|
|
def test_format_size_megabytes(self):
|
|
"""Test format_size with megabyte values."""
|
|
assert format_size(1024 * 1024) == " 1.0 MB"
|
|
mb_1_5 = int(1.5 * 1024 * 1024)
|
|
assert format_size(mb_1_5) == " 1.5 MB"
|
|
|
|
def test_format_size_gigabytes(self):
|
|
"""Test format_size with gigabyte values."""
|
|
assert format_size(1024 * 1024 * 1024) == " 1.0 GB"
|
|
gb_2_5 = int(2.5 * 1024 * 1024 * 1024)
|
|
assert format_size(gb_2_5) == " 2.5 GB"
|
|
|
|
def test_format_size_terabytes(self):
|
|
"""Test format_size with terabyte values."""
|
|
tb_size = 1024 * 1024 * 1024 * 1024
|
|
assert format_size(tb_size) == " 1.0 TB"
|
|
|
|
def test_format_size_petabytes(self):
|
|
"""Test format_size with very large values (petabytes)."""
|
|
huge_size = 1024 * 1024 * 1024 * 1024 * 1024 * 2
|
|
result = format_size(huge_size)
|
|
assert result.endswith(" PB")
|
|
assert "2.0" in result
|
|
|
|
def test_print_banner_output(self, capsys):
|
|
"""Test print_banner function output."""
|
|
print_banner()
|
|
captured = capsys.readouterr()
|
|
|
|
# Should print empty line, version line, empty line
|
|
lines = captured.out.split("\n")
|
|
assert len(lines) >= 3
|
|
assert lines[0] == "" # Empty line
|
|
assert "tzst" in lines[1]
|
|
assert "Copyright" in lines[1]
|
|
assert lines[2] == "" # Empty line
|
|
|
|
def test_validate_compression_level_valid(self):
|
|
"""Test validate_compression_level with valid levels."""
|
|
assert validate_compression_level("1") == 1
|
|
assert validate_compression_level("11") == 11
|
|
assert validate_compression_level("22") == 22
|
|
|
|
def test_validate_compression_level_invalid_range(self):
|
|
"""Test validate_compression_level with out-of-range values."""
|
|
with pytest.raises(
|
|
argparse.ArgumentTypeError, match="Invalid compression level: 0"
|
|
):
|
|
validate_compression_level("0")
|
|
|
|
with pytest.raises(
|
|
argparse.ArgumentTypeError, match="Invalid compression level: 23"
|
|
):
|
|
validate_compression_level("23")
|
|
|
|
def test_validate_compression_level_invalid_format(self):
|
|
"""Test validate_compression_level with non-numeric values."""
|
|
with pytest.raises(
|
|
argparse.ArgumentTypeError, match="Invalid compression level: 'abc'"
|
|
):
|
|
validate_compression_level("abc")
|
|
|
|
with pytest.raises(
|
|
argparse.ArgumentTypeError, match="Invalid compression level: '1.5'"
|
|
):
|
|
validate_compression_level("1.5")
|
|
|
|
|
|
class TestCLIParser:
|
|
"""Test CLI argument parsing."""
|
|
|
|
def test_parser_creation(self):
|
|
"""Test that parser can be created."""
|
|
parser = create_parser()
|
|
assert parser is not None
|
|
|
|
def test_add_command_parsing(self):
|
|
"""Test parsing of add command."""
|
|
parser = create_parser()
|
|
args = parser.parse_args(["a", "test.tzst", "file1.txt", "file2.txt"])
|
|
|
|
assert args.command == "a"
|
|
assert args.archive == "test.tzst"
|
|
assert args.files == ["file1.txt", "file2.txt"]
|
|
assert args.compression_level == 3 # default
|
|
|
|
def test_extract_command_parsing(self):
|
|
"""Test parsing of extract command."""
|
|
parser = create_parser()
|
|
args = parser.parse_args(["x", "test.tzst", "-o", "output"])
|
|
|
|
assert args.command == "x"
|
|
assert args.archive == "test.tzst"
|
|
assert args.output == "output"
|
|
|
|
def test_list_command_parsing(self):
|
|
"""Test parsing of list command."""
|
|
parser = create_parser()
|
|
args = parser.parse_args(["l", "test.tzst", "-v"])
|
|
|
|
assert args.command == "l"
|
|
assert args.archive == "test.tzst"
|
|
assert args.verbose is True
|
|
|
|
def test_test_command_parsing(self):
|
|
"""Test parsing of test command."""
|
|
parser = create_parser()
|
|
args = parser.parse_args(["t", "test.tzst"])
|
|
|
|
assert args.command == "t"
|
|
assert args.archive == "test.tzst"
|
|
|
|
|
|
class TestCLICommands:
|
|
"""Test CLI command execution."""
|
|
|
|
def test_add_command(self, sample_files, temp_dir):
|
|
"""Test add command functionality."""
|
|
archive_path = temp_dir / "test.tzst"
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
|
|
# Run add command
|
|
result = main(["a", str(archive_path), *file_paths])
|
|
|
|
assert result == 0
|
|
assert archive_path.exists()
|
|
|
|
def test_list_command(self, sample_files, temp_dir):
|
|
"""Test list command functionality."""
|
|
archive_path = temp_dir / "test.tzst"
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
|
|
# Create archive first
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Run list command
|
|
result = main(["l", str(archive_path)])
|
|
|
|
assert result == 0
|
|
|
|
def test_extract_command(self, sample_files, temp_dir):
|
|
"""Test extract command functionality."""
|
|
archive_path = temp_dir / "test.tzst"
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
extract_dir = temp_dir / "extracted"
|
|
|
|
# Create archive first
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Run extract command
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir)])
|
|
|
|
assert result == 0
|
|
assert extract_dir.exists()
|
|
|
|
def test_test_command(self, sample_files, temp_dir):
|
|
"""Test test command functionality."""
|
|
archive_path = temp_dir / "test.tzst"
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
|
|
# Create archive first
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Run test command
|
|
result = main(["t", str(archive_path)])
|
|
|
|
assert result == 0
|
|
|
|
|
|
class TestCLIErrorHandling:
|
|
"""Test CLI error handling."""
|
|
|
|
def test_missing_archive(self, temp_dir):
|
|
"""Test handling of missing archive file."""
|
|
fake_archive = temp_dir / "fake.tzst"
|
|
|
|
result = main(["l", str(fake_archive)])
|
|
assert result == 1
|
|
|
|
def test_missing_files_to_add(self, temp_dir):
|
|
"""Test handling of missing files to add."""
|
|
archive_path = temp_dir / "test.tzst"
|
|
fake_file = temp_dir / "fake.txt"
|
|
|
|
result = main(["a", str(archive_path), str(fake_file)])
|
|
assert result == 1
|
|
|
|
def test_no_command(self):
|
|
"""Test handling of no command provided."""
|
|
result = main([])
|
|
assert result == 1
|
|
|
|
def test_keyboard_interrupt_handling(self, temp_dir):
|
|
"""Test that KeyboardInterrupt is handled properly."""
|
|
# This test is more conceptual since we can't easily simulate KeyboardInterrupt
|
|
# in a unit test, but we can verify the error handling structure exists
|
|
from tzst.cli import cmd_add
|
|
|
|
# Create a mock args object
|
|
class MockArgs:
|
|
def __init__(self):
|
|
self.archive = str(temp_dir / "interrupt_test.tzst")
|
|
self.files = ["non_existent_file.txt"]
|
|
self.compression_level = 3
|
|
|
|
# Test that the function handles FileNotFoundError properly
|
|
result = cmd_add(MockArgs())
|
|
assert result == 1 # Should return error code for missing files
|
|
|
|
|
|
class TestCLIAliases:
|
|
"""Test CLI command aliases."""
|
|
|
|
def test_add_aliases(self, sample_files, temp_dir):
|
|
"""Test add command aliases."""
|
|
archive_path = temp_dir / "test.tzst"
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
|
|
# Test 'add' alias
|
|
result = main(["add", str(archive_path), *file_paths])
|
|
assert result == 0
|
|
|
|
# Test 'create' alias
|
|
archive_path2 = temp_dir / "test2.tzst"
|
|
result = main(["create", str(archive_path2), *file_paths])
|
|
assert result == 0
|
|
|
|
def test_extract_aliases(self, sample_files, temp_dir):
|
|
"""Test extract command aliases."""
|
|
archive_path = temp_dir / "test.tzst"
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
extract_dir = temp_dir / "extracted"
|
|
|
|
# Create archive first
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Test 'extract' alias
|
|
result = main(["extract", str(archive_path), "-o", str(extract_dir)])
|
|
assert result == 0
|
|
|
|
def test_list_aliases(self, sample_files, temp_dir):
|
|
"""Test list command aliases."""
|
|
archive_path = temp_dir / "test.tzst"
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
|
|
# Create archive first
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Test 'list' alias
|
|
result = main(["list", str(archive_path)])
|
|
assert result == 0
|
|
|
|
|
|
class TestCLIHelp:
|
|
"""Test CLI help and documentation."""
|
|
|
|
def test_help_command(self):
|
|
"""Test help command execution."""
|
|
# Test main help
|
|
result = main(["--help"]) # Help should exit successfully
|
|
assert result == 0
|
|
|
|
def test_help_contains_security_info(self, capsys):
|
|
"""Test that help output contains security information."""
|
|
try:
|
|
main(["--help"])
|
|
except SystemExit:
|
|
pass # Help exits with SystemExit
|
|
|
|
captured = capsys.readouterr()
|
|
help_output = captured.out + captured.err
|
|
|
|
# Should mention security filters
|
|
assert "filter" in help_output.lower() or "security" in help_output.lower()
|
|
|
|
def test_command_specific_help(self):
|
|
"""Test command-specific help if available."""
|
|
# Some CLI implementations support command-specific help
|
|
# This test can be extended based on actual implementation
|
|
pass
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestCLIIntegration:
|
|
"""Integration tests for CLI."""
|
|
|
|
def test_full_workflow(self, sample_files, temp_dir):
|
|
"""Test complete workflow: create, list, test, extract."""
|
|
archive_path = temp_dir / "workflow.tzst"
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
extract_dir = temp_dir / "workflow_extracted"
|
|
|
|
# Create archive
|
|
result = main(["a", str(archive_path), *file_paths])
|
|
assert result == 0
|
|
assert archive_path.exists()
|
|
|
|
# List contents
|
|
result = main(["l", str(archive_path)])
|
|
assert result == 0
|
|
|
|
# Test integrity
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 0
|
|
|
|
# Extract
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir)])
|
|
assert result == 0
|
|
assert extract_dir.exists()
|
|
|
|
# Verify extracted files exist and have correct content
|
|
for file_path in sample_files:
|
|
if file_path.is_file():
|
|
relative_path = file_path.relative_to(sample_files[0].parent)
|
|
extracted_file = extract_dir / relative_path
|
|
assert extracted_file.exists()
|
|
|
|
# Compare content
|
|
original_content = file_path.read_bytes()
|
|
extracted_content = extracted_file.read_bytes()
|
|
assert original_content == extracted_content
|
|
|
|
|
|
class TestCLIStreamingOptions:
|
|
"""Test CLI streaming options."""
|
|
|
|
def test_extract_streaming_flag(self, sample_files, temp_dir):
|
|
"""Test extract command with streaming flag."""
|
|
archive_path = temp_dir / "cli_streaming_test.tzst"
|
|
file_paths = [f for f in sample_files if f.is_file()]
|
|
|
|
# Create archive first
|
|
from tzst import create_archive
|
|
|
|
create_archive(archive_path, file_paths)
|
|
|
|
# Test extract with streaming
|
|
parser = create_parser()
|
|
args = parser.parse_args(
|
|
["x", str(archive_path), "--streaming", "-o", str(temp_dir / "extracted")]
|
|
)
|
|
|
|
assert args.command == "x"
|
|
assert hasattr(args, "streaming")
|
|
assert args.streaming is True
|
|
|
|
def test_list_streaming_flag(self, sample_files, temp_dir):
|
|
"""Test list command with streaming flag."""
|
|
archive_path = temp_dir / "cli_list_streaming.tzst"
|
|
file_paths = [f for f in sample_files if f.is_file()]
|
|
|
|
# Create archive first
|
|
from tzst import create_archive
|
|
|
|
create_archive(archive_path, file_paths)
|
|
|
|
# Test list with streaming
|
|
parser = create_parser()
|
|
args = parser.parse_args(["l", str(archive_path), "--streaming"])
|
|
|
|
assert args.command == "l"
|
|
assert hasattr(args, "streaming")
|
|
assert args.streaming is True
|
|
|
|
def test_test_streaming_flag(self, sample_files, temp_dir):
|
|
"""Test test command with streaming flag."""
|
|
archive_path = temp_dir / "cli_test_streaming.tzst"
|
|
file_paths = [f for f in sample_files if f.is_file()]
|
|
|
|
# Create archive first
|
|
from tzst import create_archive
|
|
|
|
create_archive(archive_path, file_paths)
|
|
|
|
# Test test command with streaming
|
|
parser = create_parser()
|
|
args = parser.parse_args(["t", str(archive_path), "--streaming"])
|
|
|
|
assert args.command == "t"
|
|
assert hasattr(args, "streaming")
|
|
assert args.streaming is True
|
|
|
|
|
|
class TestCLICompressionLevels:
|
|
"""Test CLI compression level handling."""
|
|
|
|
def test_valid_compression_levels(self, sample_files, temp_dir):
|
|
"""Test various valid compression levels."""
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
|
|
# Test compression levels 1, 3, 10, 22
|
|
for level in [1, 3, 10, 22]:
|
|
archive_path = temp_dir / f"level_{level}.tzst"
|
|
result = main(["a", str(archive_path), *file_paths, "-l", str(level)])
|
|
assert result == 0
|
|
assert archive_path.exists()
|
|
|
|
|
|
class TestCLIAtomicOperations:
|
|
"""Test CLI atomic and non-atomic operations."""
|
|
|
|
def test_no_atomic_flag(self, sample_files, temp_dir):
|
|
"""Test --no-atomic flag functionality."""
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "no_atomic.tzst"
|
|
|
|
# Test --no-atomic flag
|
|
result = main(["a", str(archive_path), *file_paths, "--no-atomic"])
|
|
assert result == 0
|
|
assert archive_path.exists()
|
|
|
|
# Verify archive integrity
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 0
|
|
|
|
def test_atomic_vs_no_atomic_results(self, sample_files, temp_dir):
|
|
"""Test that atomic and non-atomic modes produce equivalent archives."""
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
|
|
# Create archive with atomic mode (default)
|
|
atomic_archive = temp_dir / "atomic.tzst"
|
|
result = main(["a", str(atomic_archive), *file_paths])
|
|
assert result == 0
|
|
|
|
# Create archive with non-atomic mode
|
|
non_atomic_archive = temp_dir / "non_atomic.tzst"
|
|
result = main(["a", str(non_atomic_archive), *file_paths, "--no-atomic"])
|
|
assert result == 0
|
|
|
|
# Both archives should be valid and contain same files
|
|
result_atomic = main(["t", str(atomic_archive)])
|
|
result_non_atomic = main(["t", str(non_atomic_archive)])
|
|
assert result_atomic == 0
|
|
assert result_non_atomic == 0
|
|
|
|
|
|
class TestCLISecurityFilters:
|
|
"""Test CLI security filtering options."""
|
|
|
|
def test_tar_filter(self, sample_files, temp_dir):
|
|
"""Test --filter tar option."""
|
|
# First create an archive
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "filter_test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Test extraction with tar filter
|
|
extract_dir = temp_dir / "tar_filtered"
|
|
result = main(
|
|
["x", str(archive_path), "-o", str(extract_dir), "--filter", "tar"]
|
|
)
|
|
assert result == 0
|
|
assert extract_dir.exists()
|
|
|
|
def test_data_filter(self, sample_files, temp_dir):
|
|
"""Test --filter data option."""
|
|
# First create an archive
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "filter_test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Test extraction with data filter
|
|
extract_dir = temp_dir / "data_filtered"
|
|
result = main(
|
|
["x", str(archive_path), "-o", str(extract_dir), "--filter", "data"]
|
|
)
|
|
assert result == 0
|
|
assert extract_dir.exists()
|
|
|
|
def test_invalid_filter(self, sample_files, temp_dir):
|
|
"""Test invalid filter option returns error."""
|
|
# First create an archive
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "filter_test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Test extraction with invalid filter
|
|
extract_dir = temp_dir / "invalid_filtered"
|
|
result = main(
|
|
["x", str(archive_path), "-o", str(extract_dir), "--filter", "invalid"]
|
|
)
|
|
assert result == 1 # Should fail
|
|
|
|
|
|
class TestCLIStreamingOperations:
|
|
"""Test CLI streaming operations."""
|
|
|
|
def test_list_streaming(self, sample_files, temp_dir):
|
|
"""Test list command with --streaming flag."""
|
|
# First create an archive
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "streaming_test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Test list with streaming
|
|
result = main(["l", str(archive_path), "--streaming"])
|
|
assert result == 0
|
|
|
|
def test_test_streaming(self, sample_files, temp_dir):
|
|
"""Test test command with --streaming flag."""
|
|
# First create an archive
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "streaming_test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Test archive integrity with streaming
|
|
result = main(["t", str(archive_path), "--streaming"])
|
|
assert result == 0
|
|
|
|
def test_extract_streaming(self, sample_files, temp_dir):
|
|
"""Test extract command with --streaming flag."""
|
|
# First create an archive
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "streaming_test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Test extraction with streaming
|
|
extract_dir = temp_dir / "streaming_extracted"
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir), "--streaming"])
|
|
assert result == 0
|
|
assert extract_dir.exists()
|
|
|
|
|
|
class TestCLISpecialFiles:
|
|
"""Test CLI with special file types and names."""
|
|
|
|
def test_empty_files(self, temp_dir):
|
|
"""Test archiving empty files."""
|
|
# Create empty file
|
|
empty_file = temp_dir / "empty.txt"
|
|
empty_file.touch()
|
|
|
|
archive_path = temp_dir / "empty_test.tzst"
|
|
result = main(["a", str(archive_path), str(empty_file)])
|
|
assert result == 0
|
|
assert archive_path.exists()
|
|
|
|
def test_files_with_spaces_and_special_chars(self, temp_dir):
|
|
"""Test files with spaces and special characters."""
|
|
# Create file with special characters
|
|
special_file = temp_dir / "file with spaces & special chars!@#$%.txt"
|
|
special_file.write_text("Special file content")
|
|
|
|
archive_path = temp_dir / "special_chars.tzst"
|
|
result = main(["a", str(archive_path), str(special_file)])
|
|
assert result == 0
|
|
|
|
def test_very_long_filenames(self, temp_dir):
|
|
"""Test files with very long names."""
|
|
# Create file with very long name
|
|
long_name = "long_" + "x" * 200 + ".txt"
|
|
long_file = temp_dir / long_name
|
|
long_file.write_text("Long filename content")
|
|
|
|
archive_path = temp_dir / "long_filename.tzst"
|
|
result = main(["a", str(archive_path), str(long_file)])
|
|
assert result == 0
|
|
|
|
def test_files_with_dots(self, temp_dir):
|
|
"""Test files with multiple dots in name."""
|
|
# Create file with multiple dots
|
|
dotted_file = temp_dir / "multiple.dots.in.name.txt"
|
|
dotted_file.write_text("Dotted file content")
|
|
|
|
archive_path = temp_dir / "dotted.tzst"
|
|
result = main(["a", str(archive_path), str(dotted_file)])
|
|
assert result == 0
|
|
|
|
|
|
class TestCLILargeFiles:
|
|
"""Test CLI with large files."""
|
|
|
|
def test_large_text_file(self, temp_dir):
|
|
"""Test archiving large text files."""
|
|
# Create large text file (1MB+)
|
|
large_file = temp_dir / "large.txt"
|
|
content = "This is a large file content.\n" * 50000
|
|
large_file.write_text(content)
|
|
|
|
archive_path = temp_dir / "large_file.tzst"
|
|
result = main(["a", str(archive_path), str(large_file), "-l", "22"])
|
|
assert result == 0
|
|
assert archive_path.exists()
|
|
|
|
def test_binary_file(self, temp_dir):
|
|
"""Test archiving binary files."""
|
|
# Create binary file
|
|
binary_file = temp_dir / "binary.bin"
|
|
binary_content = bytes(range(256)) * 1000 # 256KB of binary data
|
|
binary_file.write_bytes(binary_content)
|
|
|
|
archive_path = temp_dir / "binary.tzst"
|
|
result = main(["a", str(archive_path), str(binary_file)])
|
|
assert result == 0
|
|
|
|
|
|
class TestCLIUnicodeHandling:
|
|
"""Test CLI with Unicode and international characters."""
|
|
|
|
def test_unicode_filenames(self, temp_dir):
|
|
"""Test files with Unicode characters in names."""
|
|
# Create file with Unicode characters (may fail on some platforms)
|
|
try:
|
|
unicode_file = temp_dir / "测试文件_🌟.txt"
|
|
unicode_file.write_text("Unicode content", encoding="utf-8")
|
|
|
|
archive_path = temp_dir / "unicode.tzst"
|
|
result = main(["a", str(archive_path), str(unicode_file)])
|
|
# Result may be 0 (success) or 1 (failure) depending on platform
|
|
assert result in [0, 1]
|
|
except (OSError, UnicodeError):
|
|
# Skip if platform doesn't support Unicode filenames
|
|
pytest.skip("Platform doesn't support Unicode filenames")
|
|
|
|
def test_unicode_content(self, temp_dir):
|
|
"""Test files with Unicode content."""
|
|
# Create file with Unicode content
|
|
unicode_content_file = temp_dir / "unicode_content.txt"
|
|
unicode_content = "Hello 世界! 🌍 Здравствуй мир!"
|
|
unicode_content_file.write_text(unicode_content, encoding="utf-8")
|
|
|
|
archive_path = temp_dir / "unicode_content.tzst"
|
|
result = main(["a", str(archive_path), str(unicode_content_file)])
|
|
assert result == 0
|
|
|
|
# Test extraction and content verification
|
|
extract_dir = temp_dir / "unicode_extracted"
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir)])
|
|
assert result == 0
|
|
|
|
extracted_file = extract_dir / "unicode_content.txt"
|
|
assert extracted_file.exists()
|
|
extracted_content = extracted_file.read_text(encoding="utf-8")
|
|
assert extracted_content == unicode_content
|
|
|
|
|
|
class TestCLIExitCodes:
|
|
"""Test CLI exit codes for proper script integration."""
|
|
|
|
def test_successful_operations_return_zero(self, sample_files, temp_dir):
|
|
"""Test that successful operations return exit code 0."""
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "success.tzst"
|
|
|
|
# Create archive
|
|
result = main(["a", str(archive_path), *file_paths])
|
|
assert result == 0
|
|
|
|
# List archive
|
|
result = main(["l", str(archive_path)])
|
|
assert result == 0
|
|
|
|
# Test archive
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 0
|
|
|
|
# Extract archive
|
|
extract_dir = temp_dir / "extracted"
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir)])
|
|
assert result == 0
|
|
|
|
def test_error_conditions_return_nonzero(self, temp_dir):
|
|
"""Test that error conditions return non-zero exit codes."""
|
|
# Non-existent archive
|
|
result = main(["l", str(temp_dir / "nonexistent.tzst")])
|
|
assert result != 0
|
|
|
|
# Non-existent file to archive
|
|
result = main(["a", str(temp_dir / "test.tzst"), "nonexistent.txt"])
|
|
assert result != 0
|
|
|
|
# Invalid compression level
|
|
result = main(["a", str(temp_dir / "test.tzst"), __file__, "-l", "0"])
|
|
assert result != 0
|
|
|
|
# Invalid filter
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), __file__]) # Create valid archive first
|
|
result = main(["x", str(archive_path), "--filter", "invalid"])
|
|
assert result != 0
|
|
|
|
def test_help_and_version_return_proper_codes(self):
|
|
"""Test help and version commands return appropriate codes."""
|
|
# Help should return non-zero (standard for help)
|
|
result = main([])
|
|
assert result == 1
|
|
|
|
# Version should return zero
|
|
result = main(["--version"])
|
|
assert result == 0
|
|
|
|
|
|
class TestCLIPerformance:
|
|
"""Test CLI performance with various scenarios."""
|
|
|
|
def test_large_number_of_small_files(self, temp_dir):
|
|
"""Test archiving many small files."""
|
|
# Create multiple small files
|
|
files = []
|
|
for i in range(50):
|
|
small_file = temp_dir / f"small_{i:03d}.txt"
|
|
small_file.write_text(f"Small file {i} content")
|
|
files.append(str(small_file))
|
|
|
|
archive_path = temp_dir / "many_files.tzst"
|
|
result = main(["a", str(archive_path), *files])
|
|
assert result == 0
|
|
assert archive_path.exists()
|
|
|
|
def test_nested_directory_structure(self, temp_dir):
|
|
"""Test archiving nested directory structures."""
|
|
# Create nested structure
|
|
for i in range(5):
|
|
nested_dir = temp_dir / f"level_{i}"
|
|
for j in range(i + 1):
|
|
nested_dir = nested_dir / f"sub_{j}"
|
|
nested_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
nested_file = nested_dir / f"file_{i}.txt"
|
|
nested_file.write_text(f"Nested file at level {i}")
|
|
|
|
archive_path = temp_dir / "nested.tzst"
|
|
result = main(["a", str(archive_path), str(temp_dir / "level_0")])
|
|
assert result == 0
|
|
|
|
|
|
class TestCLIRealWorldScenarios:
|
|
"""Test scenarios discovered during comprehensive real-world testing."""
|
|
|
|
def test_no_atomic_flag_with_path_resolution(self, temp_dir):
|
|
"""Test --no-atomic flag with various path scenarios.
|
|
|
|
This tests the critical bug fix where --no-atomic failed due to
|
|
path resolution issues when changing working directory.
|
|
"""
|
|
# Create test file
|
|
test_file = temp_dir / "test_no_atomic.txt"
|
|
test_file.write_text("No atomic test content")
|
|
|
|
# Test no-atomic in current directory
|
|
archive_path = temp_dir / "no_atomic_test.tzst"
|
|
result = main(["a", str(archive_path), str(test_file), "--no-atomic"])
|
|
assert result == 0
|
|
assert archive_path.exists()
|
|
|
|
# Verify archive integrity
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 0 # Test extraction works
|
|
extract_dir = temp_dir / "no_atomic_extracted"
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir)])
|
|
assert result == 0
|
|
extracted_file = extract_dir / "test_no_atomic.txt"
|
|
assert extracted_file.exists()
|
|
|
|
def test_no_atomic_flag_in_subdirectory(self, temp_dir):
|
|
"""Test --no-atomic flag when archive is in subdirectory."""
|
|
# Create subdirectory and test file
|
|
subdir = temp_dir / "subdir"
|
|
subdir.mkdir()
|
|
test_file = temp_dir / "test_sub.txt"
|
|
test_file.write_text("Subdirectory test content")
|
|
|
|
# Create archive in subdirectory with --no-atomic
|
|
archive_path = subdir / "sub_no_atomic.tzst"
|
|
result = main(["a", str(archive_path), str(test_file), "--no-atomic"])
|
|
assert result == 0
|
|
assert archive_path.exists()
|
|
|
|
def test_compression_level_clamping(self, temp_dir):
|
|
"""Test that extreme compression levels are handled properly."""
|
|
test_file = temp_dir / "clamp_test.txt"
|
|
test_file.write_text("Compression clamping test")
|
|
|
|
# Test compression level higher than maximum
|
|
archive_path = temp_dir / "extreme_compression.tzst"
|
|
result = main(["a", str(archive_path), str(test_file), "-l", "50"])
|
|
assert result == 2 # argparse error
|
|
|
|
# Test negative compression level
|
|
result = main(["a", str(archive_path), str(test_file), "-l", "-1"])
|
|
assert result == 2 # argparse error
|
|
|
|
def test_whitespace_only_file(self, temp_dir):
|
|
"""Test archiving files with only whitespace content."""
|
|
whitespace_file = temp_dir / "whitespace.txt"
|
|
whitespace_file.write_text(" \n\t\n \n")
|
|
|
|
archive_path = temp_dir / "whitespace.tzst"
|
|
result = main(["a", str(archive_path), str(whitespace_file)])
|
|
assert result == 0
|
|
|
|
# Extract and verify content preserved
|
|
extract_dir = temp_dir / "whitespace_extracted"
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir)])
|
|
assert result == 0
|
|
|
|
extracted_file = extract_dir / "whitespace.txt"
|
|
assert extracted_file.exists()
|
|
assert extracted_file.read_text() == " \n\t\n \n"
|
|
|
|
def test_null_bytes_binary_file(self, temp_dir):
|
|
"""Test archiving binary files with null bytes."""
|
|
null_file = temp_dir / "null_bytes.bin"
|
|
null_content = b"\x00\x00\x00\x01\x00\x02\x00\x00\x03"
|
|
null_file.write_bytes(null_content)
|
|
|
|
archive_path = temp_dir / "null_bytes.tzst"
|
|
result = main(["a", str(archive_path), str(null_file)])
|
|
assert result == 0
|
|
|
|
# Extract and verify binary content preserved
|
|
extract_dir = temp_dir / "null_extracted"
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir)])
|
|
assert result == 0
|
|
|
|
extracted_file = extract_dir / "null_bytes.bin"
|
|
assert extracted_file.exists()
|
|
assert extracted_file.read_bytes() == null_content
|
|
|
|
def test_streaming_with_filters_combination(self, temp_dir):
|
|
"""Test streaming flag combined with security filters."""
|
|
# Create test archive
|
|
test_file = temp_dir / "stream_filter.txt"
|
|
test_file.write_text("Streaming with filter test")
|
|
|
|
archive_path = temp_dir / "stream_filter.tzst"
|
|
result = main(["a", str(archive_path), str(test_file)])
|
|
assert result == 0
|
|
|
|
# Test list with streaming
|
|
result = main(["l", str(archive_path), "--streaming"])
|
|
assert result == 0
|
|
|
|
# Test extract with streaming and filter
|
|
extract_dir = temp_dir / "stream_filter_extracted"
|
|
result = main(
|
|
[
|
|
"x",
|
|
str(archive_path),
|
|
"-o",
|
|
str(extract_dir),
|
|
"--streaming",
|
|
"--filter",
|
|
"data",
|
|
]
|
|
)
|
|
assert result == 0
|
|
|
|
def test_exit_code_propagation_fix(self, temp_dir):
|
|
"""Test that exit codes are properly propagated (fixed issue)."""
|
|
# Test successful operation returns 0
|
|
test_file = temp_dir / "exit_test.txt"
|
|
test_file.write_text("Exit code test")
|
|
|
|
archive_path = temp_dir / "exit_test.tzst"
|
|
result = main(["a", str(archive_path), str(test_file)])
|
|
assert result == 0 # Success should return 0
|
|
|
|
# Test error operation returns non-zero
|
|
result = main(["l", "nonexistent_archive.tzst"])
|
|
assert result != 0 # Error should return non-zero
|
|
|
|
|
|
class TestCLISecurityFilterParsing:
|
|
"""Test CLI security filter argument parsing."""
|
|
|
|
def test_extract_filter_argument_parsing(self):
|
|
"""Test that --filter argument is parsed correctly for extract commands."""
|
|
parser = create_parser()
|
|
|
|
# Test x/extract command with filter
|
|
args = parser.parse_args(["x", "test.tzst", "--filter", "data"])
|
|
assert args.command == "x"
|
|
assert args.filter == "data"
|
|
|
|
args = parser.parse_args(["extract", "test.tzst", "--filter", "tar"])
|
|
assert args.command == "extract"
|
|
assert args.filter == "tar"
|
|
|
|
# Test e/extract-flat command with filter
|
|
args = parser.parse_args(["e", "test.tzst", "--filter", "data"])
|
|
assert args.command == "e"
|
|
assert args.filter == "data"
|
|
|
|
def test_filter_default_value(self):
|
|
"""Test that filter defaults to 'data'."""
|
|
parser = create_parser()
|
|
|
|
# Test default for x command
|
|
args = parser.parse_args(["x", "test.tzst"])
|
|
assert args.filter == "data"
|
|
|
|
# Test default for e command
|
|
args = parser.parse_args(["e", "test.tzst"])
|
|
assert args.filter == "data"
|
|
|
|
def test_filter_invalid_choices(self):
|
|
"""Test that invalid filter choices are rejected."""
|
|
parser = create_parser()
|
|
|
|
# Invalid filter should raise SystemExit (argparse error)
|
|
with pytest.raises(SystemExit):
|
|
parser.parse_args(["x", "test.tzst", "--filter", "invalid"])
|
|
|
|
def test_extract_with_filter(self, sample_files, temp_dir):
|
|
"""Test extract command with security filters."""
|
|
archive_path = temp_dir / "cli_extract_filter.tzst"
|
|
file_paths = [str(f) for f in sample_files if f.is_file()][:1]
|
|
|
|
# Create archive first
|
|
result = main(["a", str(archive_path), *file_paths])
|
|
assert result == 0
|
|
|
|
# Test extraction with different filters
|
|
for filter_type in ["data", "tar", "fully_trusted"]:
|
|
output_dir = temp_dir / f"extracted_{filter_type}"
|
|
result = main(
|
|
["x", str(archive_path), "--filter", filter_type, "-o", str(output_dir)]
|
|
)
|
|
assert result == 0
|
|
|
|
def test_filter_with_other_options(self):
|
|
"""Test filter option combined with other options."""
|
|
parser = create_parser()
|
|
|
|
# Test with streaming and output directory
|
|
args = parser.parse_args(
|
|
[
|
|
"x",
|
|
"test.tzst",
|
|
"file1.txt",
|
|
"file2.txt",
|
|
"--filter",
|
|
"tar",
|
|
"--streaming",
|
|
"-o",
|
|
"output_dir",
|
|
]
|
|
)
|
|
assert args.filter == "tar"
|
|
assert args.streaming is True
|
|
assert args.output == "output_dir"
|
|
assert args.files == ["file1.txt", "file2.txt"]
|
|
|
|
def test_filter_not_available_for_non_extract_commands(self):
|
|
"""Test that --filter is only available for extract commands."""
|
|
parser = create_parser()
|
|
|
|
# Should work for extract commands
|
|
parser.parse_args(["x", "test.tzst", "--filter", "data"])
|
|
parser.parse_args(["e", "test.tzst", "--filter", "tar"])
|
|
|
|
# Should fail for non-extract commands
|
|
with pytest.raises(SystemExit):
|
|
parser.parse_args(["a", "test.tzst", "file.txt", "--filter", "data"])
|
|
|
|
with pytest.raises(SystemExit):
|
|
parser.parse_args(["l", "test.tzst", "--filter", "data"])
|
|
|
|
with pytest.raises(SystemExit):
|
|
parser.parse_args(["t", "test.tzst", "--filter", "data"])
|
|
|
|
|
|
class TestCLIBoundaryConditions:
|
|
"""Test boundary conditions and edge cases."""
|
|
|
|
def test_compression_level_boundaries(self, temp_dir):
|
|
"""Test exact boundary values for compression levels."""
|
|
test_file = temp_dir / "boundary_test.txt"
|
|
test_file.write_text("Boundary compression test")
|
|
|
|
# Test minimum valid level (1)
|
|
archive_min = temp_dir / "min_compression.tzst"
|
|
result = main(["a", str(archive_min), str(test_file), "-l", "1"])
|
|
assert result == 0
|
|
|
|
# Test maximum valid level (22)
|
|
archive_max = temp_dir / "max_compression.tzst"
|
|
result = main(["a", str(archive_max), str(test_file), "-l", "22"])
|
|
assert result == 0
|
|
|
|
# Test just below minimum (should fail with argparse error)
|
|
result = main(["a", "temp.tzst", str(test_file), "-l", "0"])
|
|
assert result == 2 # argparse error for invalid choice
|
|
|
|
# Test just above maximum (should fail or clamp)
|
|
result = main(["a", "temp.tzst", str(test_file), "-l", "23"])
|
|
assert result in [0, 1, 2] # May clamp or error
|
|
|
|
def test_edge_case_scenarios(self, temp_dir):
|
|
"""Test edge cases and boundary conditions."""
|
|
# Test with special characters in paths
|
|
special_file = temp_dir / "file with spaces.txt"
|
|
special_file.write_text("content")
|
|
special_archive = temp_dir / "special archive.tzst"
|
|
|
|
result = main(["a", str(special_archive), str(special_file)])
|
|
# Should handle special characters gracefully
|
|
|
|
if result == 0:
|
|
# If creation succeeded, test other operations
|
|
result = main(["l", str(special_archive)])
|
|
assert result == 0
|
|
|
|
result = main(["t", str(special_archive)])
|
|
assert result == 0
|
|
|
|
def test_maximum_filename_length(self, temp_dir):
|
|
"""Test files with maximum allowable filename length."""
|
|
# Create file with very long name (close to filesystem limit)
|
|
long_name = "a" * 200 + ".txt"
|
|
try:
|
|
long_file = temp_dir / long_name
|
|
long_file.write_text("Long filename test")
|
|
|
|
archive_path = temp_dir / "long_filename.tzst"
|
|
result = main(["a", str(archive_path), str(long_file)])
|
|
# Should succeed or fail gracefully
|
|
assert result in [0, 1]
|
|
except OSError:
|
|
# Skip if filesystem doesn't support such long names
|
|
pytest.skip("Filesystem doesn't support long filenames")
|
|
|
|
|
|
class TestCompressionLevelValidation:
|
|
"""Test compression level validation in CLI."""
|
|
|
|
def test_valid_compression_levels(self, temp_dir):
|
|
"""Test that valid compression levels (1-22) work correctly."""
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("Test content")
|
|
|
|
# Test boundary values and some middle values
|
|
valid_levels = [1, 2, 10, 22]
|
|
|
|
for level in valid_levels:
|
|
archive_path = temp_dir / f"test_level_{level}.tzst"
|
|
result = main(["a", str(archive_path), str(test_file), "-l", str(level)])
|
|
assert result == 0, f"Compression level {level} should be valid"
|
|
|
|
def test_invalid_compression_levels(self, temp_dir):
|
|
"""Test that invalid compression levels return proper error codes."""
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("Test content")
|
|
archive_path = temp_dir / "test.tzst"
|
|
|
|
# Test invalid levels that should return exit code 2 (argparse error)
|
|
invalid_levels = [0, 23, 50, 100, -1, -10]
|
|
|
|
for level in invalid_levels:
|
|
result = main(["a", str(archive_path), str(test_file), "-l", str(level)])
|
|
assert result == 2, (
|
|
f"Invalid compression level {level} should return exit code 2"
|
|
)
|
|
|
|
def test_non_numeric_compression_levels(self, temp_dir):
|
|
"""Test that non-numeric compression levels return proper error codes."""
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("Test content")
|
|
archive_path = temp_dir / "test.tzst"
|
|
|
|
# Test non-numeric values
|
|
invalid_values = ["abc", "1.5", "high", "max", ""]
|
|
|
|
for value in invalid_values:
|
|
result = main(["a", str(archive_path), str(test_file), "-l", value])
|
|
assert result == 2, (
|
|
f"Non-numeric compression level '{value}' should return exit code 2"
|
|
)
|
|
|
|
def test_compression_level_extremes(self, temp_dir):
|
|
"""Test compression level validation with extreme values."""
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("Test content")
|
|
archive_path = temp_dir / "test.tzst"
|
|
|
|
# Test level 50 - should return argparse error code 2
|
|
result = main(["a", str(archive_path), str(test_file), "-l", "50"])
|
|
assert result == 2, "Compression level 50 should return exit code 2"
|
|
|
|
|
|
class TestCLIVersionCommand:
|
|
"""Test version command functionality."""
|
|
|
|
def test_version_command_direct(self):
|
|
"""Test version command handler directly."""
|
|
from tzst.cli import cmd_version
|
|
|
|
class MockArgs:
|
|
pass
|
|
|
|
args = MockArgs()
|
|
result = cmd_version(args)
|
|
assert result == 0
|
|
|
|
def test_version_command_via_main(self):
|
|
"""Test version command via main function."""
|
|
result = main(["--version"])
|
|
assert result == 0
|
|
|
|
def test_version_flag_parsing(self):
|
|
"""Test that version flag is properly parsed."""
|
|
parser = create_parser()
|
|
args = parser.parse_args(["--version"])
|
|
assert hasattr(args, "version")
|
|
assert args.version is True
|
|
|
|
|
|
class TestCLIValidationFunctions:
|
|
"""Test internal CLI validation functions."""
|
|
|
|
def test_validate_compression_level_in_argv_valid(self):
|
|
"""Test compression level validation with valid levels."""
|
|
from tzst.cli import _validate_compression_level_in_argv
|
|
|
|
# Test valid levels
|
|
assert not _validate_compression_level_in_argv(["a", "test.tzst", "-l", "3"])
|
|
assert not _validate_compression_level_in_argv(
|
|
["a", "test.tzst", "--level", "22"]
|
|
)
|
|
assert not _validate_compression_level_in_argv(
|
|
["a", "test.tzst", "--level", "1"]
|
|
)
|
|
|
|
def test_validate_compression_level_in_argv_invalid_range(self, capsys):
|
|
"""Test compression level validation with invalid range."""
|
|
from tzst.cli import _validate_compression_level_in_argv
|
|
|
|
# Test invalid range
|
|
assert _validate_compression_level_in_argv(["a", "test.tzst", "-l", "0"])
|
|
captured = capsys.readouterr()
|
|
assert "Invalid compression level: 0" in captured.err
|
|
assert "Must be between 1 and 22" in captured.err
|
|
|
|
assert _validate_compression_level_in_argv(["a", "test.tzst", "--level", "23"])
|
|
captured = capsys.readouterr()
|
|
assert "Invalid compression level: 23" in captured.err
|
|
|
|
def test_validate_compression_level_in_argv_invalid_format(self, capsys):
|
|
"""Test compression level validation with invalid format."""
|
|
from tzst.cli import _validate_compression_level_in_argv
|
|
|
|
# Test invalid format
|
|
assert _validate_compression_level_in_argv(["a", "test.tzst", "-l", "abc"])
|
|
captured = capsys.readouterr()
|
|
assert "Invalid compression level: 'abc'" in captured.err
|
|
assert "Must be an integer" in captured.err
|
|
|
|
def test_validate_compression_level_in_argv_no_level(self):
|
|
"""Test compression level validation when no level specified."""
|
|
from tzst.cli import _validate_compression_level_in_argv
|
|
|
|
# Test no level specified
|
|
assert not _validate_compression_level_in_argv(["a", "test.tzst", "file.txt"])
|
|
|
|
def test_validate_filter_in_argv_valid(self):
|
|
"""Test filter validation with valid filters."""
|
|
from tzst.cli import _validate_filter_in_argv
|
|
|
|
# Test valid filters
|
|
assert not _validate_filter_in_argv(["x", "test.tzst", "--filter", "data"])
|
|
assert not _validate_filter_in_argv(["x", "test.tzst", "--filter", "tar"])
|
|
assert not _validate_filter_in_argv(
|
|
["x", "test.tzst", "--filter", "fully_trusted"]
|
|
)
|
|
|
|
def test_validate_filter_in_argv_invalid(self, capsys):
|
|
"""Test filter validation with invalid filter."""
|
|
from tzst.cli import _validate_filter_in_argv
|
|
|
|
# Test invalid filter
|
|
assert _validate_filter_in_argv(["x", "test.tzst", "--filter", "invalid"])
|
|
captured = capsys.readouterr()
|
|
assert "Invalid filter specified: invalid" in captured.err
|
|
assert "Must be one of: data, tar, fully_trusted" in captured.err
|
|
|
|
def test_validate_filter_in_argv_no_filter(self):
|
|
"""Test filter validation when no filter specified."""
|
|
from tzst.cli import _validate_filter_in_argv
|
|
|
|
# Test no filter specified
|
|
assert not _validate_filter_in_argv(["x", "test.tzst"])
|
|
|
|
|
|
class TestCLIErrorHandlingInternal:
|
|
def test_handle_parsing_errors_help_requested(self):
|
|
"""Test error handling when help is requested."""
|
|
from tzst.cli import _handle_parsing_errors
|
|
|
|
# Simulate help request (exit code 0)
|
|
e = SystemExit(0)
|
|
result = _handle_parsing_errors(e, ["--help"])
|
|
assert result == 0
|
|
|
|
def test_handle_parsing_errors_invalid_filter(self, capsys):
|
|
"""Test error handling for invalid filter."""
|
|
from tzst.cli import _handle_parsing_errors
|
|
|
|
# Simulate parsing error (exit code 2) with invalid filter
|
|
e = SystemExit(2)
|
|
argv = ["x", "test.tzst", "--filter", "invalid"]
|
|
result = _handle_parsing_errors(e, argv)
|
|
assert result == 1 # Should convert to exit code 1
|
|
captured = capsys.readouterr()
|
|
assert "Invalid filter specified" in captured.err
|
|
|
|
def test_handle_parsing_errors_other_errors(self):
|
|
"""Test error handling for other parsing errors."""
|
|
from tzst.cli import _handle_parsing_errors
|
|
|
|
# Test with exit code 2 but no special validation errors
|
|
e = SystemExit(2)
|
|
result = _handle_parsing_errors(e, ["invalid", "command"])
|
|
assert result == 2
|
|
|
|
# Test with None exit code
|
|
e = SystemExit(None)
|
|
result = _handle_parsing_errors(e, [])
|
|
assert result == 1
|
|
|
|
def test_parse_arguments_success(self):
|
|
"""Test successful argument parsing."""
|
|
from tzst.cli import _parse_arguments
|
|
|
|
parser = create_parser()
|
|
args, error_code = _parse_arguments(parser, ["a", "test.tzst", "file.txt"])
|
|
assert args is not None
|
|
assert error_code is None
|
|
assert args.command == "a"
|
|
|
|
def test_parse_arguments_error(self):
|
|
"""Test argument parsing with errors."""
|
|
from tzst.cli import _parse_arguments
|
|
|
|
parser = create_parser()
|
|
args, error_code = _parse_arguments(parser, ["invalid"])
|
|
assert args is None
|
|
assert error_code == 2
|
|
|
|
def test_execute_command_no_func(self, capsys):
|
|
"""Test command execution when no function is set."""
|
|
from tzst.cli import _execute_command
|
|
|
|
class MockArgs:
|
|
pass
|
|
|
|
args = MockArgs()
|
|
parser = create_parser()
|
|
result = _execute_command(args, parser)
|
|
assert result == 1
|
|
# Should print help when no function is available
|
|
|
|
def test_execute_command_version_flag(self):
|
|
"""Test command execution with version flag."""
|
|
from tzst.cli import _execute_command
|
|
|
|
class MockArgs:
|
|
version = True
|
|
|
|
args = MockArgs()
|
|
parser = create_parser()
|
|
result = _execute_command(args, parser)
|
|
assert result == 0
|
|
|
|
def test_invalid_arguments_main(self):
|
|
"""Test main function with invalid arguments."""
|
|
result = main(["invalid-command"])
|
|
assert result == 2 # Argument parsing error
|
|
|
|
|
|
class TestCLIStreamingAndAdvancedOptions:
|
|
"""Test CLI streaming and advanced options."""
|
|
|
|
def test_streaming_option_parsing(self):
|
|
"""Test that streaming options are properly parsed."""
|
|
parser = create_parser()
|
|
|
|
# Test list command with streaming
|
|
args = parser.parse_args(["l", "test.tzst", "--streaming"])
|
|
assert hasattr(args, "streaming")
|
|
assert args.streaming is True
|
|
|
|
# Test test command with streaming
|
|
args = parser.parse_args(["t", "test.tzst", "--streaming"])
|
|
assert hasattr(args, "streaming")
|
|
assert args.streaming is True
|
|
|
|
def test_atomic_options_parsing(self):
|
|
"""Test atomic operation options."""
|
|
parser = create_parser()
|
|
|
|
# Test with atomic (default)
|
|
args = parser.parse_args(["a", "test.tzst", "file.txt"])
|
|
assert not hasattr(args, "no_atomic") or not args.no_atomic
|
|
|
|
# Test with no-atomic
|
|
args = parser.parse_args(["a", "test.tzst", "file.txt", "--no-atomic"])
|
|
assert hasattr(args, "no_atomic")
|
|
assert args.no_atomic is True
|
|
|
|
def test_verbose_options_parsing(self):
|
|
"""Test verbose option parsing."""
|
|
parser = create_parser()
|
|
|
|
# Test list command with verbose
|
|
args = parser.parse_args(["l", "test.tzst", "-v"])
|
|
assert hasattr(args, "verbose")
|
|
assert args.verbose is True # Test list command with --verbose
|
|
args = parser.parse_args(["l", "test.tzst", "--verbose"])
|
|
assert hasattr(args, "verbose")
|
|
assert args.verbose is True
|
|
|
|
|
|
class TestCLIEdgeCases:
|
|
"""Test CLI edge cases and boundary conditions."""
|
|
|
|
def test_empty_arguments(self):
|
|
"""Test main function with no arguments."""
|
|
result = main([])
|
|
# Should show help and exit with error code
|
|
assert result in [1, 2]
|
|
|
|
def test_help_with_subcommands(self):
|
|
"""Test help display for specific subcommands."""
|
|
# Test that help can be requested for specific commands
|
|
result = main(["a", "--help"])
|
|
assert result == 0
|
|
|
|
result = main(["x", "--help"])
|
|
assert result == 0
|
|
|
|
def test_compression_level_edge_values(self):
|
|
"""Test compression level with edge values."""
|
|
parser = create_parser()
|
|
|
|
# Test minimum valid level
|
|
args = parser.parse_args(["a", "test.tzst", "file.txt", "-l", "1"])
|
|
assert args.compression_level == 1
|
|
|
|
# Test maximum valid level
|
|
args = parser.parse_args(["a", "test.tzst", "file.txt", "--level", "22"])
|
|
assert args.compression_level == 22
|
|
|
|
def test_filter_options_all_values(self):
|
|
"""Test all valid filter option values."""
|
|
parser = create_parser()
|
|
|
|
for filter_val in ["data", "tar", "fully_trusted"]:
|
|
args = parser.parse_args(["x", "test.tzst", "--filter", filter_val])
|
|
assert args.filter == filter_val
|
|
|
|
def test_output_directory_parsing(self):
|
|
"""Test output directory parsing for extract commands."""
|
|
parser = create_parser()
|
|
|
|
# Test extract with output directory
|
|
args = parser.parse_args(["x", "test.tzst", "-o", "/tmp/output"])
|
|
assert args.output == "/tmp/output"
|
|
|
|
# Test extract-flat with output directory
|
|
args = parser.parse_args(["e", "test.tzst", "--output", "/tmp/output"])
|
|
assert args.output == "/tmp/output"
|
|
|
|
|
|
class TestCLIFileOperations:
|
|
"""Test CLI file operation error scenarios."""
|
|
|
|
def test_test_command_missing_file(self, temp_dir):
|
|
"""Test test command with missing archive file."""
|
|
missing_archive = temp_dir / "missing.tzst"
|
|
result = main(["t", str(missing_archive)])
|
|
assert result == 1
|
|
|
|
def test_list_command_missing_file(self, temp_dir):
|
|
"""Test list command with missing archive file."""
|
|
missing_archive = temp_dir / "missing.tzst"
|
|
result = main(["l", str(missing_archive)])
|
|
assert result == 1
|
|
|
|
def test_extract_command_missing_file(self, temp_dir):
|
|
"""Test extract command with missing archive file."""
|
|
missing_archive = temp_dir / "missing.tzst"
|
|
result = main(["x", str(missing_archive)])
|
|
assert result == 1
|
|
|
|
|
|
class TestCLIExceptionHandling:
|
|
"""Test CLI exception handling for comprehensive coverage."""
|
|
|
|
def test_oserror_in_validate_files(self, temp_dir, monkeypatch):
|
|
"""Test OSError exception handling in _validate_files function."""
|
|
from pathlib import Path
|
|
|
|
from tzst.cli import _validate_files
|
|
|
|
# Create a path that will trigger OSError when checking existence
|
|
invalid_path = Path(temp_dir / "invalid")
|
|
|
|
# Mock Path.exists to raise OSError at the class level
|
|
def mock_exists(self):
|
|
if str(self).endswith("invalid"):
|
|
raise OSError("Invalid path")
|
|
return True
|
|
|
|
monkeypatch.setattr(Path, "exists", mock_exists)
|
|
|
|
with pytest.raises(OSError):
|
|
_validate_files([invalid_path])
|
|
|
|
def test_file_not_found_error_in_extract(self, temp_dir):
|
|
"""Test FileNotFoundError handling in extract command."""
|
|
missing_archive = temp_dir / "missing.tzst"
|
|
result = main(["x", str(missing_archive)])
|
|
assert result == 1
|
|
|
|
def test_file_not_found_error_in_extract_flat(self, temp_dir):
|
|
"""Test FileNotFoundError handling in extract-flat command."""
|
|
missing_archive = temp_dir / "missing.tzst"
|
|
result = main(["e", str(missing_archive)])
|
|
assert result == 1
|
|
|
|
def test_file_not_found_error_in_list(self, temp_dir):
|
|
"""Test FileNotFoundError handling in list command."""
|
|
missing_archive = temp_dir / "missing.tzst"
|
|
result = main(["l", str(missing_archive)])
|
|
assert result == 1
|
|
|
|
def test_file_not_found_error_in_test(self, temp_dir):
|
|
"""Test FileNotFoundError handling in test command."""
|
|
missing_archive = temp_dir / "missing.tzst"
|
|
result = main(["t", str(missing_archive)])
|
|
assert result == 1
|
|
|
|
def test_tzst_archive_error_in_add(self, temp_dir, monkeypatch):
|
|
"""Test TzstArchiveError handling in add command."""
|
|
from tzst.exceptions import TzstArchiveError
|
|
|
|
# Create a file to add
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("test content")
|
|
|
|
# Mock create_archive to raise TzstArchiveError
|
|
def mock_create_archive(*args, **kwargs):
|
|
raise TzstArchiveError("Mock archive error")
|
|
|
|
monkeypatch.setattr("tzst.cli.create_archive", mock_create_archive)
|
|
|
|
archive_path = temp_dir / "test.tzst"
|
|
result = main(["a", str(archive_path), str(test_file)])
|
|
assert result == 1
|
|
|
|
def test_tzst_archive_error_in_extract(self, sample_files, temp_dir, monkeypatch):
|
|
"""Test TzstArchiveError handling in extract command."""
|
|
from tzst.exceptions import TzstArchiveError
|
|
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock extract_archive to raise TzstArchiveError
|
|
def mock_extract_archive(*args, **kwargs):
|
|
raise TzstArchiveError("Mock extraction error")
|
|
|
|
monkeypatch.setattr("tzst.cli.extract_archive", mock_extract_archive)
|
|
|
|
result = main(["x", str(archive_path)])
|
|
assert result == 1
|
|
|
|
def test_tzst_archive_error_in_list(self, sample_files, temp_dir, monkeypatch):
|
|
"""Test TzstArchiveError handling in list command."""
|
|
from tzst.exceptions import TzstArchiveError
|
|
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock list_archive to raise TzstArchiveError
|
|
def mock_list_archive(*args, **kwargs):
|
|
raise TzstArchiveError("Mock list error")
|
|
|
|
monkeypatch.setattr("tzst.cli.list_archive", mock_list_archive)
|
|
|
|
result = main(["l", str(archive_path)])
|
|
assert result == 1
|
|
|
|
def test_tzst_archive_error_in_test(self, sample_files, temp_dir, monkeypatch):
|
|
"""Test TzstArchiveError handling in test command."""
|
|
from tzst.exceptions import TzstArchiveError
|
|
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock test_archive to raise TzstArchiveError
|
|
def mock_test_archive(*args, **kwargs):
|
|
raise TzstArchiveError("Mock test error")
|
|
|
|
monkeypatch.setattr("tzst.cli.test_archive", mock_test_archive)
|
|
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 1
|
|
|
|
def test_value_error_in_add(self, temp_dir, monkeypatch):
|
|
"""Test ValueError handling in add command."""
|
|
|
|
# Create a file to add
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("test content")
|
|
|
|
# Mock create_archive to raise ValueError
|
|
def mock_create_archive(*args, **kwargs):
|
|
raise ValueError("Invalid compression level")
|
|
|
|
monkeypatch.setattr("tzst.cli.create_archive", mock_create_archive)
|
|
|
|
archive_path = temp_dir / "test.tzst"
|
|
result = main(["a", str(archive_path), str(test_file)])
|
|
assert result == 1
|
|
|
|
def test_oserror_in_add(self, temp_dir, monkeypatch):
|
|
"""Test OSError handling in add command."""
|
|
|
|
# Create a file to add
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("test content")
|
|
|
|
# Mock create_archive to raise OSError
|
|
def mock_create_archive(*args, **kwargs):
|
|
raise OSError("Permission denied")
|
|
|
|
monkeypatch.setattr("tzst.cli.create_archive", mock_create_archive)
|
|
|
|
archive_path = temp_dir / "test.tzst"
|
|
result = main(["a", str(archive_path), str(test_file)])
|
|
assert result == 1
|
|
|
|
def test_keyboard_interrupt_in_add(self, temp_dir, monkeypatch):
|
|
"""Test KeyboardInterrupt handling in add command."""
|
|
|
|
# Create a file to add
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("test content")
|
|
|
|
# Mock create_archive to raise KeyboardInterrupt
|
|
def mock_create_archive(*args, **kwargs):
|
|
raise KeyboardInterrupt()
|
|
|
|
monkeypatch.setattr("tzst.cli.create_archive", mock_create_archive)
|
|
|
|
archive_path = temp_dir / "test.tzst"
|
|
result = main(["a", str(archive_path), str(test_file)])
|
|
assert result == 130 # SIGINT exit code
|
|
|
|
def test_generic_exception_in_add(self, temp_dir, monkeypatch):
|
|
"""Test generic Exception handling in add command."""
|
|
|
|
# Create a file to add
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("test content")
|
|
|
|
# Mock create_archive to raise generic Exception
|
|
def mock_create_archive(*args, **kwargs):
|
|
raise Exception("Unexpected error")
|
|
|
|
monkeypatch.setattr("tzst.cli.create_archive", mock_create_archive)
|
|
|
|
archive_path = temp_dir / "test.tzst"
|
|
result = main(["a", str(archive_path), str(test_file)])
|
|
assert result == 1
|
|
|
|
def test_generic_exception_in_extract(self, sample_files, temp_dir, monkeypatch):
|
|
"""Test generic Exception handling in extract command."""
|
|
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock extract_archive to raise generic Exception
|
|
def mock_extract_archive(*args, **kwargs):
|
|
raise Exception("Unexpected extraction error")
|
|
|
|
monkeypatch.setattr("tzst.cli.extract_archive", mock_extract_archive)
|
|
|
|
result = main(["x", str(archive_path)])
|
|
assert result == 1
|
|
|
|
def test_generic_exception_in_list(self, sample_files, temp_dir, monkeypatch):
|
|
"""Test generic Exception handling in list command."""
|
|
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock list_archive to raise generic Exception
|
|
def mock_list_archive(*args, **kwargs):
|
|
raise Exception("Unexpected list error")
|
|
|
|
monkeypatch.setattr("tzst.cli.list_archive", mock_list_archive)
|
|
|
|
result = main(["l", str(archive_path)])
|
|
assert result == 1
|
|
|
|
def test_generic_exception_in_test(self, sample_files, temp_dir, monkeypatch):
|
|
"""Test generic Exception handling in test command."""
|
|
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock test_archive to raise generic Exception
|
|
def mock_test_archive(*args, **kwargs):
|
|
raise Exception("Unexpected test error")
|
|
|
|
monkeypatch.setattr("tzst.cli.test_archive", mock_test_archive)
|
|
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 1
|
|
|
|
def test_test_archive_failure(self, sample_files, temp_dir, monkeypatch):
|
|
"""Test archive test failure detection."""
|
|
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock test_archive to return False (test failed)
|
|
def mock_test_archive(*args, **kwargs):
|
|
return False
|
|
|
|
monkeypatch.setattr("tzst.cli.test_archive", mock_test_archive)
|
|
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 1
|
|
|
|
|
|
class TestCLIArgumentParsingEdgeCases:
|
|
"""Test advanced argument parsing edge cases."""
|
|
|
|
def test_validation_functions_edge_cases(self):
|
|
"""Test validation functions with edge cases."""
|
|
from tzst.cli import (
|
|
_validate_compression_level_in_argv,
|
|
_validate_filter_in_argv,
|
|
)
|
|
|
|
# Test compression level validation with edge cases
|
|
assert (
|
|
_validate_compression_level_in_argv(["x", "test.tzst", "-l", "1"]) is False
|
|
) # Valid level returns False
|
|
assert (
|
|
_validate_compression_level_in_argv(["x", "test.tzst", "-l", "22"]) is False
|
|
) # Valid level returns False
|
|
assert (
|
|
_validate_compression_level_in_argv(["x", "test.tzst", "-l", "0"]) is True
|
|
) # Invalid level returns True
|
|
assert (
|
|
_validate_compression_level_in_argv(["x", "test.tzst", "-l", "23"]) is True
|
|
) # Invalid level returns True
|
|
assert (
|
|
_validate_compression_level_in_argv(["x", "test.tzst", "-l", "abc"]) is True
|
|
) # Invalid level returns True
|
|
assert (
|
|
_validate_compression_level_in_argv(["x", "test.tzst"]) is False
|
|
) # No compression level returns False
|
|
|
|
# Test filter validation with edge cases
|
|
assert (
|
|
_validate_filter_in_argv(["x", "test.tzst", "--filter", "data"]) is False
|
|
) # Valid filter returns False
|
|
assert (
|
|
_validate_filter_in_argv(["x", "test.tzst", "--filter", "tar"]) is False
|
|
) # Valid filter returns False
|
|
assert (
|
|
_validate_filter_in_argv(["x", "test.tzst", "--filter", "invalid"]) is True
|
|
) # Invalid filter returns True
|
|
assert (
|
|
_validate_filter_in_argv(["x", "test.tzst"]) is False
|
|
) # No filter returns False
|
|
|
|
def test_handle_parsing_errors_edge_cases(self):
|
|
"""Test _handle_parsing_errors with various edge cases."""
|
|
from tzst.cli import _handle_parsing_errors
|
|
|
|
# Test with different exit codes
|
|
e = SystemExit(0)
|
|
result = _handle_parsing_errors(e, ["--help"])
|
|
assert result == 0
|
|
|
|
e = SystemExit(1)
|
|
result = _handle_parsing_errors(e, ["some", "args"])
|
|
assert result == 1
|
|
|
|
e = SystemExit(None)
|
|
result = _handle_parsing_errors(e, [])
|
|
assert result == 1 # Test with compression level error
|
|
e = SystemExit(2)
|
|
result = _handle_parsing_errors(e, ["a", "test.tzst", "file.txt", "-l", "1000"])
|
|
assert result == 1
|
|
|
|
# Test with filter error
|
|
e = SystemExit(2)
|
|
result = _handle_parsing_errors(e, ["x", "test.tzst", "--filter", "badfilter"])
|
|
assert result == 1
|
|
|
|
def test_parse_arguments_edge_cases(self):
|
|
"""Test _parse_arguments with edge cases."""
|
|
from tzst.cli import _parse_arguments, create_parser
|
|
|
|
parser = create_parser()
|
|
|
|
# Test successful parsing
|
|
args, error_code = _parse_arguments(parser, ["a", "test.tzst", "file.txt"])
|
|
assert args is not None
|
|
assert error_code is None
|
|
|
|
# Test parsing error
|
|
args, error_code = _parse_arguments(parser, ["invalid"])
|
|
assert args is None
|
|
assert error_code == 2
|
|
|
|
def test_execute_command_edge_cases(self):
|
|
"""Test _execute_command with edge cases."""
|
|
from tzst.cli import _execute_command, create_parser
|
|
|
|
parser = create_parser()
|
|
|
|
# Test with version flag
|
|
class MockArgsVersion:
|
|
version = True
|
|
|
|
args = MockArgsVersion()
|
|
result = _execute_command(args, parser)
|
|
assert result == 0
|
|
|
|
# Test with no function attribute
|
|
class MockArgsNoFunc:
|
|
version = False
|
|
|
|
args = MockArgsNoFunc()
|
|
result = _execute_command(args, parser)
|
|
assert result == 1
|
|
|
|
|
|
class TestCLIVerboseListingCoverage:
|
|
"""Test verbose listing functionality for coverage."""
|
|
|
|
def test_verbose_listing_with_mixed_content(self, sample_files, temp_dir, capsys):
|
|
"""Test verbose listing functionality with files and directories."""
|
|
# Create archive with mixed content
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Test verbose listing
|
|
result = main(["l", str(archive_path), "-v"])
|
|
assert result == 0
|
|
|
|
# Check that verbose output is displayed
|
|
captured = capsys.readouterr()
|
|
assert "Mode" in captured.out
|
|
assert "Size" in captured.out
|
|
assert "Modified" in captured.out
|
|
assert "Name" in captured.out
|
|
assert "-" * 60 in captured.out
|
|
|
|
def test_verbose_listing_with_mode_information(self, temp_dir, capsys):
|
|
"""Test verbose listing with specific mode information."""
|
|
# Create a test file
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("test content")
|
|
|
|
# Create archive
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), str(test_file)])
|
|
|
|
# Test verbose listing
|
|
result = main(["l", str(archive_path), "-v"])
|
|
assert result == 0
|
|
|
|
# Check that mode information is displayed
|
|
captured = capsys.readouterr()
|
|
assert "test.txt" in captured.out
|
|
# Should have mode information displayed
|
|
|
|
def test_verbose_listing_directory_entries(self, temp_dir, capsys):
|
|
"""Test verbose listing with directory entries."""
|
|
# Create test directory structure
|
|
test_dir = temp_dir / "test_dir"
|
|
test_dir.mkdir()
|
|
test_file = test_dir / "nested.txt"
|
|
test_file.write_text("nested content")
|
|
|
|
# Create archive with directory
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), str(test_dir)])
|
|
|
|
# Test verbose listing
|
|
result = main(["l", str(archive_path), "-v"])
|
|
assert result == 0
|
|
|
|
# Check directory is shown as <DIR>
|
|
captured = capsys.readouterr()
|
|
assert "<DIR>" in captured.out or "test_dir" in captured.out
|
|
|
|
|
|
class TestCLISimpleListingCoverage:
|
|
"""Test simple listing functionality for coverage."""
|
|
|
|
def test_simple_listing_with_summary(self, sample_files, temp_dir, capsys):
|
|
"""Test simple listing with file and directory summary.""" # Create test files and directories
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
|
|
# Create archive
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Test simple listing
|
|
result = main(["l", str(archive_path)])
|
|
assert result == 0
|
|
|
|
# Check that summary is displayed
|
|
captured = capsys.readouterr()
|
|
assert "Total:" in captured.out
|
|
assert "files" in captured.out
|
|
|
|
def test_simple_listing_with_directories(self, temp_dir, capsys):
|
|
"""Test simple listing counting directories."""
|
|
# Create test structure with directories
|
|
test_dir1 = temp_dir / "dir1"
|
|
test_dir1.mkdir()
|
|
test_file1 = test_dir1 / "file1.txt"
|
|
test_file1.write_text("content1")
|
|
|
|
test_dir2 = temp_dir / "dir2"
|
|
test_dir2.mkdir()
|
|
test_file2 = test_dir2 / "file2.txt"
|
|
test_file2.write_text("content2")
|
|
|
|
# Create archive
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), str(test_dir1), str(test_dir2)])
|
|
|
|
# Test simple listing
|
|
result = main(["l", str(archive_path)])
|
|
assert result == 0
|
|
|
|
# Check directory counting in summary
|
|
captured = capsys.readouterr()
|
|
assert "directories" in captured.out
|
|
assert "Total:" in captured.out
|
|
|
|
def test_simple_listing_size_calculation(self, temp_dir, capsys):
|
|
"""Test simple listing size calculation."""
|
|
# Create test files with known content
|
|
test_file1 = temp_dir / "file1.txt"
|
|
test_file1.write_text("A" * 100) # 100 bytes
|
|
test_file2 = temp_dir / "file2.txt"
|
|
test_file2.write_text("B" * 200) # 200 bytes
|
|
|
|
# Create archive
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), str(test_file1), str(test_file2)])
|
|
|
|
# Test simple listing
|
|
result = main(["l", str(archive_path)])
|
|
assert result == 0
|
|
|
|
# Check total size is calculated
|
|
captured = capsys.readouterr()
|
|
assert "Total:" in captured.out
|
|
# Should show total size of files
|
|
|
|
|
|
class TestCLIExtractFlatExceptionHandling:
|
|
"""Test extract-flat command exception handling for coverage."""
|
|
|
|
def test_extract_flat_file_not_found_error(self, temp_dir):
|
|
"""Test extract-flat command with missing archive file."""
|
|
missing_archive = temp_dir / "missing.tzst"
|
|
result = main(["e", str(missing_archive)])
|
|
assert result == 1
|
|
|
|
def test_extract_flat_tzst_decompression_error(
|
|
self, sample_files, temp_dir, monkeypatch
|
|
):
|
|
"""Test extract-flat command with TzstDecompressionError."""
|
|
from tzst.exceptions import TzstDecompressionError
|
|
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock extract_archive to raise TzstDecompressionError
|
|
def mock_extract_archive(*args, **kwargs):
|
|
raise TzstDecompressionError("Mock decompression error")
|
|
|
|
monkeypatch.setattr("tzst.cli.extract_archive", mock_extract_archive)
|
|
|
|
result = main(["e", str(archive_path)])
|
|
assert result == 1
|
|
|
|
def test_extract_flat_tzst_archive_error(self, sample_files, temp_dir, monkeypatch):
|
|
"""Test extract-flat command with TzstArchiveError."""
|
|
from tzst.exceptions import TzstArchiveError
|
|
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock extract_archive to raise TzstArchiveError
|
|
def mock_extract_archive(*args, **kwargs):
|
|
raise TzstArchiveError("Mock archive error")
|
|
|
|
monkeypatch.setattr("tzst.cli.extract_archive", mock_extract_archive)
|
|
|
|
result = main(["e", str(archive_path)])
|
|
assert result == 1
|
|
|
|
def test_extract_flat_generic_exception(self, sample_files, temp_dir, monkeypatch):
|
|
"""Test extract-flat command with generic Exception."""
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock extract_archive to raise generic Exception
|
|
def mock_extract_archive(*args, **kwargs):
|
|
raise Exception("Mock generic error")
|
|
|
|
monkeypatch.setattr("tzst.cli.extract_archive", mock_extract_archive)
|
|
|
|
result = main(["e", str(archive_path)])
|
|
assert result == 1
|
|
|
|
def test_extract_flat_with_filter_option(self, sample_files, temp_dir, capsys):
|
|
"""Test extract-flat command with non-default filter."""
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Extract with tar filter
|
|
output_dir = temp_dir / "extracted"
|
|
result = main(
|
|
["e", str(archive_path), "-o", str(output_dir), "--filter", "tar"]
|
|
)
|
|
assert result == 0
|
|
|
|
# Check that filter is mentioned in output
|
|
captured = capsys.readouterr()
|
|
assert "Using security filter: tar" in captured.out
|
|
|
|
|
|
class TestCLIKeyboardInterruptHandling:
|
|
"""Test KeyboardInterrupt handling in CLI commands for coverage."""
|
|
|
|
def test_keyboard_interrupt_in_extract_full(
|
|
self, sample_files, temp_dir, monkeypatch
|
|
):
|
|
"""Test KeyboardInterrupt handling in extract command."""
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock extract_archive to raise KeyboardInterrupt
|
|
def mock_extract_archive(*args, **kwargs):
|
|
raise KeyboardInterrupt()
|
|
|
|
monkeypatch.setattr("tzst.cli.extract_archive", mock_extract_archive)
|
|
|
|
result = main(["x", str(archive_path)])
|
|
assert result == 130 # SIGINT exit code
|
|
|
|
def test_keyboard_interrupt_in_list(self, sample_files, temp_dir, monkeypatch):
|
|
"""Test KeyboardInterrupt handling in list command."""
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock list_archive to raise KeyboardInterrupt
|
|
def mock_list_archive(*args, **kwargs):
|
|
raise KeyboardInterrupt()
|
|
|
|
monkeypatch.setattr("tzst.cli.list_archive", mock_list_archive)
|
|
|
|
result = main(["l", str(archive_path)])
|
|
assert result == 130
|
|
|
|
def test_keyboard_interrupt_in_test(self, sample_files, temp_dir, monkeypatch):
|
|
"""Test KeyboardInterrupt handling in test command."""
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock test_archive to raise KeyboardInterrupt
|
|
def mock_test_archive(*args, **kwargs):
|
|
raise KeyboardInterrupt()
|
|
|
|
monkeypatch.setattr("tzst.cli.test_archive", mock_test_archive)
|
|
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 130
|
|
|
|
|
|
class TestCLITzstDecompressionErrorHandling:
|
|
"""Test TzstDecompressionError handling in CLI commands for coverage."""
|
|
|
|
def test_tzst_decompression_error_in_extract_full(
|
|
self, sample_files, temp_dir, monkeypatch
|
|
):
|
|
"""Test TzstDecompressionError handling in extract command."""
|
|
from tzst.exceptions import TzstDecompressionError
|
|
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock extract_archive to raise TzstDecompressionError
|
|
def mock_extract_archive(*args, **kwargs):
|
|
raise TzstDecompressionError("Mock decompression error")
|
|
|
|
monkeypatch.setattr("tzst.cli.extract_archive", mock_extract_archive)
|
|
|
|
result = main(["x", str(archive_path)])
|
|
assert result == 1
|
|
|
|
def test_tzst_decompression_error_in_list(
|
|
self, sample_files, temp_dir, monkeypatch
|
|
):
|
|
"""Test TzstDecompressionError handling in list command."""
|
|
from tzst.exceptions import TzstDecompressionError
|
|
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock list_archive to raise TzstDecompressionError
|
|
def mock_list_archive(*args, **kwargs):
|
|
raise TzstDecompressionError("Mock decompression error")
|
|
|
|
monkeypatch.setattr("tzst.cli.list_archive", mock_list_archive)
|
|
|
|
result = main(["l", str(archive_path)])
|
|
assert result == 1
|
|
|
|
def test_tzst_decompression_error_in_test(
|
|
self, sample_files, temp_dir, monkeypatch
|
|
):
|
|
"""Test TzstDecompressionError handling in test command."""
|
|
from tzst.exceptions import TzstDecompressionError
|
|
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock test_archive to raise TzstDecompressionError
|
|
def mock_test_archive(*args, **kwargs):
|
|
raise TzstDecompressionError("Mock decompression error")
|
|
|
|
monkeypatch.setattr("tzst.cli.test_archive", mock_test_archive)
|
|
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 1
|
|
|
|
|
|
class TestCLIAdvancedExceptionScenarios:
|
|
"""Test additional exception scenarios for comprehensive coverage."""
|
|
|
|
def test_generic_exception_in_test_command(
|
|
self, sample_files, temp_dir, monkeypatch
|
|
):
|
|
"""Test generic Exception handling in test command."""
|
|
# Create a valid archive first
|
|
file_paths = [str(f) for f in sample_files if f.is_file()]
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), *file_paths])
|
|
|
|
# Mock test_archive to raise generic Exception
|
|
def mock_test_archive(*args, **kwargs):
|
|
raise Exception("Unexpected test error")
|
|
|
|
monkeypatch.setattr("tzst.cli.test_archive", mock_test_archive)
|
|
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 1
|
|
|
|
def test_list_command_archive_not_found_path_check(self, temp_dir):
|
|
"""Test list command path existence check."""
|
|
missing_archive = temp_dir / "definitely_missing.tzst"
|
|
# Ensure the file definitely doesn't exist
|
|
assert not missing_archive.exists()
|
|
|
|
result = main(["l", str(missing_archive)])
|
|
assert result == 1
|
|
|
|
def test_test_command_archive_not_found_path_check(self, temp_dir):
|
|
"""Test test command path existence check."""
|
|
missing_archive = temp_dir / "definitely_missing.tzst"
|
|
# Ensure the file definitely doesn't exist
|
|
assert not missing_archive.exists()
|
|
|
|
result = main(["t", str(missing_archive)])
|
|
assert result == 1
|
|
|
|
def test_extract_full_archive_not_found_path_check(self, temp_dir):
|
|
"""Test extract command path existence check."""
|
|
missing_archive = temp_dir / "definitely_missing.tzst"
|
|
# Ensure the file definitely doesn't exist
|
|
assert not missing_archive.exists()
|
|
|
|
result = main(["x", str(missing_archive)])
|
|
assert result == 1
|
|
|
|
def test_extract_flat_archive_not_found_path_check(self, temp_dir):
|
|
"""Test extract-flat command path existence check."""
|
|
missing_archive = temp_dir / "definitely_missing.tzst"
|
|
# Ensure the file definitely doesn't exist
|
|
assert not missing_archive.exists()
|
|
|
|
result = main(["e", str(missing_archive)])
|
|
assert result == 1
|
|
|
|
|
|
class TestCLIListingFunctionsCoverage:
|
|
"""Test edge cases in listing functions for coverage."""
|
|
|
|
def test_verbose_listing_with_missing_mode(self, temp_dir, capsys, monkeypatch):
|
|
"""Test verbose listing when mode information is missing."""
|
|
# Create a test file
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("test content")
|
|
|
|
# Create archive
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), str(test_file)])
|
|
|
|
# Mock list_archive to return items without mode
|
|
def mock_list_archive(*args, **kwargs):
|
|
return [
|
|
{
|
|
"name": "test.txt",
|
|
"size": 12,
|
|
"is_file": True,
|
|
"is_dir": False,
|
|
# Missing mode field
|
|
}
|
|
]
|
|
|
|
monkeypatch.setattr("tzst.cli.list_archive", mock_list_archive)
|
|
|
|
# Test verbose listing
|
|
result = main(["l", str(archive_path), "-v"])
|
|
assert result == 0
|
|
|
|
# Check that missing mode is handled (shows "----")
|
|
captured = capsys.readouterr()
|
|
assert "----" in captured.out
|
|
|
|
def test_verbose_listing_with_missing_mtime(self, temp_dir, capsys, monkeypatch):
|
|
"""Test verbose listing when mtime information is missing."""
|
|
# Create a test file
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("test content")
|
|
|
|
# Create archive
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), str(test_file)])
|
|
|
|
# Mock list_archive to return items without mtime_str
|
|
def mock_list_archive(*args, **kwargs):
|
|
return [
|
|
{
|
|
"name": "test.txt",
|
|
"size": 12,
|
|
"is_file": True,
|
|
"is_dir": False,
|
|
"mode": 0o644,
|
|
# Missing mtime_str field
|
|
}
|
|
]
|
|
|
|
monkeypatch.setattr("tzst.cli.list_archive", mock_list_archive)
|
|
|
|
# Test verbose listing
|
|
result = main(["l", str(archive_path), "-v"])
|
|
assert result == 0
|
|
|
|
# Check that listing works without mtime
|
|
captured = capsys.readouterr()
|
|
assert "test.txt" in captured.out
|
|
|
|
def test_simple_listing_mixed_file_types(self, temp_dir, capsys, monkeypatch):
|
|
"""Test simple listing with mixed file types for directory counting."""
|
|
# Create a test file
|
|
test_file = temp_dir / "test.txt"
|
|
test_file.write_text("test content")
|
|
|
|
# Create archive
|
|
archive_path = temp_dir / "test.tzst"
|
|
main(["a", str(archive_path), str(test_file)])
|
|
|
|
# Mock list_archive to return mixed content
|
|
def mock_list_archive(*args, **kwargs):
|
|
return [
|
|
{
|
|
"name": "file1.txt",
|
|
"size": 100,
|
|
"is_file": True,
|
|
"is_dir": False,
|
|
},
|
|
{
|
|
"name": "dir1/",
|
|
"size": 0,
|
|
"is_file": False,
|
|
"is_dir": True,
|
|
},
|
|
{
|
|
"name": "file2.txt",
|
|
"size": 200,
|
|
"is_file": True,
|
|
"is_dir": False,
|
|
},
|
|
{
|
|
"name": "dir2/",
|
|
"size": 0,
|
|
"is_file": False,
|
|
"is_dir": True,
|
|
},
|
|
]
|
|
|
|
monkeypatch.setattr("tzst.cli.list_archive", mock_list_archive)
|
|
|
|
# Test simple listing
|
|
result = main(["l", str(archive_path)])
|
|
assert result == 0
|
|
|
|
# Check that counts are correct
|
|
captured = capsys.readouterr()
|
|
assert "2 files" in captured.out
|
|
assert "2 directories" in captured.out
|
|
assert "300.0 B" in captured.out # Total size of files
|