Coverage for tests/test_progress.py: 35%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

96 statements  

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 

23from contextlib import contextmanager 

24import logging 

25import unittest 

26 

27import click 

28 

29from lsst.daf.butler.core.progress import Progress, ProgressHandler 

30from lsst.daf.butler.cli.utils import clickResultMsg 

31from lsst.daf.butler.cli.progress import ClickProgressHandler 

32 

33 

34class MockProgressBar: 

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

36 would report in a list. 

37 

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

39 

40 Parameters 

41 ---------- 

42 iterable : `Iterable`, optional 

43 Iterable to wrap, or `None`. 

44 """ 

45 def __init__(self, iterable): 

46 self._iterable = iterable 

47 self._current = 0 

48 self.reported = [self._current] 

49 MockProgressBar.last = self 

50 

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 """ 

55 

56 def __iter__(self): 

57 for element in self._iterable: 

58 yield element 

59 self._current += 1 

60 self.reported.append(self._current) 

61 

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

63 self._current += n 

64 self.reported.append(self._current) 

65 

66 

67class MockProgressHandler(ProgressHandler): 

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

69 instances. 

70 """ 

71 @contextmanager 

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

73 yield MockProgressBar(iterable) 

74 

75 

76class ClickProgressHandlerTestCase(unittest.TestCase): 

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

78 

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

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

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

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

83 """ 

84 

85 def setUp(self): 

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

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

88 # end up with that mock. 

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

90 self.logger.setLevel(logging.INFO) 

91 Progress.set_handler(MockProgressHandler()) 

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

93 

94 def tearDown(self): 

95 MockProgressHandler.last = None 

96 Progress.set_handler(None) 

97 self.logger.setLevel(logging.NOTSET) 

98 

99 def get_cmd(self, level, enabled): 

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

101 is or not enabled, as given. 

102 """ 

103 

104 @click.command() 

105 @ClickProgressHandler.option 

106 def cmd(progress): 

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

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

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

110 r = list(bar) 

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

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

113 

114 return cmd 

115 

116 def test_click_disabled_by_default(self): 

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

118 """ 

119 result = self.runner.invoke( 

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

121 [], 

122 ) 

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

124 

125 def test_click_enabled(self): 

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

127 """ 

128 result = self.runner.invoke( 

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

130 ["--progress"], 

131 ) 

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

133 

134 def test_click_disabled_globally(self): 

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

136 """ 

137 result = self.runner.invoke( 

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

139 ["--no-progress"], 

140 ) 

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

142 

143 def test_click_disabled_by_log_level(self): 

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

145 even if progress is globally enabled. 

146 """ 

147 result = self.runner.invoke( 

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

149 ["--progress"], 

150 ) 

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

152 

153 

154class MockedProgressHandlerTestCase(unittest.TestCase): 

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

156 mock handler and progress bar objects. 

157 """ 

158 

159 def setUp(self): 

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

161 self.logger.setLevel(logging.INFO) 

162 Progress.set_handler(MockProgressHandler()) 

163 self.progress = Progress("test_progress") 

164 

165 def tearDown(self): 

166 MockProgressHandler.last = None 

167 Progress.set_handler(None) 

168 self.logger.setLevel(logging.NOTSET) 

169 

170 def test_bar_iterable(self): 

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

172 """ 

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 """ 

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

183 for i in range(5): 

184 bar.update(2) 

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

186 

187 def test_iter_chunks(self): 

188 """Test using `Progress.iter_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 

197 def test_iter_item_chunks(self): 

198 """Test using `Progress.iter_item_chunks`. 

199 """ 

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

201 seen = {} 

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

203 seen[key] = chunk 

204 self.assertEqual(seen, mapping) 

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

206 

207 

208if __name__ == "__main__": 208 ↛ 209line 208 didn't jump to line 209, because the condition on line 208 was never true

209 unittest.main()