Coverage for tests/test_progress.py: 28%

95 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-31 02:41 -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 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`, optional 

42 Iterable to wrap, or `None`. 

43 """ 

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 

72 @contextmanager 

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

74 yield MockProgressBar(iterable) 

75 

76 

77class ClickProgressHandlerTestCase(unittest.TestCase): 

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

79 

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

85 

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() 

94 

95 def tearDown(self): 

96 MockProgressHandler.last = None 

97 Progress.set_handler(None) 

98 self.logger.setLevel(logging.NOTSET) 

99 

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

104 

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()) 

114 

115 return cmd 

116 

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

124 

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

132 

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

140 

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

150 

151 

152class MockedProgressHandlerTestCase(unittest.TestCase): 

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

154 mock handler and progress bar objects. 

155 """ 

156 

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

162 

163 def tearDown(self): 

164 MockProgressHandler.last = None 

165 Progress.set_handler(None) 

166 self.logger.setLevel(logging.NOTSET) 

167 

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) 

175 

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) 

182 

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]) 

191 

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]) 

200 

201 

202if __name__ == "__main__": 

203 unittest.main()