diff --git a/.gitignore b/.gitignore index 8ad8793..192d598 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ wheels/ .venv .coverage .idea +*_profile.html diff --git a/src/pyflowx/cli/pymake.py b/src/pyflowx/cli/pymake.py index bb50120..55b9549 100644 --- a/src/pyflowx/cli/pymake.py +++ b/src/pyflowx/cli/pymake.py @@ -6,39 +6,48 @@ from __future__ import annotations +from pathlib import Path + import pyflowx as px from pyflowx.conditions import Constants +# 项目根目录(pymake.py 在 src/pyflowx/cli,向上四层到达根目录) +ROOT_DIR = Path(__file__).parent.parent.parent.parent + MATURIN_BUILD_COMMAND = ["maturin", "build", "-r"] if Constants.IS_WINDOWS: MATURIN_BUILD_COMMAND.extend(["--target", "x86_64-win7-windows-msvc", "-Zbuild-std", "-i", "python3.8"]) # 扁平注册所有任务(px.cmd 自动从命令前两段推导 name) +# 所有任务指定 cwd=ROOT_DIR,确保在项目根目录执行 tasks: list[px.TaskSpec] = [ - px.cmd(["uv", "build"]), - px.cmd(MATURIN_BUILD_COMMAND), - px.cmd(["uv", "sync"]), - px.cmd(["gitt", "c"], name="git_clean"), + px.cmd(["uv", "build"], cwd=ROOT_DIR), + px.cmd(MATURIN_BUILD_COMMAND, cwd=ROOT_DIR), + px.cmd(["uv", "sync"], cwd=ROOT_DIR), + px.cmd(["gitt", "c"], name="git_clean", cwd=ROOT_DIR), px.cmd( ["pytest", "-m", "not slow", "-n", "8", "--dist", "loadfile", "--color=yes", "--durations=10"], name="test", + cwd=ROOT_DIR, ), px.cmd( ["pytest", "-m", "not slow", "--dist", "loadfile", "--color=yes", "--durations=10"], name="test_fast", + cwd=ROOT_DIR, ), px.cmd( ["pytest", "--cov", "-n", "8", "--dist", "loadfile", "--tb=short", "-v", "--color=yes", "--durations=10"], name="test_coverage", + cwd=ROOT_DIR, ), - px.cmd(["pyrefly", "check", "."]), - px.cmd(["git", "add", "-A"], name="git_add_all"), - px.cmd(["bumpversion"]), - px.cmd(["bumpversion", "minor"]), - px.cmd(["git", "push"]), - px.cmd(["git", "push", "--tags"], name="git_push_tags"), - px.cmd(["hatch", "publish"], name="publish_python"), - px.cmd(["twine", "upload", "--disable-progress-bar"], name="twine_publish"), + px.cmd(["pyrefly", "check", "."], cwd=ROOT_DIR), + px.cmd(["git", "add", "-A"], name="git_add_all", cwd=ROOT_DIR), + px.cmd(["bumpversion"], cwd=ROOT_DIR), + px.cmd(["bumpversion", "minor"], cwd=ROOT_DIR), + px.cmd(["git", "push"], cwd=ROOT_DIR), + px.cmd(["git", "push", "--tags"], name="git_push_tags", cwd=ROOT_DIR), + px.cmd(["hatch", "publish"], name="publish_python", cwd=ROOT_DIR), + px.cmd(["twine", "upload", "--disable-progress-bar"], name="twine_publish", cwd=ROOT_DIR), ] # 单任务别名(alias 名与任务名相同):直接内联 TaskSpec,避免 str 自引用 @@ -55,13 +64,13 @@ aliases: dict[str, str | list[str | px.TaskSpec] | px.TaskSpec | px.Graph] = { "bump": ["c", "tc", "git_add_all", "bumpversion"], "bumpmi": "bumpversion_minor", "cov": ["git_clean", "test_coverage"], - "doc": px.cmd(["sphinx-build", "-b", "html", "docs", "docs/_build"], name="doc"), - "lint": px.cmd(["ruff", "check", "--fix", "--unsafe-fixes"], name="lint"), + "doc": px.cmd(["sphinx-build", "-b", "html", "docs", "docs/_build"], name="doc", cwd=ROOT_DIR), + "lint": px.cmd(["ruff", "check", "--fix", "--unsafe-fixes"], name="lint", cwd=ROOT_DIR), "pb": ["twine_publish", "publish_python"], "t": "test", "tf": "test_fast", "tc": ["pyrefly_check", "lint"], - "tox": px.cmd(["tox", "-p", "auto"], name="tox"), + "tox": px.cmd(["tox", "-p", "auto"], name="tox", cwd=ROOT_DIR), # 发布命令 "p": ["git_clean", "git_push", "git_push_tags"], } diff --git a/tests/cli/test_pymake.py b/tests/cli/test_pymake.py index 94d3ba8..4919f9a 100644 --- a/tests/cli/test_pymake.py +++ b/tests/cli/test_pymake.py @@ -2,6 +2,7 @@ from __future__ import annotations +from pathlib import Path from unittest.mock import patch import pytest @@ -123,6 +124,22 @@ class TestTaskSpecDefinitions: assert spec.cmd == ["tox", "-p", "auto"] assert spec.skip_if_missing is False + def test_all_tasks_have_correct_cwd(self) -> None: + """所有任务应该有正确的 cwd 设置(指向项目根目录).""" + # 验证 ROOT_DIR 定义正确(向上三层到达项目根目录) + expected_root = Path(__file__).parent.parent.parent + assert expected_root == pymake.ROOT_DIR + + # 验证 tasks 中的所有命令任务都有正确的 cwd + for spec in pymake.tasks: + if spec.cmd is not None: + assert spec.cwd == pymake.ROOT_DIR, f"任务 {spec.name} 的 cwd 应为 {pymake.ROOT_DIR}" + + # 验证 aliases 中的内联任务(doc/lint/tox)也有正确的 cwd + for name in ("doc", "lint", "tox"): + spec = _find_task(name) + assert spec.cwd == pymake.ROOT_DIR, f"任务 {name} 的 cwd 应为 {pymake.ROOT_DIR}" + # ---------------------------------------------------------------------- # # main function diff --git a/tests/test_profiling.py b/tests/test_profiling.py index 8c36230..f8156d5 100644 --- a/tests/test_profiling.py +++ b/tests/test_profiling.py @@ -531,16 +531,23 @@ class TestIntegrationWithRun: def test_profile_from_real_run(self) -> None: """从真实 run() 结果构建剖面.""" + import time + + def slow() -> int: + time.sleep(0.01) # 确保任务有实际耗时,避免 duration 极小导致并行度计算为 0 + return 1 + graph = px.Graph.from_specs([ - px.TaskSpec("a", lambda: 1), - px.TaskSpec("b", lambda: 2, depends_on=("a",)), - px.TaskSpec("c", lambda: 3, depends_on=("a",)), + px.TaskSpec("a", slow), + px.TaskSpec("b", slow, depends_on=("a",)), + px.TaskSpec("c", slow, depends_on=("a",)), ]) report = px.run(graph, strategy="sequential") profile = ProfileReport.from_report(report, graph) assert len(profile.tasks) == 3 + # sequential 策略下应为串行,duration > 0 assert profile.critical_path_duration > 0 # sequential 策略下并行度应为 1 assert profile.peak_parallelism == 1 diff --git a/tests/test_task.py b/tests/test_task.py index e787cee..16cebcf 100644 --- a/tests/test_task.py +++ b/tests/test_task.py @@ -193,8 +193,8 @@ def test_should_execute_skip_if_missing_cmd_not_found() -> None: def test_should_execute_skip_if_missing_cmd_found() -> None: """skip_if_missing 但命令存在时应执行.""" - # 使用 Python 作为已安装的命令 - spec = TaskSpec("a", cmd=["echo"], skip_if_missing=True) # echo 应存在 + # 使用 Python 作为已安装的命令(Windows 上 echo 是 shell 内置,shutil.which 找不到) + spec = TaskSpec("a", cmd=["python"], skip_if_missing=True) # python 应存在 should_run, reason = spec.should_execute({}) assert should_run is True assert reason is None diff --git a/uv.lock b/uv.lock index 793fe91..93e30e1 100644 --- a/uv.lock +++ b/uv.lock @@ -5603,7 +5603,7 @@ pycountry = [ [[package]] name = "pyflowx" -version = "0.2.12" +version = "0.2.13" source = { editable = "." } dependencies = [ { name = "graphlib-backport", marker = "python_full_version < '3.9'" },