Skip to content

Commit e1a69fd

Browse files
committed
100% test coverage for output.py
1 parent 9b7a7e1 commit e1a69fd

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Internal
55
--------
66
* Improve test coverage for `completion_refresher.py`.
77
* Add test coverage for `client_query.py`.
8+
* Improve test coverage for `output.py`.
89

910

1011
1.74.0 (2026/06/06)

test/pytests/test_output.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import itertools
44
import shutil
5+
from types import SimpleNamespace
56
from typing import Any, cast
67

78
import click
@@ -93,6 +94,24 @@ def test_get_output_margin_renders_prompt_once_and_counts_status_lines(monkeypat
9394
assert cli.prompt_lines == 2
9495

9596

97+
def test_get_output_margin_uses_prompt_session_render_counter(monkeypatch: pytest.MonkeyPatch) -> None:
98+
cli = make_bare_mycli()
99+
cli.prompt_lines = 0
100+
cli.prompt_session = cast(Any, SimpleNamespace(app=SimpleNamespace(render_counter=9)))
101+
cli.get_reserved_space = lambda: 1 # type: ignore[assignment]
102+
render_counters: list[int] = []
103+
104+
def render_prompt_string(_cli: Any, _prompt_format: str, render_counter: int) -> FormattedText:
105+
render_counters.append(render_counter)
106+
return FormattedText([('', 'prompt')])
107+
108+
monkeypatch.setattr(output_module.repl_mode, 'render_prompt_string', render_prompt_string)
109+
monkeypatch.setattr(output_module.special, 'is_timing_enabled', lambda: False)
110+
111+
assert OutputMixin.get_output_margin(cli) == 2
112+
assert render_counters == [9]
113+
114+
96115
def test_output_writes_lines_sinks_and_status(monkeypatch: pytest.MonkeyPatch) -> None:
97116
cli = make_bare_mycli()
98117
cli.prompt_session = None
@@ -124,6 +143,84 @@ def test_output_writes_lines_sinks_and_status(monkeypatch: pytest.MonkeyPatch) -
124143
assert list(printed_status[0])[0][0].strip() == 'class:output.status'
125144

126145

146+
def test_output_uses_prompt_session_size(monkeypatch: pytest.MonkeyPatch) -> None:
147+
cli = make_bare_mycli()
148+
cli.prompt_session = cast(
149+
Any,
150+
SimpleNamespace(output=SimpleNamespace(get_size=lambda: SimpleNamespace(columns=80, rows=24))),
151+
)
152+
cli.explicit_pager = False
153+
cli.log_output = lambda value: None # type: ignore[assignment]
154+
cli.get_output_margin = lambda status=None: 1 # type: ignore[assignment]
155+
printed_lines: list[str] = []
156+
monkeypatch.setattr(output_module.special, 'write_tee', lambda value: None)
157+
monkeypatch.setattr(output_module.special, 'write_once', lambda value: None)
158+
monkeypatch.setattr(output_module.special, 'write_pipe_once', lambda value: None)
159+
monkeypatch.setattr(output_module.special, 'is_redirected', lambda: False)
160+
monkeypatch.setattr(output_module.special, 'is_pager_enabled', lambda: False)
161+
monkeypatch.setattr(click, 'secho', lambda value, **_kwargs: printed_lines.append(value))
162+
163+
OutputMixin.output(cli, itertools.chain(['row']), SQLResult())
164+
165+
assert printed_lines == ['row']
166+
167+
168+
def test_output_flushes_buffer_when_content_does_not_fit(monkeypatch: pytest.MonkeyPatch) -> None:
169+
cli = make_bare_mycli()
170+
cli.prompt_session = None
171+
cli.explicit_pager = False
172+
cli.log_output = lambda value: None # type: ignore[assignment]
173+
cli.get_output_margin = lambda status=None: output_module.DEFAULT_HEIGHT # type: ignore[assignment]
174+
printed_lines: list[str] = []
175+
monkeypatch.setattr(output_module.special, 'write_tee', lambda value: None)
176+
monkeypatch.setattr(output_module.special, 'write_once', lambda value: None)
177+
monkeypatch.setattr(output_module.special, 'write_pipe_once', lambda value: None)
178+
monkeypatch.setattr(output_module.special, 'is_redirected', lambda: False)
179+
monkeypatch.setattr(output_module.special, 'is_pager_enabled', lambda: False)
180+
monkeypatch.setattr(click, 'secho', lambda value, **_kwargs: printed_lines.append(value))
181+
182+
OutputMixin.output(cli, itertools.chain(['row 1', 'row 2']), SQLResult())
183+
184+
assert printed_lines == ['row 1', 'row 2']
185+
186+
187+
def test_output_switches_to_pager_when_content_does_not_fit(monkeypatch: pytest.MonkeyPatch) -> None:
188+
cli = make_bare_mycli()
189+
cli.prompt_session = None
190+
cli.explicit_pager = False
191+
cli.log_output = lambda value: None # type: ignore[assignment]
192+
cli.get_output_margin = lambda status=None: output_module.DEFAULT_HEIGHT # type: ignore[assignment]
193+
paged_lines: list[str] = []
194+
monkeypatch.setattr(output_module.special, 'write_tee', lambda value: None)
195+
monkeypatch.setattr(output_module.special, 'write_once', lambda value: None)
196+
monkeypatch.setattr(output_module.special, 'write_pipe_once', lambda value: None)
197+
monkeypatch.setattr(output_module.special, 'is_redirected', lambda: False)
198+
monkeypatch.setattr(output_module.special, 'is_pager_enabled', lambda: True)
199+
monkeypatch.setattr(click, 'echo_via_pager', lambda values: paged_lines.extend(list(values)))
200+
201+
OutputMixin.output(cli, itertools.chain(['row']), SQLResult())
202+
203+
assert paged_lines == ['row\n']
204+
205+
206+
def test_output_redirected_skips_screen_printing(monkeypatch: pytest.MonkeyPatch) -> None:
207+
cli = make_bare_mycli()
208+
cli.prompt_session = None
209+
cli.log_output = lambda value: None # type: ignore[assignment]
210+
cli.get_output_margin = lambda status=None: 1 # type: ignore[assignment]
211+
printed_lines: list[str] = []
212+
monkeypatch.setattr(output_module.special, 'write_tee', lambda value: None)
213+
monkeypatch.setattr(output_module.special, 'write_once', lambda value: None)
214+
monkeypatch.setattr(output_module.special, 'write_pipe_once', lambda value: None)
215+
monkeypatch.setattr(output_module.special, 'is_redirected', lambda: True)
216+
monkeypatch.setattr(output_module.special, 'is_pager_enabled', lambda: False)
217+
monkeypatch.setattr(click, 'secho', lambda value, **_kwargs: printed_lines.append(value))
218+
219+
OutputMixin.output(cli, itertools.chain(['row']), SQLResult())
220+
221+
assert printed_lines == []
222+
223+
127224
def test_output_uses_warning_status_style(monkeypatch: pytest.MonkeyPatch) -> None:
128225
cli = make_bare_mycli()
129226
cli.log_output = lambda value: None # type: ignore[assignment]
@@ -136,6 +233,19 @@ def test_output_uses_warning_status_style(monkeypatch: pytest.MonkeyPatch) -> No
136233
assert list(printed_status[0])[0][0].strip() == 'class:warnings.status'
137234

138235

236+
def test_output_preserves_formatted_status(monkeypatch: pytest.MonkeyPatch) -> None:
237+
cli = make_bare_mycli()
238+
cli.log_output = lambda value: None # type: ignore[assignment]
239+
cli.get_output_margin = lambda status=None: 1 # type: ignore[assignment]
240+
printed_status: list[Any] = []
241+
monkeypatch.setattr(prompt_toolkit, 'print_formatted_text', lambda text, style=None: printed_status.append(text))
242+
status = FormattedText([('class:custom', 'formatted')])
243+
244+
OutputMixin.output(cli, itertools.chain([]), SQLResult(status=status))
245+
246+
assert to_plain_text(printed_status[0]) == 'formatted'
247+
248+
139249
def test_output_sends_buffer_to_pager_when_pager_is_explicit(monkeypatch: pytest.MonkeyPatch) -> None:
140250
cli = make_bare_mycli()
141251
cli.prompt_session = None
@@ -156,6 +266,22 @@ def test_output_sends_buffer_to_pager_when_pager_is_explicit(monkeypatch: pytest
156266
assert paged_lines == ['row 1\n', 'row 2\n']
157267

158268

269+
def test_configure_pager_uses_more_for_missing_less_on_windows(monkeypatch: pytest.MonkeyPatch) -> None:
270+
cli = make_bare_mycli()
271+
cli.my_cnf = ConfigObj({'client': {'pager': 'less'}})
272+
cli.config = ConfigObj({'main': {'pager': '', 'enable_pager': 'True'}})
273+
cli.read_my_cnf = lambda cnf, keys: {'pager': 'less', 'skip-pager': None} # type: ignore[assignment]
274+
pager_calls: list[str] = []
275+
monkeypatch.setattr(output_module, 'WIN', True)
276+
monkeypatch.setattr(output_module.shutil, 'which', lambda value: None)
277+
monkeypatch.setattr(output_module.special, 'set_pager', lambda value: pager_calls.append(value))
278+
monkeypatch.setattr(output_module.special, 'disable_pager', lambda: None)
279+
280+
OutputMixin.configure_pager(cli)
281+
282+
assert pager_calls == ['more']
283+
284+
159285
def test_configure_pager_prefers_my_cnf_pager_and_sets_less(monkeypatch: pytest.MonkeyPatch) -> None:
160286
cli = make_bare_mycli()
161287
cli.my_cnf = ConfigObj({'client': {'pager': 'my-pager'}})
@@ -203,6 +329,17 @@ def test_format_sqlresult_uses_redirect_formatter_and_appends_preamble_postamble
203329
assert cli.redirect_formatter.calls
204330

205331

332+
def test_format_sqlresult_uses_null_string_when_default_missing_value_is_configured() -> None:
333+
cli = make_bare_mycli()
334+
cli.main_formatter = DummyFormatter()
335+
result = SQLResult(header=['id'], rows=[(None,)])
336+
337+
list(OutputMixin.format_sqlresult(cli, result, null_string='<null>'))
338+
339+
_, kwargs = cli.main_formatter.calls[-1]
340+
assert kwargs['missing_value'] == '<null>'
341+
342+
206343
def test_format_sqlresult_for_cursor_sets_column_types_and_alignment(monkeypatch: pytest.MonkeyPatch) -> None:
207344
cli = make_bare_mycli()
208345
cli.main_formatter = DummyFormatter()
@@ -217,6 +354,42 @@ def test_format_sqlresult_for_cursor_sets_column_types_and_alignment(monkeypatch
217354
assert kwargs['colalign'] == ['left', 'left']
218355

219356

357+
def test_format_sqlresult_for_empty_cursor_uses_empty_column_metadata(monkeypatch: pytest.MonkeyPatch) -> None:
358+
cli = make_bare_mycli()
359+
cli.main_formatter = DummyFormatter()
360+
monkeypatch.setattr(output_module, 'Cursor', FakeCursorBase)
361+
rows = FakeCursorBase(rowcount=0, description=[('id', 3)])
362+
result = SQLResult(header=['id'], rows=cast(Any, rows))
363+
364+
list(OutputMixin.format_sqlresult(cli, result))
365+
366+
_, kwargs = cli.main_formatter.calls[-1]
367+
assert kwargs['column_types'] == []
368+
assert kwargs['colalign'] == []
369+
370+
371+
def test_format_sqlresult_materializes_cursor_rows_when_width_is_limited(monkeypatch: pytest.MonkeyPatch) -> None:
372+
cli = make_bare_mycli()
373+
cli.main_formatter = DummyFormatter()
374+
monkeypatch.setattr(output_module, 'Cursor', FakeCursorBase)
375+
rows = FakeCursorBase(rows=[(1,)], rowcount=1, description=[('id', 3)])
376+
result = SQLResult(header=['id'], rows=cast(Any, rows))
377+
378+
list(OutputMixin.format_sqlresult(cli, result, max_width=100))
379+
380+
formatted_rows = cli.main_formatter.calls[-1][0][0]
381+
assert formatted_rows == [(1,)]
382+
383+
384+
def test_format_sqlresult_splits_string_formatter_output() -> None:
385+
cli = make_bare_mycli()
386+
cli.main_formatter = DummyFormatter()
387+
cli.main_formatter.format_output = lambda *args, **kwargs: 'one\ntwo' # type: ignore[method-assign]
388+
result = SQLResult(header=['id'], rows=[(1,)])
389+
390+
assert list(OutputMixin.format_sqlresult(cli, result)) == ['one', 'two']
391+
392+
220393
def test_format_sqlresult_switches_to_vertical_when_first_line_is_too_wide() -> None:
221394
cli = make_bare_mycli()
222395
cli.main_formatter = DummyFormatter()
@@ -225,6 +398,29 @@ def test_format_sqlresult_switches_to_vertical_when_first_line_is_too_wide() ->
225398
assert list(OutputMixin.format_sqlresult(cli, result, max_width=2)) == ['vertical output']
226399

227400

401+
def test_format_sqlresult_splits_string_vertical_output_when_table_is_too_wide() -> None:
402+
cli = make_bare_mycli()
403+
cli.main_formatter = DummyFormatter()
404+
405+
def format_output(rows, header, format_name=None, **kwargs):
406+
if format_name == 'vertical':
407+
return 'vertical one\nvertical two'
408+
return ['too wide']
409+
410+
cli.main_formatter.format_output = format_output # type: ignore[method-assign]
411+
result = SQLResult(header=['id'], rows=[(1,)])
412+
413+
assert list(OutputMixin.format_sqlresult(cli, result, max_width=2)) == ['vertical one', 'vertical two']
414+
415+
416+
def test_format_sqlresult_keeps_table_when_first_line_fits_width() -> None:
417+
cli = make_bare_mycli()
418+
cli.main_formatter = DummyFormatter()
419+
result = SQLResult(header=['id'], rows=[(1,)])
420+
421+
assert list(OutputMixin.format_sqlresult(cli, result, max_width=100)) == ['plain output']
422+
423+
228424
def test_get_reserved_space_caps_ratio(monkeypatch: pytest.MonkeyPatch) -> None:
229425
cli = make_bare_mycli()
230426
monkeypatch.setattr(shutil, 'get_terminal_size', lambda *args, **kwargs: (120, 40))

0 commit comments

Comments
 (0)