Add test suite for ddss/output.py - #12
Conversation
Co-authored-by: hzhangxyz <11623447+hzhangxyz@users.noreply.github.com>
Co-authored-by: hzhangxyz <11623447+hzhangxyz@users.noreply.github.com>
Co-authored-by: hzhangxyz <11623447+hzhangxyz@users.noreply.github.com>
Co-authored-by: hzhangxyz <11623447+hzhangxyz@users.noreply.github.com>
hzhangxyz
left a comment
There was a problem hiding this comment.
改用pytest吧,如果可以用pytest的特性就用其高级特性,不行就直接用pytest
Co-authored-by: hzhangxyz <11623447+hzhangxyz@users.noreply.github.com>
Converted tests to pytest with pytest-asyncio. The changes leverage:
Commit: a07d2fe |
There was a problem hiding this comment.
Pull request overview
This PR adds a comprehensive test suite for the ddss/output.py module, which continuously polls the database for Facts and Ideas entries and outputs them in a formatted way. The testing approach successfully handles the challenge of testing an infinite loop by using asyncio task cancellation.
Key Changes
- Created
tests/directory withtest_output.pycontaining 4 test cases covering Facts/Ideas formatting, multiple entries, and graceful cancellation - Added development dependencies (pytest, pytest-asyncio, pytest-cov, ruff) to pyproject.toml and uv.lock
- Tests use async fixtures and task cancellation to safely test the infinite polling loop without hanging
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| tests/test_output.py | New test file with 4 test cases for output formatting and cancellation behavior |
| pyproject.toml | Added dev optional dependencies for testing and linting tools |
| uv.lock | Updated lock file with new test dependencies (pytest, pytest-asyncio, pytest-cov, ruff, colorama, coverage, iniconfig, packaging, pluggy, pygments, tomli) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| addr = f"sqlite+aiosqlite:///{db_path.as_posix()}" | ||
| engine, session = await initialize_database(addr) | ||
| yield addr, engine, session | ||
| await engine.dispose() |
There was a problem hiding this comment.
The test fixture disposes the engine after yielding, but the main() function being tested also disposes the engine in its finally block (line 37 of output.py). This could lead to a "double dispose" situation where the engine is disposed twice, potentially causing warnings or errors.
Consider either:
- Not disposing the engine in the fixture since
main()will handle it - Adding a flag to
main()to skip engine disposal when running in test mode - Catching and ignoring any disposal errors in the fixture cleanup
| await engine.dispose() | |
| try: | |
| await engine.dispose() | |
| except Exception: | |
| # Ignore errors if the engine was already disposed (e.g., by main()). | |
| pass |
|
|
||
| # Run the main function with a timeout to avoid infinite loop | ||
| task = asyncio.create_task(main(addr, engine, session)) | ||
| await asyncio.sleep(0.2) # Give it time to process |
There was a problem hiding this comment.
The tests rely on timing with asyncio.sleep(0.2) to allow the output loop to process entries. This makes tests non-deterministic and potentially flaky, especially on slower systems or under load. The tests could fail if the system is too slow to process within 200ms, or could waste time if processing completes faster.
Consider using a more reliable synchronization mechanism, such as:
- Adding a callback or event mechanism to
main()for testing - Using a mock/spy on the print function to detect when output occurs
- Polling the output buffer until expected content appears (with a timeout)
| [project.optional-dependencies] | ||
| dev = [ | ||
| "pytest~=9.0.2", | ||
| "pytest-asyncio~=1.3.0", | ||
| "pytest-cov~=7.0.0", | ||
| "ruff~=0.14.10", | ||
| ] |
There was a problem hiding this comment.
Missing pytest-asyncio configuration in pyproject.toml. pytest-asyncio requires explicit configuration to handle async fixtures and tests properly. Without configuration, you may encounter warnings or unexpected behavior.
Add a [tool.pytest.ini_options] section with asyncio_mode configuration, for example:
[tool.pytest.ini_options]
asyncio_mode = "auto"
or
asyncio_default_fixture_loop_scope = "function"
This ensures pytest-asyncio operates in the expected mode.
| task.cancel() | ||
| try: | ||
| await task | ||
| except asyncio.CancelledError: |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| task.cancel() | ||
| try: | ||
| await task | ||
| except asyncio.CancelledError: |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| task.cancel() | ||
| try: | ||
| await task | ||
| except asyncio.CancelledError: |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
The
output.pymodule lacked test coverage. It continuously polls the database forFactsandIdeasentries, formats them withunparse()(e.g.,a\n----\nb→a => b), and prints them. Testing was non-trivial due to the infinite loop.Changes
tests/directory withtest_output.pyusing pytest and pytest-asyncio@pytest_asyncio.fixture), async test support (@pytest.mark.asyncio), and thecapsysfixture for stdout capture. Temporary databases andasyncio.Task.cancel()safely test the infinite loop.Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.