Skip to content

Conversation

@xunyoyo
Copy link
Contributor

@xunyoyo xunyoyo commented Nov 15, 2025

Motivation

NO.34 功能模块 fastdeploy/scheduler/splitwise_scheduler.py 单测补充

Modifications

new dir and add tests/scheduler/test_splitwise_scheduler.py

Usage or Command

scheduler/test_splitwise_scheduler.py:

python -m coverage run -m unittest tests.scheduler.test_splitwise_scheduler \
&& python -m coverage report -m --include='fastdeploy/scheduler/splitwise_scheduler.py'

Accuracy Tests

tests/scheduler/test_splitwise_scheduler.py:

Name                                          Stmts   Miss  Cover   Missing
---------------------------------------------------------------------------
fastdeploy/scheduler/splitwise_scheduler.py     538     84    84%   349-350, 353, 384, 390-394, 396, 409, 418-419, 489-515, 539,
 543, 566-571, 578-591, 604, 646-649, 660, 676, 681, 739, 741, 750, 752, 772-775, 788, 790, 804, 814, 818-820, 825-826, 836, 839
-840, 847-849
---------------------------------------------------------------------------
TOTAL                                           538     84    84%

Checklist

  • Add at least a tag in the PR title.
    • Tag list: [[FDConfig],[APIServer],[Engine], [Scheduler], [PD Disaggregation], [Executor], [Graph Optimization], [Speculative Decoding], [RL], [Models], [Quantization], [Loader], [OP], [KVCache], [DataProcessor], [BugFix], [Docs], [CI], [Optimization], [Feature], [Benchmark], [Others], [XPU], [HPU], [GCU], [DCU], [Iluvatar], [Metax]]
    • You can add new tags based on the PR content, but the semantics must be clear.
  • Format your code, run pre-commit before commit.
  • Add unit tests. Please write the reason in this PR if no unit tests.
  • Provide accuracy results.
  • If the current PR is submitting to the release branch, make sure the PR has been submitted to the develop branch, then cherry-pick it to the release branch with the [Cherry-Pick] PR tag.

Copilot AI review requested due to automatic review settings November 15, 2025 10:29
@paddle-bot
Copy link

paddle-bot bot commented Nov 15, 2025

Thanks for your contribution!

@paddle-bot paddle-bot bot added the contributor External developers label Nov 15, 2025
Copilot finished reviewing on behalf of xunyoyo November 15, 2025 10:31
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds comprehensive unit tests for the fastdeploy/scheduler/splitwise_scheduler.py module as part of Hackathon 9th Sprint No.34, achieving 84% test coverage. The tests use stub implementations for external dependencies (Redis, orjson, logging) to enable isolated testing without requiring actual dependencies.

Key Changes

  • Creates a new test file tests/scheduler/test_splitwise_scheduler.py with 976 lines of test code
  • Implements stub modules for Redis, orjson, request classes, and logging to avoid external dependencies
  • Covers 8 test classes testing configuration, node management, scheduling logic, and background workers

raise SystemExit()

def execute(self):
return None
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The execute method returns None explicitly. This is unnecessary and adds clutter. Consider removing the return None statement.

Suggested change
return None
pass

Copilot uses AI. Check for mistakes.
Comment on lines +115 to +123
def rpop(self, key: str, count: Optional[int] = None) -> Optional[list[Any]]:
bucket = self.storage.get(key)
if not bucket:
return None
if count is None:
return [bucket.pop()]
count = min(count, len(bucket))
values = [bucket.pop() for _ in range(count)]
return values
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rpop method has a potential bug when an empty list exists for a key. If bucket is an empty list (which is falsy), the method returns None, but it should attempt to pop from the empty list (which would raise an IndexError) or return an empty list. This inconsistency could lead to incorrect test behavior.

Consider changing line 117 to:

if bucket is None or not bucket:

Or better yet, check the bucket state more explicitly to match Redis behavior.

Copilot uses AI. Check for mistakes.
logger_mod = types.ModuleType("fastdeploy.utils.scheduler_logger")

def _log(*_args: Any, **_kwargs: Any) -> None:
return None
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _log function explicitly returns None, which is unnecessary in Python. Functions without a return statement implicitly return None. This adds no value and reduces readability. Consider removing the return None statement.

Suggested change
return None
pass

Copilot uses AI. Check for mistakes.
scheduler_pkg.__path__ = [str(PROJECT_ROOT / "fastdeploy" / "scheduler")]
sys.modules.setdefault("fastdeploy.scheduler", scheduler_pkg)

_install_stub_modules._installed = True
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using function attributes to store state (_install_stub_modules._installed) is an anti-pattern that makes the code harder to understand and test. Consider using a module-level variable or a class-based approach for managing this state.

Copilot uses AI. Check for mistakes.
Comment on lines +502 to +508
original_sleep = self.module.time.sleep
self.module.time.sleep = lambda *_args, **_kwargs: (_ for _ in ()).throw(SystemExit())
try:
with self.assertRaises(SystemExit):
reader.run()
finally:
self.module.time.sleep = original_sleep
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach to stopping an infinite loop by monkeypatching time.sleep to throw SystemExit is fragile and obscures test intent. Consider refactoring the code under test to make it more testable (e.g., using a flag or iteration limit), or using a proper threading/timeout approach for testing background loops.

Suggested change
original_sleep = self.module.time.sleep
self.module.time.sleep = lambda *_args, **_kwargs: (_ for _ in ()).throw(SystemExit())
try:
with self.assertRaises(SystemExit):
reader.run()
finally:
self.module.time.sleep = original_sleep
# NOTE: Requires ResultReader.run to support max_iterations argument.
reader.run(max_iterations=1)

Copilot uses AI. Check for mistakes.
Comment on lines +688 to +693
class _Writer:
def __init__(self) -> None:
self.items: list[tuple[str, list[bytes]]] = []

def put(self, key: str, items: list[bytes]) -> None:
self.items.append((key, items))
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _Writer class is duplicated in multiple test methods (lines 688-693 and 721-726). This code duplication reduces maintainability. Consider extracting it as a shared test helper class at the module or class level to follow the DRY (Don't Repeat Yourself) principle.

Copilot uses AI. Check for mistakes.
self.assertEqual(picked_prefill.nodeid, "b")
self.assertEqual(picked_decode.nodeid, "d")

with self.assertRaises(Exception):
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a bare Exception in assertRaises is too broad and can mask unexpected errors. Consider using a more specific exception type that the code is expected to raise, such as ValueError or a custom exception type, to make the test more precise and maintainable.

Suggested change
with self.assertRaises(Exception):
with self.assertRaises(ValueError):

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,976 @@
"""Unit tests for :mod:`fastdeploy.scheduler.splitwise_scheduler`.
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing copyright header. All source files in this project should include the Apache License 2.0 copyright header at the top of the file, as seen in other files in the codebase.

Copilot uses AI. Check for mistakes.
return self

def __exit__(self, exc_type, exc, tb) -> None: # type: ignore[override]
return None
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __exit__ method should return None or a boolean value. While returning None explicitly works, this is typically omitted in __exit__ implementations. Consider just using pass for consistency with Python conventions.

Suggested change
return None
pass

Copilot uses AI. Check for mistakes.
def test_comparisons(self) -> None:
low = self.module.NodeInfo("a", "prefill", "h", {"transfer_protocol": ["ipc"]}, load=1)
high = self.module.NodeInfo("b", "prefill", "h", {"transfer_protocol": ["ipc"]}, load=5)
self.assertTrue(low < high)
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertTrue(a < b) cannot provide an informative message. Using assertLess(a, b) instead will give more informative messages.

Suggested change
self.assertTrue(low < high)
self.assertLess(low, high)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants