Updated test cases in `test_cli.py` and `test_platform_specific.py` to use argument unpacking (`*files`) for file lists passed to the `main` function. This improves readability and consistency across test cases.
386 lines
15 KiB
Python
386 lines
15 KiB
Python
"""Platform-specific and cross-platform compatibility tests."""
|
|
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
from tzst.cli import main
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Unix-specific features")
|
|
class TestUnixSpecificFeatures:
|
|
"""Test Unix/Linux specific features like symlinks and permissions."""
|
|
|
|
def test_symbolic_links_preservation(self, temp_dir):
|
|
"""Test that symbolic links are preserved in archives."""
|
|
# Create target file and symlink
|
|
target_file = temp_dir / "target.txt"
|
|
target_file.write_text("Symlink target content")
|
|
|
|
symlink_file = temp_dir / "symlink.txt"
|
|
try:
|
|
symlink_file.symlink_to(target_file)
|
|
except OSError:
|
|
pytest.skip("Symlinks not supported on this system")
|
|
|
|
# Create archive with symlink
|
|
archive_path = temp_dir / "symlinks.tzst"
|
|
result = main(["a", str(archive_path), str(target_file), str(symlink_file)])
|
|
assert result == 0
|
|
|
|
# Extract and verify symlink is preserved
|
|
extract_dir = temp_dir / "symlink_extracted"
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir)])
|
|
assert result == 0
|
|
|
|
extracted_symlink = extract_dir / "symlink.txt"
|
|
if extracted_symlink.exists():
|
|
# Verify it's still a symlink
|
|
assert extracted_symlink.is_symlink()
|
|
|
|
def test_file_permissions_preservation(self, temp_dir):
|
|
"""Test that file permissions are preserved."""
|
|
# Create file with specific permissions
|
|
perm_file = temp_dir / "permissions.txt"
|
|
perm_file.write_text("Permission test content")
|
|
|
|
# Set specific permissions (readable/writable by owner only)
|
|
try:
|
|
perm_file.chmod(0o600)
|
|
except OSError:
|
|
pytest.skip("Permission setting not supported")
|
|
|
|
original_mode = perm_file.stat().st_mode
|
|
|
|
# Create archive
|
|
archive_path = temp_dir / "permissions.tzst"
|
|
result = main(["a", str(archive_path), str(perm_file)])
|
|
assert result == 0
|
|
|
|
# Extract and verify permissions
|
|
extract_dir = temp_dir / "perm_extracted"
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir)])
|
|
assert result == 0
|
|
|
|
extracted_file = extract_dir / "permissions.txt"
|
|
if extracted_file.exists():
|
|
# Permissions should be preserved (may vary by system)
|
|
extracted_mode = extracted_file.stat().st_mode
|
|
# At minimum, check that it's not world-writable if original wasn't
|
|
if not (original_mode & 0o002):
|
|
assert not (extracted_mode & 0o002)
|
|
|
|
def test_executable_files(self, temp_dir):
|
|
"""Test handling of executable files."""
|
|
# Create executable script
|
|
script_file = temp_dir / "test_script.sh"
|
|
script_file.write_text("#!/bin/bash\necho 'Hello World'\n")
|
|
|
|
try:
|
|
script_file.chmod(0o755) # Make executable
|
|
except OSError:
|
|
pytest.skip("Permission setting not supported")
|
|
|
|
# Create archive
|
|
archive_path = temp_dir / "executable.tzst"
|
|
result = main(["a", str(archive_path), str(script_file)])
|
|
assert result == 0
|
|
|
|
# Extract and verify executable bit preserved
|
|
extract_dir = temp_dir / "exec_extracted"
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir)])
|
|
assert result == 0
|
|
|
|
extracted_script = extract_dir / "test_script.sh"
|
|
if extracted_script.exists():
|
|
# Check if executable bit is preserved
|
|
mode = extracted_script.stat().st_mode
|
|
assert mode & 0o100 # Owner execute bit
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific tests")
|
|
class TestWindowsSpecificFeatures:
|
|
"""Test Windows specific behaviors and limitations."""
|
|
|
|
def test_windows_reserved_names(self, temp_dir):
|
|
"""Test handling of Windows reserved names."""
|
|
# Windows reserved names that should be handled gracefully
|
|
reserved_names = ["CON.txt", "PRN.txt", "AUX.txt", "NUL.txt"]
|
|
|
|
files_created = []
|
|
for name in reserved_names:
|
|
try:
|
|
reserved_file = temp_dir / name
|
|
reserved_file.write_text(f"Content for {name}")
|
|
files_created.append(str(reserved_file))
|
|
except OSError:
|
|
# Expected on Windows - these names are reserved
|
|
continue
|
|
|
|
if files_created:
|
|
archive_path = temp_dir / "reserved_names.tzst"
|
|
result = main(["a", str(archive_path), *files_created])
|
|
# Should either succeed or fail gracefully
|
|
assert result in [0, 1]
|
|
|
|
def test_windows_long_path_handling(self, temp_dir):
|
|
"""Test handling of Windows long paths."""
|
|
# Create deeply nested path (Windows has 260 char limit traditionally)
|
|
deep_path = temp_dir
|
|
for i in range(10):
|
|
deep_path = deep_path / f"very_long_directory_name_{i}"
|
|
|
|
try:
|
|
deep_path.mkdir(parents=True)
|
|
deep_file = deep_path / "deep_file.txt"
|
|
deep_file.write_text("Deep path content")
|
|
|
|
archive_path = temp_dir / "long_path.tzst"
|
|
result = main(["a", str(archive_path), str(deep_file)])
|
|
# Should handle long paths gracefully
|
|
assert result in [0, 1]
|
|
except OSError:
|
|
# Skip if system doesn't support such deep paths
|
|
pytest.skip("System doesn't support deep paths")
|
|
|
|
|
|
class TestUnicodeAndInternational:
|
|
"""Test Unicode and international character handling."""
|
|
|
|
def test_unicode_content_various_encodings(self, temp_dir):
|
|
"""Test files with various Unicode content."""
|
|
# Test various Unicode characters
|
|
unicode_tests = [
|
|
("chinese.txt", "你好世界 Hello World"),
|
|
("emoji.txt", "🌟⭐✨🎉🚀💖"),
|
|
("cyrillic.txt", "Привет мир"),
|
|
("arabic.txt", "مرحبا بالعالم"),
|
|
("japanese.txt", "こんにちは世界"),
|
|
]
|
|
|
|
files_created = []
|
|
for filename, content in unicode_tests:
|
|
try:
|
|
unicode_file = temp_dir / filename
|
|
unicode_file.write_text(content, encoding="utf-8")
|
|
files_created.append(str(unicode_file))
|
|
except (OSError, UnicodeError):
|
|
# Skip files that can't be created on this system
|
|
continue
|
|
|
|
if files_created:
|
|
archive_path = temp_dir / "unicode_content.tzst"
|
|
result = main(["a", str(archive_path), *files_created])
|
|
assert result == 0
|
|
|
|
# Extract and verify content preservation
|
|
extract_dir = temp_dir / "unicode_extracted"
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir)])
|
|
assert result == 0
|
|
|
|
# Verify at least one file's content
|
|
for filename, expected_content in unicode_tests:
|
|
extracted_file = extract_dir / filename
|
|
if extracted_file.exists():
|
|
actual_content = extracted_file.read_text(encoding="utf-8")
|
|
assert actual_content == expected_content
|
|
|
|
def test_unicode_filenames_platform_dependent(self, temp_dir):
|
|
"""Test Unicode filenames with platform-aware expectations."""
|
|
unicode_filenames = [
|
|
"测试文件.txt",
|
|
"файл_тест.txt",
|
|
"αρχείο_δοκιμή.txt",
|
|
"ファイル_テスト.txt",
|
|
]
|
|
|
|
files_created = []
|
|
for filename in unicode_filenames:
|
|
try:
|
|
unicode_file = temp_dir / filename
|
|
unicode_file.write_text(f"Content of {filename}", encoding="utf-8")
|
|
files_created.append(str(unicode_file))
|
|
except (OSError, UnicodeError):
|
|
# Skip files that can't be created
|
|
continue
|
|
|
|
if files_created:
|
|
archive_path = temp_dir / "unicode_filenames.tzst"
|
|
result = main(["a", str(archive_path), *files_created])
|
|
|
|
# On Windows PowerShell, this might fail due to encoding issues
|
|
if sys.platform == "win32":
|
|
assert result in [0, 1] # May fail on Windows
|
|
else:
|
|
assert result == 0 # Should succeed on Unix
|
|
|
|
|
|
class TestPerformanceAndScalability:
|
|
"""Test performance-related scenarios and scalability."""
|
|
|
|
def test_many_small_files_archive(self, temp_dir):
|
|
"""Test archiving many small files efficiently.""" # Create 100 small files
|
|
files = []
|
|
for i in range(100):
|
|
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_small.tzst"
|
|
result = main(["a", str(archive_path), *files])
|
|
assert result == 0
|
|
|
|
# Verify archive integrity
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 0
|
|
|
|
def test_large_single_file_archive(self, temp_dir):
|
|
"""Test archiving a single large file."""
|
|
# Create 5MB file
|
|
large_file = temp_dir / "large.txt"
|
|
content = "This is large file content.\n" * 200000 # ~5MB
|
|
large_file.write_text(content)
|
|
|
|
archive_path = temp_dir / "large_single.tzst"
|
|
result = main(["a", str(archive_path), str(large_file), "-l", "22"])
|
|
assert result == 0
|
|
|
|
# Verify archive can be tested
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 0
|
|
|
|
def test_compression_efficiency_levels(self, temp_dir):
|
|
"""Test different compression levels for efficiency."""
|
|
# Create test file with repetitive content (compresses well)
|
|
test_file = temp_dir / "compressible.txt"
|
|
repetitive_content = "ABCD" * 10000 # 40KB of repetitive data
|
|
test_file.write_text(repetitive_content)
|
|
|
|
# Test different compression levels
|
|
for level in [1, 3, 10, 22]:
|
|
archive_path = temp_dir / f"compression_level_{level}.tzst"
|
|
result = main(["a", str(archive_path), str(test_file), "-l", str(level)])
|
|
assert result == 0
|
|
|
|
# Higher compression should generally result in smaller files
|
|
# (though not guaranteed for all content types)
|
|
assert archive_path.exists()
|
|
assert archive_path.stat().st_size > 0
|
|
|
|
|
|
class TestErrorRecoveryAndRobustness:
|
|
"""Test error recovery and robustness scenarios."""
|
|
|
|
def test_disk_space_simulation(self, temp_dir):
|
|
"""Test behavior when disk space might be limited."""
|
|
# Create moderately large file
|
|
large_file = temp_dir / "disk_space_test.txt"
|
|
content = "Large content for disk space test.\n" * 50000
|
|
large_file.write_text(content)
|
|
|
|
# Try to create archive with maximum compression
|
|
archive_path = temp_dir / "disk_space.tzst"
|
|
result = main(["a", str(archive_path), str(large_file), "-l", "22"])
|
|
# Should either succeed or fail gracefully
|
|
assert result in [0, 1]
|
|
|
|
def test_interrupted_operation_simulation(self, temp_dir):
|
|
"""Test that partial operations don't leave corrupted files."""
|
|
# Create test file
|
|
test_file = temp_dir / "interrupt_test.txt"
|
|
test_file.write_text("Interrupt test content")
|
|
|
|
# Create archive successfully first
|
|
archive_path = temp_dir / "interrupt_test.tzst"
|
|
result = main(["a", str(archive_path), str(test_file)])
|
|
assert result == 0
|
|
|
|
# Verify archive integrity
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 0
|
|
|
|
# If archive exists, it should be valid
|
|
if archive_path.exists():
|
|
result = main(["l", str(archive_path)])
|
|
assert result == 0
|
|
|
|
def test_readonly_archive_handling(self, temp_dir):
|
|
"""Test behavior with read-only archives."""
|
|
# Create archive first
|
|
test_file = temp_dir / "readonly_test.txt"
|
|
test_file.write_text("Read-only test content")
|
|
|
|
archive_path = temp_dir / "readonly.tzst"
|
|
result = main(["a", str(archive_path), str(test_file)])
|
|
assert result == 0
|
|
|
|
# Make archive read-only
|
|
try:
|
|
archive_path.chmod(0o444)
|
|
except OSError:
|
|
pytest.skip("Cannot set read-only permissions")
|
|
|
|
# Should still be able to read the archive
|
|
result = main(["l", str(archive_path)])
|
|
assert result == 0
|
|
|
|
result = main(["t", str(archive_path)])
|
|
assert result == 0
|
|
|
|
# Extract should work
|
|
extract_dir = temp_dir / "readonly_extracted"
|
|
result = main(["x", str(archive_path), "-o", str(extract_dir)])
|
|
assert result == 0
|
|
|
|
|
|
class TestCommandLineInterfaceEdgeCases:
|
|
"""Test edge cases in command line interface handling."""
|
|
|
|
def test_empty_argument_handling(self, temp_dir):
|
|
"""Test behavior with empty or unusual arguments."""
|
|
# Test with empty string as filename (treated as current directory)
|
|
archive_path = temp_dir / "test.tzst"
|
|
result = main(["a", str(archive_path), ""])
|
|
assert result == 0 # Empty string is treated as current directory
|
|
|
|
# Test with very long argument
|
|
long_arg = "a" * 1000
|
|
result = main(["a", "test.tzst", long_arg])
|
|
assert result == 1 # Should fail gracefully
|
|
|
|
def test_special_characters_in_archive_names(self, temp_dir):
|
|
"""Test archive names with special characters."""
|
|
test_file = temp_dir / "special_arch_test.txt"
|
|
test_file.write_text("Special archive name test")
|
|
|
|
# Test various special characters in archive names
|
|
special_names = [
|
|
"archive with spaces.tzst",
|
|
"archive-with-dashes.tzst",
|
|
"archive_with_underscores.tzst",
|
|
"archive.with.dots.tzst",
|
|
]
|
|
|
|
for name in special_names:
|
|
archive_path = temp_dir / name
|
|
result = main(["a", str(archive_path), str(test_file)])
|
|
assert result == 0
|
|
assert archive_path.exists()
|
|
|
|
def test_output_directory_creation(self, temp_dir):
|
|
"""Test that output directories are created when they don't exist."""
|
|
# Create test archive first
|
|
test_file = temp_dir / "output_test.txt"
|
|
test_file.write_text("Output directory test")
|
|
|
|
archive_path = temp_dir / "output.tzst"
|
|
result = main(["a", str(archive_path), str(test_file)])
|
|
assert result == 0
|
|
|
|
# Extract to non-existent directory
|
|
nonexistent_dir = temp_dir / "does" / "not" / "exist"
|
|
result = main(["x", str(archive_path), "-o", str(nonexistent_dir)])
|
|
assert result == 0
|
|
assert nonexistent_dir.exists()
|
|
assert (nonexistent_dir / "output_test.txt").exists()
|