50575c6e91
1. 统一格式化多个文件的字典/列表缩进样式 2. 为pymake的bump命令新增typecheck、ruff_lint、ruff_format检查步骤 3. 扩充test_packtool.py的嵌入式Python安装测试用例
309 lines
12 KiB
Python
309 lines
12 KiB
Python
"""Tests for cli.packtool module."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pyflowx as px
|
|
from pyflowx.cli import packtool
|
|
|
|
|
|
# ---------------------------------------------------------------------- #
|
|
# pack_source
|
|
# ---------------------------------------------------------------------- #
|
|
class TestPackSource:
|
|
"""Test pack_source function."""
|
|
|
|
def test_pack_source_basic(self, tmp_path: Path) -> None:
|
|
"""Should pack source code."""
|
|
project_dir = tmp_path / "project"
|
|
project_dir.mkdir()
|
|
(project_dir / "main.py").write_text("print('hello')")
|
|
output_dir = tmp_path / "output"
|
|
|
|
packtool.pack_source(project_dir, output_dir)
|
|
assert output_dir.exists()
|
|
|
|
def test_pack_source_with_pyproject(self, tmp_path: Path) -> None:
|
|
"""Should pack source with pyproject.toml."""
|
|
project_dir = tmp_path / "project"
|
|
project_dir.mkdir()
|
|
(project_dir / "pyproject.toml").write_text("[project]\nname = 'test'")
|
|
(project_dir / "main.py").write_text("print('hello')")
|
|
output_dir = tmp_path / "output"
|
|
|
|
packtool.pack_source(project_dir, output_dir)
|
|
assert output_dir.exists()
|
|
|
|
|
|
# ---------------------------------------------------------------------- #
|
|
# pack_dependencies
|
|
# ---------------------------------------------------------------------- #
|
|
class TestPackDependencies:
|
|
"""Test pack_dependencies function."""
|
|
|
|
def test_pack_dependencies_empty(self, tmp_path: Path) -> None:
|
|
"""Should handle empty dependencies."""
|
|
lib_dir = tmp_path / "libs"
|
|
|
|
packtool.pack_dependencies(lib_dir, [])
|
|
# Should print message and return
|
|
|
|
def test_pack_dependencies_with_deps(self, tmp_path: Path) -> None:
|
|
"""Should pack dependencies."""
|
|
lib_dir = tmp_path / "libs"
|
|
|
|
with patch("subprocess.run") as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
packtool.pack_dependencies(lib_dir, ["numpy", "pandas"])
|
|
assert mock_run.called
|
|
|
|
|
|
# ---------------------------------------------------------------------- #
|
|
# pack_wheel
|
|
# ---------------------------------------------------------------------- #
|
|
class TestPackWheel:
|
|
"""Test pack_wheel function."""
|
|
|
|
def test_pack_wheel(self, tmp_path: Path) -> None:
|
|
"""Should pack wheel."""
|
|
project_dir = tmp_path / "project"
|
|
project_dir.mkdir()
|
|
(project_dir / "pyproject.toml").write_text("[project]\nname = 'test'")
|
|
output_dir = tmp_path / "dist"
|
|
|
|
with patch("subprocess.run") as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
packtool.pack_wheel(project_dir, output_dir)
|
|
assert mock_run.called
|
|
|
|
|
|
# ---------------------------------------------------------------------- #
|
|
# install_embed_python
|
|
# ---------------------------------------------------------------------- #
|
|
class TestInstallEmbedPython:
|
|
"""Test install_embed_python function."""
|
|
|
|
def test_install_embed_python_basic(self, tmp_path: Path) -> None:
|
|
"""Should install embedded Python (mocked for speed)."""
|
|
output_dir = tmp_path / "python"
|
|
|
|
# Create a mock cache file that doesn't exist (force download)
|
|
with patch("urllib.request.urlretrieve") as mock_urlretrieve, \
|
|
patch("zipfile.ZipFile") as mock_zipfile:
|
|
|
|
# Mock successful download
|
|
mock_urlretrieve.return_value = None
|
|
mock_zip_instance = MagicMock()
|
|
mock_zipfile.return_value.__enter__.return_value = mock_zip_instance
|
|
|
|
# Ensure cache doesn't exist by using tmp_path as cache dir
|
|
with patch.object(packtool, 'DEFAULT_CACHE_DIR', str(tmp_path / ".cache")):
|
|
packtool.install_embed_python("3.10", output_dir)
|
|
|
|
# Verify download was called
|
|
assert mock_urlretrieve.called
|
|
# Verify extraction was called
|
|
assert mock_zip_instance.extractall.called
|
|
# Verify output directory was created
|
|
assert output_dir.exists()
|
|
|
|
def test_install_embed_python_with_cache(self, tmp_path: Path) -> None:
|
|
"""Should use cached Python if available."""
|
|
output_dir = tmp_path / "python"
|
|
cache_dir = tmp_path / ".cache" / "pypack"
|
|
cache_dir.mkdir(parents=True)
|
|
|
|
# Create a fake cached zip file
|
|
cache_file = cache_dir / "python-3.10.11-embed-amd64.zip"
|
|
cache_file.write_bytes(b"PK\x03\x04" + b"\x00" * 100) # Minimal ZIP header
|
|
|
|
with patch("zipfile.ZipFile") as mock_zipfile:
|
|
mock_zip_instance = MagicMock()
|
|
mock_zipfile.return_value.__enter__.return_value = mock_zip_instance
|
|
|
|
packtool.install_embed_python("3.10", output_dir)
|
|
|
|
# Verify extraction was called (using cache)
|
|
assert mock_zip_instance.extractall.called
|
|
# Verify output directory was created
|
|
assert output_dir.exists()
|
|
|
|
def test_install_embed_python_real_download(self, tmp_path: Path) -> None:
|
|
"""Should actually download and extract embedded Python (requires network).
|
|
|
|
This test performs a real download to verify the entire workflow.
|
|
It's marked to run only when network is available.
|
|
"""
|
|
import platform
|
|
import zipfile
|
|
|
|
output_dir = tmp_path / "python_real"
|
|
|
|
# Only run on Windows (embed Python is Windows-specific)
|
|
if platform.system() != "Windows":
|
|
return
|
|
|
|
# Perform real installation
|
|
packtool.install_embed_python("3.10", output_dir)
|
|
|
|
# Verify installation succeeded
|
|
assert output_dir.exists()
|
|
|
|
# Verify key files are present
|
|
expected_files = [
|
|
"python.exe",
|
|
"python310.dll",
|
|
"python310.zip",
|
|
]
|
|
|
|
for expected_file in expected_files:
|
|
file_path = output_dir / expected_file
|
|
assert file_path.exists(), f"Expected file {expected_file} not found"
|
|
assert file_path.stat().st_size > 0, f"File {expected_file} is empty"
|
|
|
|
# Verify python.exe is executable
|
|
python_exe = output_dir / "python.exe"
|
|
assert python_exe.is_file()
|
|
|
|
# Verify the installation is functional
|
|
# Check that we can at least read the zip file
|
|
python_zip = output_dir / "python310.zip"
|
|
assert zipfile.is_zipfile(python_zip)
|
|
|
|
print(f"✅ Successfully downloaded and installed embed Python to {output_dir}")
|
|
print(f" Files: {list(output_dir.iterdir())}")
|
|
|
|
def test_install_embed_python_different_versions(self, tmp_path: Path) -> None:
|
|
"""Should handle different Python versions."""
|
|
output_dir = tmp_path / "python"
|
|
|
|
with patch("urllib.request.urlretrieve") as mock_urlretrieve, patch("zipfile.ZipFile") as mock_zipfile:
|
|
mock_zip_instance = MagicMock()
|
|
mock_zipfile.return_value.__enter__.return_value = mock_zip_instance
|
|
|
|
# Test different versions
|
|
for version in ["3.8", "3.9", "3.10", "3.11", "3.12"]:
|
|
packtool.install_embed_python(version, output_dir)
|
|
assert mock_urlretrieve.called
|
|
|
|
def test_install_embed_python_creates_cache(self, tmp_path: Path) -> None:
|
|
"""Should create cache directory and file."""
|
|
output_dir = tmp_path / "python"
|
|
|
|
with patch("urllib.request.urlretrieve") as mock_urlretrieve, patch("zipfile.ZipFile") as mock_zipfile:
|
|
mock_urlretrieve.return_value = None
|
|
mock_zip_instance = MagicMock()
|
|
mock_zipfile.return_value.__enter__.return_value = mock_zip_instance
|
|
|
|
packtool.install_embed_python("3.10", output_dir)
|
|
|
|
# Verify cache directory was created
|
|
Path(packtool.DEFAULT_CACHE_DIR)
|
|
# Note: In test environment, cache might not persist due to mocking
|
|
|
|
|
|
# ---------------------------------------------------------------------- #
|
|
# create_zip_package
|
|
# ---------------------------------------------------------------------- #
|
|
class TestCreateZipPackage:
|
|
"""Test create_zip_package function."""
|
|
|
|
def test_create_zip_package(self, tmp_path: Path) -> None:
|
|
"""Should create ZIP package."""
|
|
source_dir = tmp_path / "source"
|
|
source_dir.mkdir()
|
|
(source_dir / "test.txt").write_text("test content")
|
|
output_file = tmp_path / "package.zip"
|
|
|
|
packtool.create_zip_package(source_dir, output_file)
|
|
assert output_file.exists()
|
|
|
|
|
|
# ---------------------------------------------------------------------- #
|
|
# clean_build_dir
|
|
# ---------------------------------------------------------------------- #
|
|
class TestCleanBuildDir:
|
|
"""Test clean_build_dir function."""
|
|
|
|
def test_clean_build_dir_exists(self, tmp_path: Path) -> None:
|
|
"""Should clean existing build directory."""
|
|
build_dir = tmp_path / "build"
|
|
build_dir.mkdir()
|
|
(build_dir / "test.txt").write_text("test")
|
|
|
|
packtool.clean_build_dir(build_dir)
|
|
assert not build_dir.exists()
|
|
|
|
def test_clean_build_dir_not_exists(self, tmp_path: Path) -> None:
|
|
"""Should handle nonexistent build directory."""
|
|
build_dir = tmp_path / "nonexistent"
|
|
|
|
packtool.clean_build_dir(build_dir)
|
|
# Should print message
|
|
|
|
|
|
# ---------------------------------------------------------------------- #
|
|
# main function
|
|
# ---------------------------------------------------------------------- #
|
|
class TestMain:
|
|
"""Test main function."""
|
|
|
|
def test_main_src_command(self, tmp_path: Path) -> None:
|
|
"""main() should handle src command."""
|
|
project_dir = tmp_path / "project"
|
|
project_dir.mkdir()
|
|
|
|
with patch("sys.argv", ["packtool", "src", "--project-dir", str(project_dir)]), patch.object(
|
|
px, "run"
|
|
) as mock_run:
|
|
packtool.main()
|
|
assert mock_run.called
|
|
|
|
def test_main_deps_command(self, tmp_path: Path) -> None:
|
|
"""main() should handle deps command."""
|
|
with patch("sys.argv", ["packtool", "deps", "numpy", "pandas"]), patch.object(px, "run") as mock_run:
|
|
packtool.main()
|
|
assert mock_run.called
|
|
|
|
def test_main_wheel_command(self, tmp_path: Path) -> None:
|
|
"""main() should handle wheel command."""
|
|
project_dir = tmp_path / "project"
|
|
project_dir.mkdir()
|
|
|
|
with patch("sys.argv", ["packtool", "wheel", "--project-dir", str(project_dir)]), patch.object(
|
|
px, "run"
|
|
) as mock_run:
|
|
packtool.main()
|
|
assert mock_run.called
|
|
|
|
def test_main_embed_command(self, tmp_path: Path) -> None:
|
|
"""main() should handle embed command."""
|
|
with patch("sys.argv", ["packtool", "embed", "--version", "3.10"]), patch.object(px, "run") as mock_run:
|
|
packtool.main()
|
|
assert mock_run.called
|
|
|
|
def test_main_zip_command(self, tmp_path: Path) -> None:
|
|
"""main() should handle zip command."""
|
|
source_dir = tmp_path / "source"
|
|
source_dir.mkdir()
|
|
|
|
with patch("sys.argv", ["packtool", "zip", "--source-dir", str(source_dir)]), patch.object(
|
|
px, "run"
|
|
) as mock_run:
|
|
packtool.main()
|
|
assert mock_run.called
|
|
|
|
def test_main_clean_command(self) -> None:
|
|
"""main() should handle clean command."""
|
|
with patch("sys.argv", ["packtool", "clean"]), patch.object(px, "run") as mock_run:
|
|
packtool.main()
|
|
assert mock_run.called
|
|
|
|
def test_main_with_no_args_shows_help(self) -> None:
|
|
"""main() with no args should show help."""
|
|
with patch("sys.argv", ["packtool"]):
|
|
packtool.main()
|
|
# Should print help and return
|