refactor: 全面迁移至 Python 3.9+ 原生泛型类型语法
- 将所有 `Optional[T]` 替换为 `T | None` - 将所有 `List[T]`/`Dict[K, V]`/`Tuple[Ts, ...]` 替换为对应原生泛型 - 调整类型导入,移除冗余的 typing 导入项 - 更新项目依赖,添加 typing-extensions 兼容旧版本 Python - 重构部分函数签名与内部实现以匹配新类型语法
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
"""Tests for pymake CLI."""
|
||||
|
||||
from pyflowx.cli.pymake import _build_graphs, _get_maturin_build_command, conf
|
||||
|
||||
|
||||
def test_pymake_config_attributes():
|
||||
"""Test PymakeConfig has expected attributes."""
|
||||
assert hasattr(conf, "PROJECT_ROOT")
|
||||
assert hasattr(conf, "BUILD_TOOL")
|
||||
assert hasattr(conf, "BUILD_COMMAND")
|
||||
assert hasattr(conf, "MATURIN_TOOL")
|
||||
assert hasattr(conf, "MATURIN_BUILD_COMMAND")
|
||||
assert hasattr(conf, "MATURIN_DEV_COMMAND")
|
||||
assert hasattr(conf, "TIMEOUT")
|
||||
|
||||
|
||||
def test_pymake_config_values():
|
||||
"""Test PymakeConfig values are correct."""
|
||||
assert conf.BUILD_TOOL == "uv"
|
||||
assert conf.BUILD_COMMAND == ["uv", "build"]
|
||||
assert conf.MATURIN_TOOL == "maturin"
|
||||
assert conf.TIMEOUT == 600
|
||||
|
||||
|
||||
def test_get_maturin_build_command_basic():
|
||||
"""Test _get_maturin_build_command returns base command."""
|
||||
cmd = _get_maturin_build_command()
|
||||
assert "maturin" in cmd
|
||||
assert "build" in cmd
|
||||
assert "-r" in cmd
|
||||
|
||||
|
||||
def test_build_graphs_returns_dict():
|
||||
"""Test _build_graphs returns a dictionary."""
|
||||
graphs = _build_graphs()
|
||||
assert isinstance(graphs, dict)
|
||||
assert len(graphs) > 0
|
||||
|
||||
|
||||
def test_build_graphs_has_expected_commands():
|
||||
"""Test _build_graphs has expected command keys."""
|
||||
graphs = _build_graphs()
|
||||
expected_commands = [
|
||||
"b",
|
||||
"bc",
|
||||
"ba",
|
||||
"ic",
|
||||
"ip",
|
||||
"ia",
|
||||
"cp",
|
||||
"cc",
|
||||
"ca",
|
||||
"t",
|
||||
"lint",
|
||||
]
|
||||
for cmd in expected_commands:
|
||||
assert cmd in graphs, f"Expected command '{cmd}' not found in graphs"
|
||||
|
||||
|
||||
def test_build_graphs_values_are_graphs():
|
||||
"""Test _build_graphs values are Graph instances."""
|
||||
import pyflowx as px
|
||||
|
||||
graphs = _build_graphs()
|
||||
for name, graph in graphs.items():
|
||||
assert isinstance(graph, px.Graph), (
|
||||
f"Graph for command '{name}' is not a Graph instance"
|
||||
)
|
||||
|
||||
|
||||
def test_build_command_graph_structure():
|
||||
"""Test 'b' command graph has correct structure."""
|
||||
|
||||
graphs = _build_graphs()
|
||||
graph = graphs["b"]
|
||||
assert len(graph.all_specs()) == 1
|
||||
spec = graph.spec("uv_build")
|
||||
assert spec.cmd == conf.BUILD_COMMAND
|
||||
|
||||
|
||||
def test_build_all_command_graph_structure():
|
||||
"""Test 'ba' command graph has correct dependencies."""
|
||||
|
||||
graphs = _build_graphs()
|
||||
graph = graphs["ba"]
|
||||
specs = graph.all_specs()
|
||||
assert len(specs) == 2
|
||||
# Check dependency
|
||||
uv_build_spec = graph.spec("uv_build")
|
||||
assert "maturin_build" in uv_build_spec.depends_on
|
||||
|
||||
|
||||
def test_maturin_build_command_graph_structure():
|
||||
"""Test 'bc' command graph has correct structure."""
|
||||
graphs = _build_graphs()
|
||||
graph = graphs["bc"]
|
||||
specs = graph.all_specs()
|
||||
assert len(specs) == 1
|
||||
spec = graph.spec("maturin_build")
|
||||
assert spec.cmd == _get_maturin_build_command()
|
||||
|
||||
|
||||
def test_install_all_command_graph_structure():
|
||||
"""Test 'ia' command graph has correct dependencies."""
|
||||
graphs = _build_graphs()
|
||||
graph = graphs["ia"]
|
||||
specs = graph.all_specs()
|
||||
assert len(specs) == 2
|
||||
uv_install_spec = graph.spec("uv_install")
|
||||
assert "maturin_dev" in uv_install_spec.depends_on
|
||||
|
||||
|
||||
def test_clean_all_command_graph_structure():
|
||||
"""Test 'ca' command graph has correct structure."""
|
||||
graphs = _build_graphs()
|
||||
graph = graphs["ca"]
|
||||
specs = graph.all_specs()
|
||||
assert len(specs) == 2
|
||||
|
||||
|
||||
def test_test_command_graph_structure():
|
||||
"""Test 't' command graph has correct structure."""
|
||||
graphs = _build_graphs()
|
||||
graph = graphs["t"]
|
||||
specs = graph.all_specs()
|
||||
assert len(specs) == 1
|
||||
spec = graph.spec("pytest")
|
||||
assert "pytest" in spec.cmd
|
||||
|
||||
|
||||
def test_lint_command_graph_structure():
|
||||
"""Test 'lint' command graph has correct structure."""
|
||||
graphs = _build_graphs()
|
||||
graph = graphs["lint"]
|
||||
specs = graph.all_specs()
|
||||
assert len(specs) == 1
|
||||
spec = graph.spec("ruff_check")
|
||||
assert "ruff" in spec.cmd
|
||||
|
||||
|
||||
def test_pymake_config_dirs_to_ignore():
|
||||
"""Test PymakeConfig has correct dirs to ignore."""
|
||||
assert ".venv" in conf.DIRS_TO_IGNORE
|
||||
assert ".git" in conf.DIRS_TO_IGNORE
|
||||
assert ".tox" in conf.DIRS_TO_IGNORE
|
||||
|
||||
|
||||
def test_pymake_config_python_build_dirs():
|
||||
"""Test PymakeConfig has correct Python build dirs."""
|
||||
assert "dist" in conf.PYTHON_BUILD_DIRS
|
||||
assert "build" in conf.PYTHON_BUILD_DIRS
|
||||
|
||||
|
||||
def test_maturin_build_options_win7():
|
||||
"""Test MATURIN_BUILD_OPTIONS_WIN7 has expected options."""
|
||||
assert "--target" in conf.MATURIN_BUILD_OPTIONS_WIN7
|
||||
assert "x86_64-win7-windows-msvc" in conf.MATURIN_BUILD_OPTIONS_WIN7
|
||||
assert "-Zbuild-std" in conf.MATURIN_BUILD_OPTIONS_WIN7
|
||||
|
||||
|
||||
def test_doc_build_command():
|
||||
"""Test DOC_BUILD_COMMAND has expected structure."""
|
||||
assert "sphinx-build" in conf.DOC_BUILD_COMMAND
|
||||
assert "-b" in conf.DOC_BUILD_COMMAND
|
||||
assert "html" in conf.DOC_BUILD_COMMAND
|
||||
@@ -0,0 +1,178 @@
|
||||
"""Tests for conditions module."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
from pyflowx.conditions import (
|
||||
IS_LINUX,
|
||||
IS_MACOS,
|
||||
IS_POSIX,
|
||||
IS_WINDOWS,
|
||||
BuiltinConditions,
|
||||
Constants,
|
||||
)
|
||||
|
||||
|
||||
def test_constants_is_windows():
|
||||
"""Test Constants.IS_WINDOWS is correct."""
|
||||
assert (sys.platform == "win32") == Constants.IS_WINDOWS
|
||||
|
||||
|
||||
def test_constants_is_linux():
|
||||
"""Test Constants.IS_LINUX is correct."""
|
||||
assert (sys.platform == "linux") == Constants.IS_LINUX
|
||||
|
||||
|
||||
def test_constants_is_macos():
|
||||
"""Test Constants.IS_MACOS is correct."""
|
||||
assert (sys.platform == "darwin") == Constants.IS_MACOS
|
||||
|
||||
|
||||
def test_constants_is_posix():
|
||||
"""Test Constants.IS_POSIX is correct."""
|
||||
assert (sys.platform != "win32") == Constants.IS_POSIX
|
||||
|
||||
|
||||
def test_builtin_conditions_is_windows():
|
||||
"""Test BuiltinConditions.IS_WINDOWS."""
|
||||
result = BuiltinConditions.IS_WINDOWS()
|
||||
assert result == Constants.IS_WINDOWS
|
||||
|
||||
|
||||
def test_builtin_conditions_is_linux():
|
||||
"""Test BuiltinConditions.IS_LINUX."""
|
||||
result = BuiltinConditions.IS_LINUX()
|
||||
assert result == Constants.IS_LINUX
|
||||
|
||||
|
||||
def test_builtin_conditions_is_macos():
|
||||
"""Test BuiltinConditions.IS_MACOS."""
|
||||
result = BuiltinConditions.IS_MACOS()
|
||||
assert result == Constants.IS_MACOS
|
||||
|
||||
|
||||
def test_builtin_conditions_is_posix():
|
||||
"""Test BuiltinConditions.IS_POSIX."""
|
||||
result = BuiltinConditions.IS_POSIX()
|
||||
assert result == Constants.IS_POSIX
|
||||
|
||||
|
||||
def test_builtin_conditions_python_version_major_only():
|
||||
"""Test BuiltinConditions.PYTHON_VERSION with major only."""
|
||||
# Test with current Python version
|
||||
current_major = sys.version_info.major
|
||||
assert BuiltinConditions.PYTHON_VERSION(current_major) is True
|
||||
assert BuiltinConditions.PYTHON_VERSION(current_major + 1) is False
|
||||
|
||||
|
||||
def test_builtin_conditions_python_version_with_minor():
|
||||
"""Test BuiltinConditions.PYTHON_VERSION with major and minor."""
|
||||
current_major = sys.version_info.major
|
||||
current_minor = sys.version_info.minor
|
||||
assert BuiltinConditions.PYTHON_VERSION(current_major, current_minor) is True
|
||||
assert BuiltinConditions.PYTHON_VERSION(current_major, current_minor + 1) is False
|
||||
|
||||
|
||||
def test_builtin_conditions_python_version_at_least():
|
||||
"""Test BuiltinConditions.PYTHON_VERSION_AT_LEAST."""
|
||||
current_major = sys.version_info.major
|
||||
current_minor = sys.version_info.minor
|
||||
# Current version should be at least itself
|
||||
assert (
|
||||
BuiltinConditions.PYTHON_VERSION_AT_LEAST(current_major, current_minor) is True
|
||||
)
|
||||
# Current version should be at least an older version
|
||||
assert BuiltinConditions.PYTHON_VERSION_AT_LEAST(current_major - 1, 0) is True
|
||||
# Current version should NOT be at least a newer version
|
||||
assert BuiltinConditions.PYTHON_VERSION_AT_LEAST(current_major + 1, 0) is False
|
||||
|
||||
|
||||
def test_builtin_conditions_has_app_installed_true():
|
||||
"""Test BuiltinConditions.HAS_APP_INSTALLED when app exists."""
|
||||
# Python should always be available
|
||||
condition = BuiltinConditions.HAS_APP_INSTALLED("python")
|
||||
assert condition() is True
|
||||
|
||||
|
||||
def test_builtin_conditions_has_app_installed_false():
|
||||
"""Test BuiltinConditions.HAS_APP_INSTALLED when app doesn't exist."""
|
||||
condition = BuiltinConditions.HAS_APP_INSTALLED("nonexistent_app_12345")
|
||||
assert condition() is False
|
||||
|
||||
|
||||
def test_builtin_conditions_env_var_exists_true():
|
||||
"""Test BuiltinConditions.ENV_VAR_EXISTS when variable exists."""
|
||||
with patch.dict(os.environ, {"TEST_VAR": "value"}):
|
||||
condition = BuiltinConditions.ENV_VAR_EXISTS("TEST_VAR")
|
||||
assert condition() is True
|
||||
|
||||
|
||||
def test_builtin_conditions_env_var_exists_false():
|
||||
"""Test BuiltinConditions.ENV_VAR_EXISTS when variable doesn't exist."""
|
||||
condition = BuiltinConditions.ENV_VAR_EXISTS("NONEXISTENT_VAR_12345")
|
||||
assert condition() is False
|
||||
|
||||
|
||||
def test_builtin_conditions_env_var_equals_true():
|
||||
"""Test BuiltinConditions.ENV_VAR_EQUALS when value matches."""
|
||||
with patch.dict(os.environ, {"TEST_VAR": "expected_value"}):
|
||||
condition = BuiltinConditions.ENV_VAR_EQUALS("TEST_VAR", "expected_value")
|
||||
assert condition() is True
|
||||
|
||||
|
||||
def test_builtin_conditions_env_var_equals_false():
|
||||
"""Test BuiltinConditions.ENV_VAR_EQUALS when value doesn't match."""
|
||||
with patch.dict(os.environ, {"TEST_VAR": "different_value"}):
|
||||
condition = BuiltinConditions.ENV_VAR_EQUALS("TEST_VAR", "expected_value")
|
||||
assert condition() is False
|
||||
|
||||
|
||||
def test_builtin_conditions_not():
|
||||
"""Test BuiltinConditions.NOT."""
|
||||
true_condition = lambda: True # noqa: E731
|
||||
false_condition = lambda: False # noqa: E731
|
||||
|
||||
not_true = BuiltinConditions.NOT(true_condition)
|
||||
assert not_true() is False
|
||||
|
||||
not_false = BuiltinConditions.NOT(false_condition)
|
||||
assert not_false() is True
|
||||
|
||||
|
||||
def test_builtin_conditions_and_all_true():
|
||||
"""Test BuiltinConditions.AND when all conditions are true."""
|
||||
true_condition = lambda: True # noqa: E731
|
||||
condition = BuiltinConditions.AND(true_condition, true_condition, true_condition)
|
||||
assert condition() is True
|
||||
|
||||
|
||||
def test_builtin_conditions_and_one_false():
|
||||
"""Test BuiltinConditions.AND when one condition is false."""
|
||||
true_condition = lambda: True # noqa: E731
|
||||
false_condition = lambda: False # noqa: E731
|
||||
condition = BuiltinConditions.AND(true_condition, false_condition, true_condition)
|
||||
assert condition() is False
|
||||
|
||||
|
||||
def test_builtin_conditions_or_all_false():
|
||||
"""Test BuiltinConditions.OR when all conditions are false."""
|
||||
false_condition = lambda: False # noqa: E731
|
||||
condition = BuiltinConditions.OR(false_condition, false_condition, false_condition)
|
||||
assert condition() is False
|
||||
|
||||
|
||||
def test_builtin_conditions_or_one_true():
|
||||
"""Test BuiltinConditions.OR when one condition is true."""
|
||||
true_condition = lambda: True # noqa: E731
|
||||
false_condition = lambda: False # noqa: E731
|
||||
condition = BuiltinConditions.OR(false_condition, true_condition, false_condition)
|
||||
assert condition() is True
|
||||
|
||||
|
||||
def test_exported_conditions():
|
||||
"""Test exported condition functions."""
|
||||
assert IS_WINDOWS() == Constants.IS_WINDOWS
|
||||
assert IS_LINUX() == Constants.IS_LINUX
|
||||
assert IS_MACOS() == Constants.IS_MACOS
|
||||
assert IS_POSIX() == Constants.IS_POSIX
|
||||
@@ -0,0 +1,189 @@
|
||||
"""Tests for executors module edge cases."""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
import pyflowx as px
|
||||
from pyflowx.task import TaskStatus
|
||||
|
||||
# 跨平台的 echo 命令
|
||||
if sys.platform == "win32":
|
||||
ECHO_CMD = ["cmd", "/c", "echo"]
|
||||
else:
|
||||
ECHO_CMD = ["echo"]
|
||||
|
||||
|
||||
def test_execute_sync_with_timeout():
|
||||
"""Test execute task with timeout correctly."""
|
||||
# Note: timeout for Python functions only works in async strategy
|
||||
# For sync functions, timeout is not enforced in sequential strategy
|
||||
# This test verifies that the task runs without timeout error
|
||||
spec = px.TaskSpec("quick", fn=lambda: "result", timeout=10)
|
||||
graph = px.Graph.from_specs([spec])
|
||||
|
||||
# Should succeed without timeout error
|
||||
report = px.run(graph, strategy="sequential")
|
||||
assert report.success
|
||||
|
||||
|
||||
def test_execute_async_with_timeout():
|
||||
"""Test execute async task with timeout correctly."""
|
||||
|
||||
async def slow_async_function():
|
||||
await asyncio.sleep(2)
|
||||
return "result"
|
||||
|
||||
spec = px.TaskSpec("slow_async", fn=slow_async_function, timeout=0.5)
|
||||
graph = px.Graph.from_specs([spec])
|
||||
|
||||
# This should timeout
|
||||
with pytest.raises(px.TaskFailedError):
|
||||
px.run(graph, strategy="async")
|
||||
|
||||
|
||||
def test_verbose_event_callback_running():
|
||||
"""Test verbose event callback for RUNNING status."""
|
||||
# Create a graph with verbose callback
|
||||
spec = px.TaskSpec("test", fn=lambda: "result", verbose=True)
|
||||
graph = px.Graph.from_specs([spec])
|
||||
report = px.run(graph, strategy="sequential")
|
||||
# Should print without error
|
||||
assert report.success
|
||||
|
||||
|
||||
def test_verbose_event_callback_success():
|
||||
"""Test verbose event callback for SUCCESS status."""
|
||||
# Create a graph with verbose callback
|
||||
spec = px.TaskSpec("test", fn=lambda: "result", verbose=True)
|
||||
graph = px.Graph.from_specs([spec])
|
||||
report = px.run(graph, strategy="sequential")
|
||||
# Should print without error
|
||||
assert report.success
|
||||
|
||||
|
||||
def test_verbose_event_callback_failed():
|
||||
"""Test verbose event callback for FAILED status."""
|
||||
# Create a graph with verbose callback and failing task
|
||||
|
||||
def raise_error():
|
||||
raise ValueError("test error")
|
||||
|
||||
spec = px.TaskSpec("test", fn=raise_error, verbose=True)
|
||||
graph = px.Graph.from_specs([spec])
|
||||
|
||||
# Should print without error
|
||||
with pytest.raises(px.TaskFailedError):
|
||||
px.run(graph, strategy="sequential")
|
||||
|
||||
|
||||
def test_verbose_event_callback_skipped():
|
||||
"""Test verbose event callback for SKIPPED status."""
|
||||
# Create a graph with verbose callback and skipped task
|
||||
spec = px.TaskSpec(
|
||||
"test",
|
||||
fn=lambda: "result",
|
||||
conditions=(lambda: False,),
|
||||
verbose=True,
|
||||
)
|
||||
graph = px.Graph.from_specs([spec])
|
||||
report = px.run(graph, strategy="sequential")
|
||||
# Should print without error
|
||||
assert report.success
|
||||
|
||||
|
||||
def test_execute_sync_with_retries():
|
||||
"""Test execute task with retries."""
|
||||
|
||||
call_count = 0
|
||||
|
||||
def failing_function():
|
||||
nonlocal call_count
|
||||
call_count += 1
|
||||
if call_count < 3:
|
||||
raise ValueError("temporary error")
|
||||
return "success"
|
||||
|
||||
spec = px.TaskSpec("retry_test", fn=failing_function, retries=3)
|
||||
graph = px.Graph.from_specs([spec])
|
||||
|
||||
# Should succeed after retries
|
||||
report = px.run(graph, strategy="sequential")
|
||||
assert report.success
|
||||
assert report.results["retry_test"].attempts == 3
|
||||
|
||||
|
||||
def test_execute_async_with_retries():
|
||||
"""Test execute async task with retries."""
|
||||
|
||||
call_count = 0
|
||||
|
||||
async def failing_async_function():
|
||||
nonlocal call_count
|
||||
call_count += 1
|
||||
if call_count < 3:
|
||||
raise ValueError("temporary error")
|
||||
return "success"
|
||||
|
||||
spec = px.TaskSpec("retry_async_test", fn=failing_async_function, retries=3)
|
||||
graph = px.Graph.from_specs([spec])
|
||||
|
||||
# Should succeed after retries
|
||||
report = px.run(graph, strategy="async")
|
||||
assert report.success
|
||||
assert report.results["retry_async_test"].attempts == 3
|
||||
|
||||
|
||||
def test_execute_sync_skip_on_condition():
|
||||
"""Test execute task skips task when condition is false."""
|
||||
spec = px.TaskSpec(
|
||||
"skip_test",
|
||||
fn=lambda: "result",
|
||||
conditions=(lambda: False,),
|
||||
)
|
||||
graph = px.Graph.from_specs([spec])
|
||||
|
||||
report = px.run(graph, strategy="sequential")
|
||||
assert report.success
|
||||
assert report.results["skip_test"].status == TaskStatus.SKIPPED
|
||||
|
||||
|
||||
def test_execute_async_skip_on_condition():
|
||||
"""Test execute async task skips task when condition is false."""
|
||||
spec = px.TaskSpec(
|
||||
"skip_async_test",
|
||||
fn=lambda: "result",
|
||||
conditions=(lambda: False,),
|
||||
)
|
||||
graph = px.Graph.from_specs([spec])
|
||||
|
||||
report = px.run(graph, strategy="async")
|
||||
assert report.success
|
||||
assert report.results["skip_async_test"].status == TaskStatus.SKIPPED
|
||||
|
||||
|
||||
def test_execute_sync_with_error():
|
||||
"""Test execute task handles errors correctly."""
|
||||
|
||||
def error_function():
|
||||
raise ValueError("test error")
|
||||
|
||||
spec = px.TaskSpec("error_test", fn=error_function)
|
||||
graph = px.Graph.from_specs([spec])
|
||||
|
||||
with pytest.raises(px.TaskFailedError):
|
||||
px.run(graph, strategy="sequential")
|
||||
|
||||
|
||||
def test_execute_async_with_error():
|
||||
"""Test execute async task handles errors correctly."""
|
||||
|
||||
async def error_async_function():
|
||||
raise ValueError("test error")
|
||||
|
||||
spec = px.TaskSpec("error_async_test", fn=error_async_function)
|
||||
graph = px.Graph.from_specs([spec])
|
||||
|
||||
with pytest.raises(px.TaskFailedError):
|
||||
px.run(graph, strategy="async")
|
||||
@@ -0,0 +1,156 @@
|
||||
"""Tests for task module edge cases."""
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
import pyflowx as px
|
||||
from pyflowx.task import TaskSpec
|
||||
|
||||
# 跨平台的 echo 命令
|
||||
if sys.platform == "win32":
|
||||
ECHO_CMD = ["cmd", "/c", "echo"]
|
||||
else:
|
||||
ECHO_CMD = ["echo"]
|
||||
|
||||
|
||||
def test_taskspec_wrap_cmd_with_list():
|
||||
"""Test TaskSpec._wrap_cmd with command list."""
|
||||
spec = TaskSpec("test", cmd=[*ECHO_CMD, "hello"])
|
||||
wrapped_fn = spec.effective_fn
|
||||
assert wrapped_fn is not None
|
||||
assert wrapped_fn.__name__ == "test"
|
||||
|
||||
|
||||
def test_taskspec_wrap_cmd_with_string():
|
||||
"""Test TaskSpec._wrap_cmd with command string."""
|
||||
if sys.platform == "win32":
|
||||
cmd_str = "cmd /c echo hello"
|
||||
else:
|
||||
cmd_str = "echo hello"
|
||||
spec = TaskSpec("test", cmd=cmd_str)
|
||||
wrapped_fn = spec.effective_fn
|
||||
assert wrapped_fn is not None
|
||||
assert wrapped_fn.__name__ == "test"
|
||||
|
||||
|
||||
def test_taskspec_wrap_cmd_with_timeout():
|
||||
"""Test TaskSpec._wrap_cmd with timeout."""
|
||||
spec = TaskSpec("test", cmd=[*ECHO_CMD, "hello"], timeout=0.1)
|
||||
wrapped_fn = spec.effective_fn
|
||||
|
||||
# Should not raise timeout error for quick command
|
||||
result = wrapped_fn()
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_taskspec_wrap_cmd_with_cwd():
|
||||
"""Test TaskSpec._wrap_cmd with working directory."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
spec = TaskSpec("test", cmd=[*ECHO_CMD, "hello"], cwd=tmpdir)
|
||||
wrapped_fn = spec.effective_fn
|
||||
result = wrapped_fn()
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_taskspec_wrap_cmd_verbose():
|
||||
"""Test TaskSpec._wrap_cmd with verbose=True."""
|
||||
spec = TaskSpec("test", cmd=[*ECHO_CMD, "hello"], verbose=True)
|
||||
wrapped_fn = spec.effective_fn
|
||||
|
||||
# Should print verbose output
|
||||
result = wrapped_fn()
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_taskspec_wrap_cmd_error():
|
||||
"""Test TaskSpec._wrap_cmd handles command error."""
|
||||
spec = TaskSpec("test", cmd=["python", "-c", "import sys; sys.exit(1)"])
|
||||
wrapped_fn = spec.effective_fn
|
||||
|
||||
with pytest.raises(RuntimeError, match="命令执行失败"):
|
||||
wrapped_fn()
|
||||
|
||||
|
||||
def test_taskspec_wrap_cmd_file_not_found():
|
||||
"""Test TaskSpec._wrap_cmd handles file not found."""
|
||||
spec = TaskSpec("test", cmd=["nonexistent_command"])
|
||||
wrapped_fn = spec.effective_fn
|
||||
|
||||
with pytest.raises(RuntimeError, match="命令未找到"):
|
||||
wrapped_fn()
|
||||
|
||||
|
||||
def test_taskspec_wrap_cmd_shell_file_not_found():
|
||||
"""Test TaskSpec._wrap_cmd handles shell command file not found."""
|
||||
spec = TaskSpec("test", cmd="nonexistent_shell_command")
|
||||
wrapped_fn = spec.effective_fn
|
||||
|
||||
# Shell commands don't raise FileNotFoundError
|
||||
# They just return non-zero exit code
|
||||
with pytest.raises(RuntimeError):
|
||||
wrapped_fn()
|
||||
|
||||
|
||||
def test_taskspec_no_fn_no_cmd():
|
||||
"""Test TaskSpec raises error when no fn or cmd."""
|
||||
with pytest.raises(ValueError, match="必须提供 fn 或 cmd 参数"):
|
||||
TaskSpec("test")
|
||||
|
||||
|
||||
def test_taskspec_cmd_overrides_fn():
|
||||
"""Test TaskSpec cmd overrides fn."""
|
||||
|
||||
def my_fn():
|
||||
return "fn_result"
|
||||
|
||||
spec = TaskSpec("test", fn=my_fn, cmd=[*ECHO_CMD, "hello"])
|
||||
wrapped_fn = spec.effective_fn
|
||||
|
||||
# cmd should override fn
|
||||
assert wrapped_fn.__name__ == "test"
|
||||
|
||||
|
||||
def test_taskspec_conditions_check():
|
||||
"""Test TaskSpec.should_execute with conditions."""
|
||||
spec = px.TaskSpec(
|
||||
"test",
|
||||
fn=lambda: "result",
|
||||
conditions=(lambda: True,),
|
||||
)
|
||||
|
||||
assert spec.should_execute() is True
|
||||
|
||||
|
||||
def test_taskspec_conditions_false():
|
||||
"""Test TaskSpec.should_execute with false conditions."""
|
||||
spec = px.TaskSpec(
|
||||
"test",
|
||||
fn=lambda: "result",
|
||||
conditions=(lambda: False,),
|
||||
)
|
||||
|
||||
assert spec.should_execute() is False
|
||||
|
||||
|
||||
def test_taskspec_conditions_multiple():
|
||||
"""Test TaskSpec.should_execute with multiple conditions."""
|
||||
spec = px.TaskSpec(
|
||||
"test",
|
||||
fn=lambda: "result",
|
||||
conditions=(lambda: True, lambda: True, lambda: True),
|
||||
)
|
||||
|
||||
assert spec.should_execute() is True
|
||||
|
||||
|
||||
def test_taskspec_conditions_multiple_one_false():
|
||||
"""Test TaskSpec.should_execute with one false condition."""
|
||||
spec = px.TaskSpec(
|
||||
"test",
|
||||
fn=lambda: "result",
|
||||
conditions=(lambda: True, lambda: False, lambda: True),
|
||||
)
|
||||
|
||||
assert spec.should_execute() is False
|
||||
Reference in New Issue
Block a user