Files
tzst/tests/cli/test_cli.py
T
xixu-me a4ddd106f8 Add comprehensive CLI test coverage
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.
2025-06-02 07:14:17 +08:00

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