Coverage for tests/test_progress.py: 22%
146 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-02 03:16 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-02 03:16 -0700
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
29import logging
30import unittest
31from contextlib import contextmanager
33import click
34from lsst.daf.butler.cli.progress import ClickProgressHandler
35from lsst.daf.butler.cli.utils import clickResultMsg
36from lsst.daf.butler.progress import Progress, ProgressHandler
39class MockProgressBar:
40 """Mock implementation of `ProgressBar` that remembers the status it
41 would report in a list.
43 Both the initial 0 and the end-of-iterable size are reported.
45 Parameters
46 ----------
47 iterable : `Iterable`
48 Iterable to wrap, or `None`.
49 total : `int` or `None`
50 Total value passed at progress-bar construction.
51 """
53 def __init__(self, iterable, total):
54 self._iterable = iterable
55 self._current = 0
56 self.reported = [self._current]
57 self.total = total
58 MockProgressBar.last = self
60 last = None
61 """Last instance of this class that was constructed, for test code that
62 cannot access it directly via other means.
63 """
65 def __iter__(self):
66 for element in self._iterable:
67 yield element
68 self._current += 1
69 self.reported.append(self._current)
71 def update(self, n: int = 1) -> None:
72 self._current += n
73 self.reported.append(self._current)
76class MockProgressHandler(ProgressHandler):
77 """A `ProgressHandler` implementation that returns `MockProgressBar`
78 instances.
79 """
81 @contextmanager
82 def get_progress_bar(self, iterable, desc, total, level):
83 yield MockProgressBar(iterable, total=total)
86class ClickProgressHandlerTestCase(unittest.TestCase):
87 """Test enabling and disabling progress in click commands.
89 It looks like click's testing harness doesn't ever actually let its
90 progress bar generate output, so the best we can do is check that using it
91 doesn't raise exceptions, and see if it looks like we're doing something
92 based on what our own progress-object state is.
93 """
95 def setUp(self):
96 # Set up a mock handler by default. Tests of click behavior will
97 # rely on this when they check that inside a click command we never
98 # end up with that mock.
99 self.logger = logging.getLogger("test_progress")
100 self.logger.setLevel(logging.INFO)
101 Progress.set_handler(MockProgressHandler())
102 self.runner = click.testing.CliRunner()
104 def tearDown(self):
105 MockProgressHandler.last = None
106 Progress.set_handler(None)
107 self.logger.setLevel(logging.NOTSET)
109 def get_cmd(self, level, enabled):
110 """Return a click command that uses a progress bar and tests that it
111 is or not enabled, as given.
112 """
114 @click.command()
115 @ClickProgressHandler.option
116 def cmd(progress):
117 p = Progress("test_progress", level=level)
118 with p.bar(range(5), desc="testing!") as bar:
119 self.assertFalse(isinstance(bar, MockProgressBar))
120 r = list(bar)
121 self.assertEqual(r, list(range(5)))
122 self.assertEqual(enabled, p.is_enabled())
124 return cmd
126 def test_click_disabled_by_default(self):
127 """Test that progress is disabled by default in click commands."""
128 result = self.runner.invoke(
129 self.get_cmd(logging.INFO, enabled=False),
130 [],
131 )
132 self.assertEqual(result.exit_code, 0, clickResultMsg(result))
134 def test_click_enabled(self):
135 """Test turning on progress in click commands."""
136 result = self.runner.invoke(
137 self.get_cmd(logging.INFO, enabled=True),
138 ["--progress"],
139 )
140 self.assertEqual(result.exit_code, 0, clickResultMsg(result))
142 def test_click_disabled_globally(self):
143 """Test turning on progress in click commands."""
144 result = self.runner.invoke(
145 self.get_cmd(logging.INFO, enabled=False),
146 ["--no-progress"],
147 )
148 self.assertEqual(result.exit_code, 0, clickResultMsg(result))
150 def test_click_disabled_by_log_level(self):
151 """Test that progress reports below the current log level are disabled,
152 even if progress is globally enabled.
153 """
154 result = self.runner.invoke(
155 self.get_cmd(logging.DEBUG, enabled=False),
156 ["--progress"],
157 )
158 self.assertEqual(result.exit_code, 0, clickResultMsg(result))
161class MockedProgressHandlerTestCase(unittest.TestCase):
162 """Test that the interface layer for progress reporting works by using
163 mock handler and progress bar objects.
164 """
166 def setUp(self):
167 self.logger = logging.getLogger("test_progress")
168 self.logger.setLevel(logging.INFO)
169 Progress.set_handler(MockProgressHandler())
170 self.progress = Progress("test_progress")
172 def tearDown(self):
173 MockProgressHandler.last = None
174 Progress.set_handler(None)
175 self.logger.setLevel(logging.NOTSET)
177 def test_bar_iterable(self):
178 """Test using `Progress.bar` to wrap an iterable."""
179 iterable = list(range(5))
180 with self.progress.bar(iterable) as bar:
181 r = list(bar)
182 self.assertEqual(r, iterable)
183 self.assertEqual(iterable + [len(iterable)], bar.reported)
185 def test_bar_update(self):
186 """Test using `Progress.bar` with manual updates."""
187 with self.progress.bar(total=10) as bar:
188 for _ in range(5):
189 bar.update(2)
190 self.assertEqual(list(range(0, 12, 2)), bar.reported)
192 def test_iter_chunks_fully_sized(self):
193 """Test using `Progress.iter_chunks` with a sized iterable of sized
194 chunks.
195 """
196 iterable = [list(range(2)), list(range(3))]
197 seen = []
198 for chunk in self.progress.iter_chunks(iterable):
199 seen.extend(chunk)
200 self.assertEqual(seen, iterable[0] + iterable[1])
201 self.assertEqual(MockProgressBar.last.reported, [0, 2, 5])
202 self.assertEqual(MockProgressBar.last.total, 5)
204 def test_iter_chunks_with_total(self):
205 """Test using `Progress.iter_chunks` with total provided and
206 sized chunks.
207 """
208 iterable = [list(range(2)), list(range(3))]
209 seen = []
210 for chunk in self.progress.iter_chunks(iter(iterable), total=5):
211 seen.extend(chunk)
212 self.assertEqual(seen, iterable[0] + iterable[1])
213 self.assertEqual(MockProgressBar.last.reported, [0, 2, 5])
214 self.assertEqual(MockProgressBar.last.total, 5)
216 def test_iter_chunks_total_false(self):
217 """Test using `Progress.iter_chunks` with total=False and non-sized
218 chunks. This should display progress with the number of
219 chunks.
220 """
221 iterable = [iter(range(2)), iter(range(3))]
222 seen = []
223 for chunk in self.progress.iter_chunks(iterable, total=False):
224 seen.extend(chunk)
225 self.assertEqual(seen, list(range(2)) + list(range(3)))
226 self.assertEqual(MockProgressBar.last.reported, [0, 1, 2])
227 self.assertEqual(MockProgressBar.last.total, 2)
229 def test_iter_chunks_not_sized(self):
230 """Test using `Progress.iter_chunks` with an unsized iterable."""
231 iterable = [iter(range(2)), iter(range(3))]
232 seen = []
233 for chunk in self.progress.iter_chunks(iter(iterable)):
234 seen.extend(chunk)
235 self.assertEqual(seen, list(range(2)) + list(range(3)))
236 self.assertEqual(MockProgressBar.last.reported, [0, 1, 2])
237 self.assertEqual(MockProgressBar.last.total, None)
239 def test_iter_item_chunks_fully_sized(self):
240 """Test using `Progress.iter_item_chunks` with a sized iterable of
241 sized chunks.
242 """
243 mapping = {"x": list(range(2)), "y": list(range(3))}
244 seen = {}
245 for key, chunk in self.progress.iter_item_chunks(mapping.items()):
246 seen[key] = chunk
247 self.assertEqual(seen, mapping)
248 self.assertEqual(MockProgressBar.last.reported, [0, 2, 5])
249 self.assertEqual(MockProgressBar.last.total, 5)
251 def test_iter_item_chunks_with_total(self):
252 """Test using `Progress.iter_item_chunks` with total provided and
253 sized chunks.
254 """
255 mapping = {"x": list(range(2)), "y": list(range(3))}
256 seen = {}
257 for key, chunk in self.progress.iter_item_chunks(iter(mapping.items()), total=5):
258 seen[key] = chunk
259 self.assertEqual(seen, mapping)
260 self.assertEqual(MockProgressBar.last.reported, [0, 2, 5])
261 self.assertEqual(MockProgressBar.last.total, 5)
263 def test_iter_item_chunks_total_false(self):
264 """Test using `Progress.iter_item_chunks` with total=False and
265 non-sized chunks. This should display progress with the number of
266 chunks.
267 """
268 mapping = {"x": iter(range(2)), "y": iter(range(3))}
269 seen = {}
270 for key, chunk in self.progress.iter_item_chunks(mapping.items(), total=False):
271 seen[key] = list(chunk)
272 self.assertEqual(seen, {"x": list(range(2)), "y": list(range(3))})
273 self.assertEqual(MockProgressBar.last.reported, [0, 1, 2])
274 self.assertEqual(MockProgressBar.last.total, 2)
276 def test_iter_item_chunks_not_sized(self):
277 """Test using `Progress.iter_item_chunks` with an unsized iterable of
278 non-sized chunks.
279 """
280 mapping = {"x": iter(range(2)), "y": iter(range(3))}
281 seen = {}
282 for key, chunk in self.progress.iter_item_chunks(iter(mapping.items())):
283 seen[key] = list(chunk)
284 self.assertEqual(seen, {"x": list(range(2)), "y": list(range(3))})
285 self.assertEqual(MockProgressBar.last.reported, [0, 1, 2])
286 self.assertEqual(MockProgressBar.last.total, None)
289if __name__ == "__main__":
290 unittest.main()