-
-
Notifications
You must be signed in to change notification settings - Fork 106
Expand file tree
/
Copy pathtest_needs_update.py
More file actions
93 lines (76 loc) · 2.78 KB
/
Copy pathtest_needs_update.py
File metadata and controls
93 lines (76 loc) · 2.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
"""`_needs_update` guard behavior.
The width-threshold computation used to run inside
``contextlib.suppress(Exception)``, so any unexpected failure silently
disabled redraws. The known-legitimate incomplete states (no value drawn
yet, no terminal width, zero max value) must still return False exactly
as before, while genuinely unexpected errors now propagate.
"""
from __future__ import annotations
import io
import typing
import pytest
import progressbar
import progressbar.utils
@pytest.fixture(autouse=True)
def _deregister_started_bars() -> typing.Iterator[None]:
# start() registers each bar as a global capture listener (so writes to
# the wrapped stream redraw it). These tests deliberately drive bars into
# invalid states -- e.g. a non-numeric term_width -- and never finish()
# them. Deregister whatever this test started so a poisoned bar cannot be
# update()d when a *later* test writes a newline to the captured stream.
streams = progressbar.utils.streams
before = set(streams.listeners)
yield
for bar in list(streams.listeners - before):
streams.stop_capturing(bar)
def _bar(**kwargs: typing.Any) -> progressbar.ProgressBar:
bar = progressbar.ProgressBar(
fd=io.StringIO(),
max_value=100,
term_width=20,
**kwargs,
)
bar.start()
bar.update(50, force=True)
# Move the rate limiters out of the way (the constructor substitutes a
# default for poll_interval=None) so the width-threshold branch decides.
bar.poll_interval = None
bar._last_update_timer = -1e9
return bar
def test_needs_update_crossing_width_threshold() -> None:
bar = _bar()
bar.value = 90
assert bar._needs_update() is True
def test_needs_update_within_same_width_bucket() -> None:
bar = _bar()
assert bar._last_drawn_value is not None
bar.value = bar._last_drawn_value
assert bar._needs_update() is False
@pytest.mark.parametrize(
'attribute, incomplete_value',
[
('value', None),
('_last_drawn_value', None),
('term_width', None),
('term_width', 0),
('max_value', None),
('max_value', 0),
],
)
def test_needs_update_incomplete_state_is_false(
attribute: str,
incomplete_value: typing.Any,
) -> None:
# Each of these used to raise inside the suppress() and fall through
# to False; the explicit guards must preserve that result.
bar = _bar()
bar.value = 90
setattr(bar, attribute, incomplete_value)
assert bar._needs_update() is False
def test_needs_update_unexpected_error_propagates() -> None:
# A genuinely wrong type is a bug and must no longer pass silently.
bar = _bar()
bar.value = 90
bar.term_width = 'wide' # type: ignore[assignment]
with pytest.raises(TypeError):
bar._needs_update()