Coverage for tests/test_progress.py: 34%
97 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-28 09:25 +0000
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-28 09:25 +0000
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 program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
23import logging
24import unittest
25from contextlib import contextmanager
27import click
28from lsst.daf.butler.cli.progress import ClickProgressHandler
29from lsst.daf.butler.cli.utils import clickResultMsg
30from lsst.daf.butler.core.progress import Progress, ProgressHandler
33class MockProgressBar:
34 """Mock implementation of `ProgressBar` that remembers the status it
35 would report in a list.
37 Both the initial 0 and the end-of-iterable size are reported.
39 Parameters
40 ----------
41 iterable : `Iterable`, optional
42 Iterable to wrap, or `None`.
43 """
45 def __init__(self, iterable):
46 self._iterable = iterable
47 self._current = 0
48 self.reported = [self._current]
49 MockProgressBar.last = self
51 last = None
52 """Last instance of this class that was constructed, for test code that
53 cannot access it directly via other means.
54 """
56 def __iter__(self):
57 for element in self._iterable:
58 yield element
59 self._current += 1
60 self.reported.append(self._current)
62 def update(self, n: int = 1) -> None:
63 self._current += n
64 self.reported.append(self._current)
67class MockProgressHandler(ProgressHandler):
68 """A `ProgressHandler` implementation that returns `MockProgressBar`
69 instances.
70 """
72 @contextmanager
73 def get_progress_bar(self, iterable, desc, total, level):
74 yield MockProgressBar(iterable)
77class ClickProgressHandlerTestCase(unittest.TestCase):
78 """Test enabling and disabling progress in click commands.
80 It looks like click's testing harness doesn't ever actually let its
81 progress bar generate output, so the best we can do is check that using it
82 doesn't raise exceptions, and see if it looks like we're doing something
83 based on what our own progress-object state is.
84 """
86 def setUp(self):
87 # Set up a mock handler by default. Tests of click behavior will
88 # rely on this when they check that inside a click command we never
89 # end up with that mock.
90 self.logger = logging.getLogger("test_progress")
91 self.logger.setLevel(logging.INFO)
92 Progress.set_handler(MockProgressHandler())
93 self.runner = click.testing.CliRunner()
95 def tearDown(self):
96 MockProgressHandler.last = None
97 Progress.set_handler(None)
98 self.logger.setLevel(logging.NOTSET)
100 def get_cmd(self, level, enabled):
101 """Return a click command that uses a progress bar and tests that it
102 is or not enabled, as given.
103 """
105 @click.command()
106 @ClickProgressHandler.option
107 def cmd(progress):
108 p = Progress("test_progress", level=level)
109 with p.bar(range(5), desc="testing!") as bar:
110 self.assertFalse(isinstance(bar, MockProgressBar))
111 r = list(bar)
112 self.assertEqual(r, list(range(5)))
113 self.assertEqual(enabled, p.is_enabled())
115 return cmd
117 def test_click_disabled_by_default(self):
118 """Test that progress is disabled by default in click commands."""
119 result = self.runner.invoke(
120 self.get_cmd(logging.INFO, enabled=False),
121 [],
122 )
123 self.assertEqual(result.exit_code, 0, clickResultMsg(result))
125 def test_click_enabled(self):
126 """Test turning on progress in click commands."""
127 result = self.runner.invoke(
128 self.get_cmd(logging.INFO, enabled=True),
129 ["--progress"],
130 )
131 self.assertEqual(result.exit_code, 0, clickResultMsg(result))
133 def test_click_disabled_globally(self):
134 """Test turning on progress in click commands."""
135 result = self.runner.invoke(
136 self.get_cmd(logging.INFO, enabled=False),
137 ["--no-progress"],
138 )
139 self.assertEqual(result.exit_code, 0, clickResultMsg(result))
141 def test_click_disabled_by_log_level(self):
142 """Test that progress reports below the current log level are disabled,
143 even if progress is globally enabled.
144 """
145 result = self.runner.invoke(
146 self.get_cmd(logging.DEBUG, enabled=False),
147 ["--progress"],
148 )
149 self.assertEqual(result.exit_code, 0, clickResultMsg(result))
152class MockedProgressHandlerTestCase(unittest.TestCase):
153 """Test that the interface layer for progress reporting works by using
154 mock handler and progress bar objects.
155 """
157 def setUp(self):
158 self.logger = logging.getLogger("test_progress")
159 self.logger.setLevel(logging.INFO)
160 Progress.set_handler(MockProgressHandler())
161 self.progress = Progress("test_progress")
163 def tearDown(self):
164 MockProgressHandler.last = None
165 Progress.set_handler(None)
166 self.logger.setLevel(logging.NOTSET)
168 def test_bar_iterable(self):
169 """Test using `Progress.bar` to wrap an iterable."""
170 iterable = list(range(5))
171 with self.progress.bar(iterable) as bar:
172 r = list(bar)
173 self.assertEqual(r, iterable)
174 self.assertEqual(iterable + [len(iterable)], bar.reported)
176 def test_bar_update(self):
177 """Test using `Progress.bar` with manual updates."""
178 with self.progress.bar(total=10) as bar:
179 for i in range(5):
180 bar.update(2)
181 self.assertEqual(list(range(0, 12, 2)), bar.reported)
183 def test_iter_chunks(self):
184 """Test using `Progress.iter_chunks`."""
185 iterable = [list(range(2)), list(range(3))]
186 seen = []
187 for chunk in self.progress.iter_chunks(iterable):
188 seen.extend(chunk)
189 self.assertEqual(seen, iterable[0] + iterable[1])
190 self.assertEqual(MockProgressBar.last.reported, [0, 2, 5])
192 def test_iter_item_chunks(self):
193 """Test using `Progress.iter_item_chunks`."""
194 mapping = {"x": list(range(2)), "y": list(range(3))}
195 seen = {}
196 for key, chunk in self.progress.iter_item_chunks(mapping.items()):
197 seen[key] = chunk
198 self.assertEqual(seen, mapping)
199 self.assertEqual(MockProgressBar.last.reported, [0, 2, 5])
202if __name__ == "__main__": 202 ↛ 203line 202 didn't jump to line 203, because the condition on line 202 was never true
203 unittest.main()