Add failing tests for #486: --quiet flag not suppressing output#489
Add failing tests for #486: --quiet flag not suppressing output#489Serhan-Asad wants to merge 7 commits intopromptdriven:mainfrom
Conversation
…en#486) Unit tests verify that preprocess(), load_prompt_template(), and llm_invoke respect the quiet flag. E2E test confirms the full CLI path still emits Rich panels and INFO logs despite --quiet. All bug-detecting tests fail on current code, confirming the bug. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Monkey-patch rich.console.Console.print, click.echo, and click.secho to suppress all non-error output when --quiet is passed. Only errors pass through; confirmations still show for safety. Also suppresses Python logging (CRITICAL level) and LiteLLM verbose output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
auto_update() makes a real network call during CliRunner.invoke(), causing 60s+ timeouts in CI. Mocking it brings test time to ~2s. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The generate command calls CloudConfig.get_jwt_token() before requests.post, triggering interactive device_flow auth in CI. Mocking it prevents the 60s timeout. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds test coverage (unit + E2E) for issue #486 where --quiet does not suppress noisy output, and introduces initial plumbing to propagate/enable quiet behavior across CLI, preprocessing, template loading, and LLM invocation.
Changes:
- Added new unit tests and an E2E test asserting
--quietsuppresses non-essential output while still surfacing errors. - Implemented quiet-mode propagation via
quietparams andPDD_QUIETenv var, plus global monkey-patching for Rich/Click output. - Adjusted logging verbosity (several
info/warning→debug) and changed error-handling expectations to always show errors.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_quiet_mode.py | New unit tests for quiet behavior in preprocess/template loading/logging and CLI generate path |
| tests/test_e2e_issue_486_quiet_flag.py | New E2E CLI test validating pdd --quiet generate suppresses known noisy strings |
| tests/test_core_errors.py | Updates expectations so errors are still displayed in quiet mode |
| tests/test_code_generator_main.py | Updates preprocess mock signature to accept quiet |
| tests/conftest.py | Adds autouse fixture attempting to reset quiet/logging global state between tests |
| pdd/quiet.py | Introduces global quiet-mode monkey-patching for Rich/Click output |
| pdd/preprocess.py | Adds quiet param + env-based quiet detection to suppress panels/messages |
| pdd/load_prompt_template.py | Adds quiet param + env-based quiet detection to suppress success/not-found messages |
| pdd/llm_invoke.py | Adds set_quiet_mode() and reduces some log noise; suppresses fallback messages in quiet |
| pdd/core/errors.py | Changes behavior to always show errors even when quiet |
| pdd/core/cli.py | Enables quiet mode by setting env var, calling set_quiet_mode, and enabling global output patching |
| pdd/code_generator_main.py | Propagates quiet into preprocess calls and suppresses some fallback notices |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tests/conftest.py
Outdated
| if old_last_resort is not None: | ||
| logging.lastResort = old_last_resort | ||
| elif logging.lastResort is None: | ||
| logging.lastResort = logging.StreamHandler() |
There was a problem hiding this comment.
If old_last_resort was None, the fixture should restore it to None, not create a new StreamHandler(). Creating a new handler changes global logging behavior and can mask/introduce logging output differences across tests. Consider logging.lastResort = old_last_resort unconditionally (or explicitly set back to None when it was None).
| if old_last_resort is not None: | |
| logging.lastResort = old_last_resort | |
| elif logging.lastResort is None: | |
| logging.lastResort = logging.StreamHandler() | |
| logging.lastResort = old_last_resort |
pdd/core/errors.py
Outdated
| elif isinstance(exception, click.UsageError): | ||
| console.print(f" [error]Usage Error:[/error] {exception}", style="error") | ||
| raise exception |
There was a problem hiding this comment.
The click.UsageError branch appears to be mis-indented relative to the surrounding elif chain (extra leading whitespace on lines 56–57). In Python, inconsistent indentation here can raise an IndentationError or change control flow. Align the block to standard 4-space indentation (8 spaces inside the elif).
| if not _quiet or (args and _is_error(args[0])): | ||
| _original_console_print(self, *args, **kwargs) | ||
|
|
There was a problem hiding this comment.
The Rich Console.print suppression only checks args[0] for error-ness. If an error indicator is in a later positional arg (or the printable is a Rich renderable like Panel containing an error), it will be suppressed even though it’s an error. Consider checking all args (e.g., any arg matches _is_error(str(arg))) and/or allowing through when kwargs indicate error styling.
| if not _quiet or (args and _is_error(args[0])): | |
| _original_console_print(self, *args, **kwargs) | |
| # Allow output if quiet mode is off | |
| if not _quiet: | |
| _original_console_print(self, *args, **kwargs) | |
| return | |
| # In quiet mode, allow through if any positional arg looks like an error | |
| if any(_is_error(arg) for arg in args): | |
| _original_console_print(self, *args, **kwargs) | |
| return | |
| # Also allow through if kwargs indicate error styling (e.g., red text) | |
| style = kwargs.get("style") | |
| if isinstance(style, str) and "red" in style: | |
| _original_console_print(self, *args, **kwargs) | |
| return | |
| # Otherwise, suppress output in quiet mode | |
| return |
| def set_quiet_mode(): | ||
| """Suppress all log output, LiteLLM verbose output, and warnings in quiet mode.""" | ||
| # Set all loggers to CRITICAL to suppress everything below | ||
| logger.setLevel(logging.CRITICAL) | ||
| litellm_logger.setLevel(logging.CRITICAL) | ||
| # Also raise handler levels so propagation doesn't leak through | ||
| for handler in logger.handlers: | ||
| handler.setLevel(logging.CRITICAL) | ||
| for handler in litellm_logger.handlers: | ||
| handler.setLevel(logging.CRITICAL) | ||
| # Suppress LiteLLM's own print-based verbose output | ||
| litellm.set_verbose = False | ||
| # Suppress the root logger and all its handlers | ||
| root = logging.getLogger() | ||
| root.setLevel(logging.CRITICAL) | ||
| for handler in root.handlers: | ||
| handler.setLevel(logging.CRITICAL) | ||
| # Disable the lastResort handler that prints WARNING: lines | ||
| logging.lastResort = None | ||
| # Suppress Python warnings (Pydantic serialization warnings, etc.) | ||
| import warnings | ||
| warnings.filterwarnings("ignore") |
There was a problem hiding this comment.
set_quiet_mode() makes several process-global changes (root logger level/handlers, logging.lastResort, and warnings.filterwarnings('ignore')). This is hard to safely undo in test runs and can cause unrelated tests to behave differently. Consider implementing a reversible quiet-mode (store/restore previous logger levels, lastResort, and warnings filters) rather than permanently mutating global state.
pdd/code_generator_main.py
Outdated
| p_output.write_text(final_content, encoding="utf-8") | ||
| if verbose or not quiet: | ||
| console.print(f"Generated code saved to: [green]{p_output.resolve()}[/green]") | ||
| click.echo(f"Generated code saved to: {p_output.resolve()}") |
There was a problem hiding this comment.
This used to be guarded by if verbose or not quiet: but now always prints. In non-CLI/programmatic usages where quiet=True is passed but global Click/Rich monkey-patching isn’t enabled, this will incorrectly emit output in quiet mode. Consider restoring a local quiet guard around this message (or route through a project-wide output helper that respects quiet).
| click.echo(f"Generated code saved to: {p_output.resolve()}") | |
| if verbose or not quiet: | |
| click.echo(f"Generated code saved to: {p_output.resolve()}") |
tests/test_quiet_mode.py
Outdated
| """ | ||
|
|
||
| import logging | ||
| from unittest.mock import patch, MagicMock, call |
There was a problem hiding this comment.
MagicMock and call are imported but not used anywhere in this new test module. Removing unused imports will reduce lint noise and keep the test file focused.
| from unittest.mock import patch, MagicMock, call | |
| from unittest.mock import patch |
| """ | ||
|
|
||
| import pytest | ||
| from unittest.mock import patch, MagicMock |
There was a problem hiding this comment.
MagicMock is imported but not used in this test file. Removing it will keep the imports clean and avoid lint warnings.
| from unittest.mock import patch, MagicMock | |
| from unittest.mock import patch |
tests/test_quiet_mode.py
Outdated
| """Tests that llm_invoke logger level is raised to WARNING when quiet=True.""" | ||
|
|
||
| def test_set_quiet_mode_raises_log_level(self): | ||
| """set_quiet_mode() should set both loggers to WARNING level.""" |
There was a problem hiding this comment.
The docstrings say set_quiet_mode() raises logger level to WARNING, but the implementation sets loggers to CRITICAL. Updating the docstrings to match the actual behavior will prevent confusion when maintaining these tests.
| """Tests that llm_invoke logger level is raised to WARNING when quiet=True.""" | |
| def test_set_quiet_mode_raises_log_level(self): | |
| """set_quiet_mode() should set both loggers to WARNING level.""" | |
| """Tests that llm_invoke logger level is raised to CRITICAL when quiet=True.""" | |
| def test_set_quiet_mode_raises_log_level(self): | |
| """set_quiet_mode() should set both loggers to CRITICAL level.""" |
- Fix lastResort restore in conftest.py (was creating new StreamHandler instead of restoring None) - Fix indentation on UsageError branch in errors.py - Restore quiet guard on "Generated code saved to" message in code_generator_main.py - Remove unused imports (MagicMock, call) from test files - Fix docstrings: say CRITICAL not WARNING to match actual log level Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Adds failing tests that detect the bug reported in #486.
Test Files
tests/test_quiet_mode.pytests/test_e2e_issue_486_quiet_flag.pyWhat This PR Contains
preprocess(),load_prompt_template(), andllm_invokequiet modepdd --quiet generateCLI pathRoot Cause
The
--quietflag is defined in the CLI but never propagated topreprocess.py,load_prompt_template.py, orllm_invoke.py. These modules unconditionally emit Rich panels, INFO logs, and status messages.Next Steps
preprocess.py,load_prompt_template.py, andllm_invoke.pyFixes #486
Generated by PDD agentic bug workflow