Coverage for tests/test_progress.py: 22%

146 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-08-05 01:26 +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/>. 

21 

22 

23import logging 

24import unittest 

25from contextlib import contextmanager 

26 

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 

31 

32 

33class MockProgressBar: 

34 """Mock implementation of `ProgressBar` that remembers the status it 

35 would report in a list. 

36 

37 Both the initial 0 and the end-of-iterable size are reported. 

38 

39 Parameters 

40 ---------- 

41 iterable : `Iterable` 

42 Iterable to wrap, or `None`. 

43 total : `int` or `None` 

44 Total value passed at progress-bar construction. 

45 """ 

46 

47 def __init__(self, iterable, total): 

48 self._iterable = iterable 

49 self._current = 0 

50 self.reported = [self._current] 

51 self.total = total 

52 MockProgressBar.last = self 

53 

54 last = None 

55 """Last instance of this class that was constructed, for test code that 

56 cannot access it directly via other means. 

57 """ 

58 

59 def __iter__(self): 

60 for element in self._iterable: 

61 yield element 

62 self._current += 1 

63 self.reported.append(self._current) 

64 

65 def update(self, n: int = 1) -> None: 

66 self._current += n 

67 self.reported.append(self._current) 

68 

69 

70class MockProgressHandler(ProgressHandler): 

71 """A `ProgressHandler` implementation that returns `MockProgressBar` 

72 instances. 

73 """ 

74 

75 @contextmanager 

76 def get_progress_bar(self, iterable, desc, total, level): 

77 yield MockProgressBar(iterable, total=total) 

78 

79 

80class ClickProgressHandlerTestCase(unittest.TestCase): 

81 """Test enabling and disabling progress in click commands. 

82 

83 It looks like click's testing harness doesn't ever actually let its 

84 progress bar generate output, so the best we can do is check that using it 

85 doesn't raise exceptions, and see if it looks like we're doing something 

86 based on what our own progress-object state is. 

87 """ 

88 

89 def setUp(self): 

90 # Set up a mock handler by default. Tests of click behavior will 

91 # rely on this when they check that inside a click command we never 

92 # end up with that mock. 

93 self.logger = logging.getLogger("test_progress") 

94 self.logger.setLevel(logging.INFO) 

95 Progress.set_handler(MockProgressHandler()) 

96 self.runner = click.testing.CliRunner() 

97 

98 def tearDown(self): 

99 MockProgressHandler.last = None 

100 Progress.set_handler(None) 

101 self.logger.setLevel(logging.NOTSET) 

102 

103 def get_cmd(self, level, enabled): 

104 """Return a click command that uses a progress bar and tests that it 

105 is or not enabled, as given. 

106 """ 

107 

108 @click.command() 

109 @ClickProgressHandler.option 

110 def cmd(progress): 

111 p = Progress("test_progress", level=level) 

112 with p.bar(range(5), desc="testing!") as bar: 

113 self.assertFalse(isinstance(bar, MockProgressBar)) 

114 r = list(bar) 

115 self.assertEqual(r, list(range(5))) 

116 self.assertEqual(enabled, p.is_enabled()) 

117 

118 return cmd 

119 

120 def test_click_disabled_by_default(self): 

121 """Test that progress is disabled by default in click commands.""" 

122 result = self.runner.invoke( 

123 self.get_cmd(logging.INFO, enabled=False), 

124 [], 

125 ) 

126 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

127 

128 def test_click_enabled(self): 

129 """Test turning on progress in click commands.""" 

130 result = self.runner.invoke( 

131 self.get_cmd(logging.INFO, enabled=True), 

132 ["--progress"], 

133 ) 

134 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

135 

136 def test_click_disabled_globally(self): 

137 """Test turning on progress in click commands.""" 

138 result = self.runner.invoke( 

139 self.get_cmd(logging.INFO, enabled=False), 

140 ["--no-progress"], 

141 ) 

142 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

143 

144 def test_click_disabled_by_log_level(self): 

145 """Test that progress reports below the current log level are disabled, 

146 even if progress is globally enabled. 

147 """ 

148 result = self.runner.invoke( 

149 self.get_cmd(logging.DEBUG, enabled=False), 

150 ["--progress"], 

151 ) 

152 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

153 

154 

155class MockedProgressHandlerTestCase(unittest.TestCase): 

156 """Test that the interface layer for progress reporting works by using 

157 mock handler and progress bar objects. 

158 """ 

159 

160 def setUp(self): 

161 self.logger = logging.getLogger("test_progress") 

162 self.logger.setLevel(logging.INFO) 

163 Progress.set_handler(MockProgressHandler()) 

164 self.progress = Progress("test_progress") 

165 

166 def tearDown(self): 

167 MockProgressHandler.last = None 

168 Progress.set_handler(None) 

169 self.logger.setLevel(logging.NOTSET) 

170 

171 def test_bar_iterable(self): 

172 """Test using `Progress.bar` to wrap an iterable.""" 

173 iterable = list(range(5)) 

174 with self.progress.bar(iterable) as bar: 

175 r = list(bar) 

176 self.assertEqual(r, iterable) 

177 self.assertEqual(iterable + [len(iterable)], bar.reported) 

178 

179 def test_bar_update(self): 

180 """Test using `Progress.bar` with manual updates.""" 

181 with self.progress.bar(total=10) as bar: 

182 for _ in range(5): 

183 bar.update(2) 

184 self.assertEqual(list(range(0, 12, 2)), bar.reported) 

185 

186 def test_iter_chunks_fully_sized(self): 

187 """Test using `Progress.iter_chunks` with a sized iterable of sized 

188 chunks. 

189 """ 

190 iterable = [list(range(2)), list(range(3))] 

191 seen = [] 

192 for chunk in self.progress.iter_chunks(iterable): 

193 seen.extend(chunk) 

194 self.assertEqual(seen, iterable[0] + iterable[1]) 

195 self.assertEqual(MockProgressBar.last.reported, [0, 2, 5]) 

196 self.assertEqual(MockProgressBar.last.total, 5) 

197 

198 def test_iter_chunks_with_total(self): 

199 """Test using `Progress.iter_chunks` with total provided and 

200 sized chunks. 

201 """ 

202 iterable = [list(range(2)), list(range(3))] 

203 seen = [] 

204 for chunk in self.progress.iter_chunks(iter(iterable), total=5): 

205 seen.extend(chunk) 

206 self.assertEqual(seen, iterable[0] + iterable[1]) 

207 self.assertEqual(MockProgressBar.last.reported, [0, 2, 5]) 

208 self.assertEqual(MockProgressBar.last.total, 5) 

209 

210 def test_iter_chunks_total_false(self): 

211 """Test using `Progress.iter_chunks` with total=False and non-sized 

212 chunks. This should display progress with the number of 

213 chunks. 

214 """ 

215 iterable = [iter(range(2)), iter(range(3))] 

216 seen = [] 

217 for chunk in self.progress.iter_chunks(iterable, total=False): 

218 seen.extend(chunk) 

219 self.assertEqual(seen, list(range(2)) + list(range(3))) 

220 self.assertEqual(MockProgressBar.last.reported, [0, 1, 2]) 

221 self.assertEqual(MockProgressBar.last.total, 2) 

222 

223 def test_iter_chunks_not_sized(self): 

224 """Test using `Progress.iter_chunks` with an unsized iterable.""" 

225 iterable = [iter(range(2)), iter(range(3))] 

226 seen = [] 

227 for chunk in self.progress.iter_chunks(iter(iterable)): 

228 seen.extend(chunk) 

229 self.assertEqual(seen, list(range(2)) + list(range(3))) 

230 self.assertEqual(MockProgressBar.last.reported, [0, 1, 2]) 

231 self.assertEqual(MockProgressBar.last.total, None) 

232 

233 def test_iter_item_chunks_fully_sized(self): 

234 """Test using `Progress.iter_item_chunks` with a sized iterable of 

235 sized chunks. 

236 """ 

237 mapping = {"x": list(range(2)), "y": list(range(3))} 

238 seen = {} 

239 for key, chunk in self.progress.iter_item_chunks(mapping.items()): 

240 seen[key] = chunk 

241 self.assertEqual(seen, mapping) 

242 self.assertEqual(MockProgressBar.last.reported, [0, 2, 5]) 

243 self.assertEqual(MockProgressBar.last.total, 5) 

244 

245 def test_iter_item_chunks_with_total(self): 

246 """Test using `Progress.iter_item_chunks` with total provided and 

247 sized chunks. 

248 """ 

249 mapping = {"x": list(range(2)), "y": list(range(3))} 

250 seen = {} 

251 for key, chunk in self.progress.iter_item_chunks(iter(mapping.items()), total=5): 

252 seen[key] = chunk 

253 self.assertEqual(seen, mapping) 

254 self.assertEqual(MockProgressBar.last.reported, [0, 2, 5]) 

255 self.assertEqual(MockProgressBar.last.total, 5) 

256 

257 def test_iter_item_chunks_total_false(self): 

258 """Test using `Progress.iter_item_chunks` with total=False and 

259 non-sized chunks. This should display progress with the number of 

260 chunks. 

261 """ 

262 mapping = {"x": iter(range(2)), "y": iter(range(3))} 

263 seen = {} 

264 for key, chunk in self.progress.iter_item_chunks(mapping.items(), total=False): 

265 seen[key] = list(chunk) 

266 self.assertEqual(seen, {"x": list(range(2)), "y": list(range(3))}) 

267 self.assertEqual(MockProgressBar.last.reported, [0, 1, 2]) 

268 self.assertEqual(MockProgressBar.last.total, 2) 

269 

270 def test_iter_item_chunks_not_sized(self): 

271 """Test using `Progress.iter_item_chunks` with an unsized iterable of 

272 non-sized chunks. 

273 """ 

274 mapping = {"x": iter(range(2)), "y": iter(range(3))} 

275 seen = {} 

276 for key, chunk in self.progress.iter_item_chunks(iter(mapping.items())): 

277 seen[key] = list(chunk) 

278 self.assertEqual(seen, {"x": list(range(2)), "y": list(range(3))}) 

279 self.assertEqual(MockProgressBar.last.reported, [0, 1, 2]) 

280 self.assertEqual(MockProgressBar.last.total, None) 

281 

282 

283if __name__ == "__main__": 

284 unittest.main()