Files
2026-07-13 13:18:33 +08:00

282 lines
9.2 KiB
Python

# Copyright (c) DeepSpeed Team.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
"""Self-tests for ci/tests_fetcher.py (#9).
These build small synthetic git repos on disk and assert the selector makes the
right call (narrow vs. full vs. nothing). They are pure-stdlib (only need ``git``
on PATH), so they run anywhere -- including the ``collect-tests`` CI job that uses
the fetcher -- without a DeepSpeed/torch install.
Run standalone::
python ci/test_tests_fetcher.py
or under pytest::
pytest ci/test_tests_fetcher.py
"""
from __future__ import annotations
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent)) # import the sibling module
from tests_fetcher import TestSelector, WorkflowConfig # noqa: E402
CONFIG = WorkflowConfig(
name="test",
test_scopes=("tests/unit/v1", ),
extra_run_all_globs=(".github/workflows/modal*.yml", ),
)
# A tiny but representative repo:
# deepspeed.leaf -> imported only by test_leaf
# deepspeed.shared -> imported by test_shared + test_shared2
# deepspeed.orphan -> imported by nobody (for clean-delete test)
# runtime/engine, comm/comm, accelerator/* -> core run-all globs
# module_inject/* -> dynamic edge -> moe tests
BASELINE = {
"deepspeed/__init__.py": "from deepspeed import runtime # noqa\n",
"deepspeed/leaf.py": "VALUE = 1\n",
"deepspeed/shared.py": "VALUE = 2\n",
"deepspeed/orphan.py": "VALUE = 3\n",
"deepspeed/runtime/__init__.py": "",
"deepspeed/runtime/engine.py": "VALUE = 4\n",
"deepspeed/comm/__init__.py": "",
"deepspeed/comm/comm.py": "VALUE = 5\n",
"deepspeed/module_inject/__init__.py": "",
"deepspeed/module_inject/replace.py": "VALUE = 6\n",
"tests/__init__.py": "",
"tests/unit/__init__.py": "",
"tests/unit/common.py": "import deepspeed # noqa\n",
"tests/unit/v1/__init__.py": "",
"tests/unit/v1/test_leaf.py": "from deepspeed.leaf import VALUE\n\n\ndef test_x():\n assert VALUE == 1\n",
"tests/unit/v1/test_shared.py": "from deepspeed.shared import VALUE\n\n\ndef test_x():\n assert VALUE == 2\n",
"tests/unit/v1/test_shared2.py": "from deepspeed.shared import VALUE\n\n\ndef test_x():\n assert VALUE == 2\n",
"tests/unit/v1/test_plain.py": "import deepspeed # noqa\n\n\ndef test_x():\n assert True\n",
"tests/unit/v1/moe/__init__.py": "",
"tests/unit/v1/moe/test_moe.py": "import deepspeed # noqa\n\n\ndef test_x():\n assert True\n",
"README.md": "# synthetic repo\n",
}
class TmpRepo:
"""A throwaway git repo seeded with BASELINE, committed on ``master``."""
def __init__(self) -> None:
self.root = Path(tempfile.mkdtemp(prefix="ds-fetcher-test-"))
for rel, content in BASELINE.items():
self.write(rel, content)
self._git("init", "-q", "-b", "master")
self._git("config", "user.email", "ci@example.com")
self._git("config", "user.name", "ci")
self._git("add", "-A")
self._git("commit", "-q", "-m", "baseline")
# Work on a feature branch so ``select("master")`` has a real diff.
self._git("checkout", "-q", "-b", "feature")
def _git(self, *args: str) -> str:
return subprocess.run(["git", *args], cwd=self.root, check=True, capture_output=True, text=True).stdout
def write(self, rel: str, content: str = "") -> None:
p = self.root / rel
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(content, encoding="utf-8")
def delete(self, rel: str) -> None:
(self.root / rel).unlink()
def commit(self, message: str) -> None:
self._git("add", "-A")
self._git("commit", "-q", "--allow-empty", "-m", message)
def selector(self) -> TestSelector:
return TestSelector(self.root, CONFIG)
def cleanup(self) -> None:
shutil.rmtree(self.root, ignore_errors=True)
def _rel_names(repo: TmpRepo, tests) -> set[str]:
return {p.relative_to(repo.root).as_posix() for p in tests}
# --------------------------------------------------------------------------- #
# Individual checks. Each builds a fresh repo, mutates it, and asserts.
# --------------------------------------------------------------------------- #
def test_leaf_change_narrows_to_one_test() -> None:
repo = TmpRepo()
try:
repo.write("deepspeed/leaf.py", "VALUE = 11\n")
repo.commit("touch leaf")
sel = repo.selector().select("master")
assert sel.mode == "subset", sel.reason
assert _rel_names(repo, sel.tests) == {"tests/unit/v1/test_leaf.py"}
finally:
repo.cleanup()
def test_shared_change_selects_all_importers() -> None:
repo = TmpRepo()
try:
repo.write("deepspeed/shared.py", "VALUE = 22\n")
repo.commit("touch shared")
sel = repo.selector().select("master")
assert sel.mode == "subset", sel.reason
assert _rel_names(repo, sel.tests) == {
"tests/unit/v1/test_shared.py",
"tests/unit/v1/test_shared2.py",
}
finally:
repo.cleanup()
def test_core_runtime_file_runs_all() -> None:
repo = TmpRepo()
try:
repo.write("deepspeed/runtime/engine.py", "VALUE = 44\n")
repo.commit("touch engine")
sel = repo.selector().select("master")
assert sel.mode == "all", sel.reason
finally:
repo.cleanup()
def test_comm_change_runs_all() -> None:
repo = TmpRepo()
try:
repo.write("deepspeed/comm/comm.py", "VALUE = 55\n")
repo.commit("touch comm")
sel = repo.selector().select("master")
assert sel.mode == "all", sel.reason
finally:
repo.cleanup()
def test_shared_fixture_runs_all() -> None:
repo = TmpRepo()
try:
repo.write("tests/unit/common.py", "import deepspeed # touched\n")
repo.commit("touch common")
sel = repo.selector().select("master")
assert sel.mode == "all", sel.reason
finally:
repo.cleanup()
def test_commit_tag_forces_all() -> None:
repo = TmpRepo()
try:
repo.write("deepspeed/leaf.py", "VALUE = 11\n")
repo.commit("touch leaf [test all]")
sel = repo.selector().select("master")
assert sel.mode == "all", sel.reason
finally:
repo.cleanup()
def test_non_python_change_selects_nothing() -> None:
repo = TmpRepo()
try:
repo.write("README.md", "# changed\n")
repo.commit("docs only")
sel = repo.selector().select("master")
assert sel.mode == "none", sel.reason
finally:
repo.cleanup()
def test_new_test_file_is_selected() -> None:
repo = TmpRepo()
try:
repo.write(
"tests/unit/v1/test_new.py",
"import deepspeed # noqa\n\n\ndef test_x():\n assert True\n",
)
repo.commit("add new test")
sel = repo.selector().select("master")
assert sel.mode == "subset", sel.reason
assert "tests/unit/v1/test_new.py" in _rel_names(repo, sel.tests)
finally:
repo.cleanup()
def test_clean_delete_does_not_run_all() -> None:
# Deleting a module nobody imports must NOT trigger a full run (#5).
repo = TmpRepo()
try:
repo.delete("deepspeed/orphan.py")
repo.commit("remove orphan")
sel = repo.selector().select("master")
assert sel.mode == "none", sel.reason
finally:
repo.cleanup()
def test_dangling_delete_runs_all() -> None:
# Deleting a module that a surviving file still imports IS unsafe -> full run (#5).
repo = TmpRepo()
try:
repo.delete("deepspeed/shared.py") # test_shared / test_shared2 still import it
repo.commit("remove shared but leave importers")
sel = repo.selector().select("master")
assert sel.mode == "all", sel.reason
finally:
repo.cleanup()
def test_dynamic_edge_pulls_in_moe_tests() -> None:
# module_inject is wired in at runtime; test_moe never imports it (#4).
repo = TmpRepo()
try:
repo.write("deepspeed/module_inject/replace.py", "VALUE = 66\n")
repo.commit("touch module_inject")
sel = repo.selector().select("master")
assert sel.mode == "subset", sel.reason
assert "tests/unit/v1/moe/test_moe.py" in _rel_names(repo, sel.tests)
finally:
repo.cleanup()
def test_missing_base_runs_all() -> None:
repo = TmpRepo()
try:
sel = repo.selector().select("")
assert sel.mode == "all", sel.reason
sel = repo.selector().select("origin/does-not-exist")
assert sel.mode == "all", sel.reason
finally:
repo.cleanup()
def _all_test_functions():
return sorted((name, obj) for name, obj in globals().items() if name.startswith("test_") and callable(obj))
def main() -> int:
failures = 0
for name, fn in _all_test_functions():
try:
fn()
print(f"PASS {name}")
except AssertionError as e:
failures += 1
print(f"FAIL {name}: {e}")
except Exception as e: # noqa: BLE001
failures += 1
print(f"ERROR {name}: {type(e).__name__}: {e}")
total = len(_all_test_functions())
print(f"\n{total - failures}/{total} passed")
return 1 if failures else 0
if __name__ == "__main__":
raise SystemExit(main())