"""Tests for cli.pymake module.""" from __future__ import annotations from unittest.mock import patch import pytest from pyflowx.cli import pymake from pyflowx.conditions import Constants # ---------------------------------------------------------------------- # # maturin_build_cmd # ---------------------------------------------------------------------- # class TestMaturinBuildCmd: """Test maturin_build_cmd function.""" def test_returns_list(self) -> None: """Should return a list.""" cmd = pymake.maturin_build_cmd() assert isinstance(cmd, list) def test_contains_maturin_build(self) -> None: """Should contain 'maturin' and 'build'.""" cmd = pymake.maturin_build_cmd() assert "maturin" in cmd assert "build" in cmd def test_contains_release_flag(self) -> None: """Should contain release flag '-r'.""" cmd = pymake.maturin_build_cmd() assert "-r" in cmd def test_windows_includes_target(self) -> None: """On Windows, should include target-specific flags.""" cmd = pymake.maturin_build_cmd() if Constants.IS_WINDOWS: assert "--target" in cmd assert "x86_64-win7-windows-msvc" in cmd assert "-Zbuild-std" in cmd assert "-i" in cmd assert "python3.8" in cmd else: # On non-Windows, should not include Windows-specific flags assert "--target" not in cmd def test_does_not_mutate_on_multiple_calls(self) -> None: """Multiple calls should return independent lists.""" cmd1 = pymake.maturin_build_cmd() cmd2 = pymake.maturin_build_cmd() assert cmd1 == cmd2 # Mutating one should not affect the other cmd1.append("extra") assert "extra" not in cmd2 def test_non_windows_excludes_target_flags(self) -> None: """On non-Windows, should not include Windows-specific flags (覆盖 22->32 分支).""" from unittest.mock import patch with patch.object(pymake.Constants, "IS_WINDOWS", False): cmd = pymake.maturin_build_cmd() assert "maturin" in cmd assert "build" in cmd assert "-r" in cmd assert "--target" not in cmd assert "-Zbuild-std" not in cmd # ---------------------------------------------------------------------- # # check helper # ---------------------------------------------------------------------- # class TestCheckHelper: """Test check helper function.""" def test_check_returns_condition(self) -> None: """check() should return a Condition callable.""" cond = pymake.check("python") assert callable(cond) def test_check_uses_has_installed(self) -> None: """check() should use BuiltinConditions.HAS_INSTALLED.""" cond = pymake.check("python") # The condition should be a callable that returns a bool result = cond() assert isinstance(result, bool) def test_check_for_nonexistent_app(self) -> None: """check() for a nonexistent app should return False.""" cond = pymake.check("definitely_not_installed_app_xyz") assert cond() is False def test_check_for_python(self) -> None: """check() for python should return True (python is always available).""" cond = pymake.check("python") # On some systems, 'python' might not be in PATH, but 'python3' might be # Just verify it returns a bool assert isinstance(cond(), bool) # ---------------------------------------------------------------------- # # TaskSpec definitions # ---------------------------------------------------------------------- # class TestTaskSpecDefinitions: """Test that all TaskSpec definitions are valid.""" def test_uv_build_spec(self) -> None: """uv_build spec should be properly defined.""" assert pymake.uv_build.name == "uv_build" assert pymake.uv_build.cmd == ["uv", "build"] assert len(pymake.uv_build.conditions) == 1 def test_maturin_build_spec(self) -> None: """maturin_build spec should be properly defined.""" assert pymake.maturin_build.name == "maturin_build" assert isinstance(pymake.maturin_build.cmd, list) assert len(pymake.maturin_build.conditions) == 1 def test_uv_sync_spec(self) -> None: """uv_sync spec should be properly defined.""" assert pymake.uv_sync.name == "uv_sync" assert pymake.uv_sync.cmd == ["uv", "sync"] def test_git_clean_spec(self) -> None: """git_clean spec should be properly defined.""" assert pymake.git_clean.name == "git_clean" assert pymake.git_clean.cmd == ["gitt", "c"] def test_test_spec(self) -> None: """test spec should be properly defined.""" assert pymake.test.name == "test" assert isinstance(pymake.test.cmd, list) assert "pytest" in pymake.test.cmd assert "-m" in pymake.test.cmd assert "not slow" in pymake.test.cmd def test_test_fast_spec(self) -> None: """test_fast spec should be properly defined.""" assert pymake.test_fast.name == "test_fast" assert isinstance(pymake.test_fast.cmd, list) assert "pytest" in pymake.test_fast.cmd assert "-n" not in pymake.test_fast.cmd # test_fast doesn't use parallel def test_test_coverage_spec(self) -> None: """test_coverage spec should be properly defined.""" assert pymake.test_coverage.name == "test_coverage" assert isinstance(pymake.test_coverage.cmd, list) assert "pytest" in pymake.test_coverage.cmd assert "--cov" in pymake.test_coverage.cmd def test_ruff_lint_spec(self) -> None: """ruff_lint spec should be properly defined.""" assert pymake.ruff_lint.name == "lint" assert isinstance(pymake.ruff_lint.cmd, list) assert "ruff" in pymake.ruff_lint.cmd assert "check" in pymake.ruff_lint.cmd def test_mypy_check_spec(self) -> None: """mypy_check spec should be properly defined.""" assert pymake.mypy_check.name == "typecheck" assert pymake.mypy_check.cmd == ["mypy", "."] def test_ty_check_spec(self) -> None: """ty_check spec should be properly defined.""" assert pymake.ty_check.name == "ty_check" assert pymake.ty_check.cmd == ["ty", "check", "."] def test_doc_spec(self) -> None: """doc spec should be properly defined.""" assert pymake.doc.name == "doc" assert isinstance(pymake.doc.cmd, list) assert "sphinx-build" in pymake.doc.cmd def test_hatch_publish_spec(self) -> None: """hatch_publish spec should be properly defined.""" assert pymake.hatch_publish.name == "publish_python" assert pymake.hatch_publish.cmd == ["hatch", "publish"] def test_twine_publish_spec(self) -> None: """twine_publish spec should be properly defined.""" assert pymake.twine_publish.name == "twine_publish" assert isinstance(pymake.twine_publish.cmd, list) assert "twine" in pymake.twine_publish.cmd assert "upload" in pymake.twine_publish.cmd def test_tox_spec(self) -> None: """tox spec should be properly defined.""" assert pymake.tox.name == "tox" assert pymake.tox.cmd == ["tox", "-p", "auto"] # ---------------------------------------------------------------------- # # main function # ---------------------------------------------------------------------- # class TestMain: """Test main function.""" def test_main_calls_run_cli(self) -> None: """main() should create a CliRunner and call run_cli().""" with pytest.raises(SystemExit) as exc_info: pymake.main() # run_cli() calls sys.exit(), so we should get SystemExit # The exit code depends on whether any commands are available assert exc_info.value.code in (0, 1, 2) def test_main_with_list_argument(self) -> None: """main() should handle --list argument.""" with patch("sys.argv", ["pymake", "--list"]), pytest.raises(SystemExit) as exc_info: pymake.main() assert exc_info.value.code == 0 def test_main_creates_runner_with_multiple_commands(self) -> None: """main() should create a CliRunner with multiple commands.""" # We can't easily test the runner creation without mocking, # but we can verify that main() doesn't raise an error for --list with patch("sys.argv", ["pymake", "--list"]), pytest.raises(SystemExit): pymake.main() def test_main_with_no_args_shows_help(self) -> None: """main() with no args should show help and exit with failure.""" with patch("sys.argv", ["pymake"]), pytest.raises(SystemExit) as exc_info: pymake.main() assert exc_info.value.code == 1