Coverage for tests/test_timer.py: 25%

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

146 statements  

1# This file is part of utils. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# Use of this source code is governed by a 3-clause BSD-style 

10# license that can be found in the LICENSE file. 

11 

12import datetime 

13import logging 

14import os.path 

15import time 

16import unittest 

17from dataclasses import dataclass 

18 

19from lsst.utils.timer import logInfo, logPairs, time_this, timeMethod 

20 

21log = logging.getLogger("test_timer") 

22 

23THIS_FILE = os.path.basename(__file__) 

24 

25# Only use this in a single test but needs to be associated 

26# with the function up front. 

27test_metadata = {} 

28 

29 

30@dataclass 

31class Example1: 

32 log: logging.Logger 

33 metadata: dict 

34 

35 @timeMethod 

36 def sleeper(self, duration: float) -> None: 

37 """Sleep for some time.""" 

38 time.sleep(duration) 

39 

40 

41@timeMethod 

42def decorated_sleeper_nothing(self, duration: float) -> None: 

43 time.sleep(duration) 

44 

45 

46@timeMethod(logger=log) 

47def decorated_sleeper_logger(self, duration: float) -> None: 

48 time.sleep(duration) 

49 

50 

51@timeMethod(logger=log, logLevel=logging.INFO) 

52def decorated_sleeper_logger_level(self, duration: float) -> None: 

53 time.sleep(duration) 

54 

55 

56@timeMethod(metadata=test_metadata) 

57def decorated_sleeper_metadata(self, duration: float) -> None: 

58 time.sleep(duration) 

59 

60 

61class TestTimeMethod(unittest.TestCase): 

62 def testLogPairs(self): 

63 # Test the non-obj case. 

64 logger = logging.getLogger("test") 

65 pairs = (("name1", 0), ("name2", 1)) 

66 metadata = {} 

67 with self.assertLogs(level=logging.INFO) as cm: 

68 logPairs(None, pairs, logLevel=logging.INFO, logger=logger, metadata=metadata) 

69 self.assertEqual(len(cm.output), 1, cm.output) 

70 self.assertTrue(cm.output[0].endswith("name1=0; name2=1"), cm.output) 

71 self.assertEqual(cm.records[0].filename, THIS_FILE, "log message should originate from here") 

72 self.assertEqual(metadata, {"name1": [0], "name2": [1]}) 

73 

74 # Call it again with an explicit stack level. 

75 # Force it to come from lsst.utils. 

76 with self.assertLogs(level=logging.INFO) as cm: 

77 logPairs(None, pairs, logLevel=logging.INFO, logger=logger, metadata=metadata, stacklevel=0) 

78 self.assertEqual(cm.records[0].filename, "timer.py") 

79 

80 # Check that the log message is filtered by default. 

81 with self.assertLogs(level=logging.INFO) as cm: 

82 logPairs(None, pairs, logger=logger, metadata=metadata) 

83 logger.info("Message") 

84 self.assertEqual(len(cm.records), 1) 

85 

86 def testLogInfo(self): 

87 metadata = {} 

88 logger = logging.getLogger("testLogInfo") 

89 with self.assertLogs(level=logging.INFO) as cm: 

90 logInfo(None, prefix="Prefix", metadata=metadata, logger=logger, logLevel=logging.INFO) 

91 self.assertEqual(cm.records[0].filename, THIS_FILE) 

92 self.assertIn("PrefixUtc", metadata) 

93 

94 # Again with no log output. 

95 logInfo(None, prefix="Prefix", metadata=metadata) 

96 self.assertEqual(len(metadata["PrefixUtc"]), 2) 

97 

98 # With an explicit stacklevel. 

99 with self.assertLogs(level=logging.INFO) as cm: 

100 logInfo( 

101 None, prefix="Prefix", metadata=metadata, logger=logger, logLevel=logging.INFO, stacklevel=0 

102 ) 

103 self.assertEqual(cm.records[0].filename, "timer.py") 

104 

105 def assertTimer(self, duration, task): 

106 # Call it twice to test the "add" functionality. 

107 task.sleeper(duration) 

108 task.sleeper(duration) 

109 counter = 2 

110 

111 has_logger = getattr(task, "log", None) is not None and task.log is not None 

112 has_metadata = getattr(task, "metadata", None) is not None and task.metadata is not None 

113 

114 if has_logger: 

115 counter += 1 

116 with self.assertLogs("timer.task", level=logging.DEBUG) as cm: 

117 task.sleeper(duration) 

118 self.assertEqual(cm.records[0].filename, THIS_FILE, "log message should originate from here") 

119 

120 if has_metadata: 

121 self.assertEqual(len(task.metadata["sleeperStartUserTime"]), counter) 

122 

123 start = datetime.datetime.fromisoformat(task.metadata["sleeperStartUtc"][1]) 

124 end = datetime.datetime.fromisoformat(task.metadata["sleeperEndUtc"][1]) 

125 delta = end - start 

126 delta_sec = delta.seconds + (delta.microseconds / 1e6) 

127 self.assertGreaterEqual(delta_sec, duration) 

128 

129 def testTaskLike(self): 

130 """Test timer on something that looks like a Task.""" 

131 

132 # Call with different parameters. 

133 parameters = ( 

134 (logging.getLogger("task"), {}), 

135 (logging.getLogger("task"), None), 

136 (None, {}), 

137 (None, None), 

138 ) 

139 

140 duration = 0.1 

141 for log, metadata in parameters: 

142 with self.subTest(log=log, metadata=metadata): 

143 task = Example1(log=log, metadata=metadata) 

144 self.assertTimer(duration, task) 

145 

146 def testDecorated(self): 

147 """Test timeMethod on non-Task like instances.""" 

148 duration = 0.1 

149 

150 # The "self" object shouldn't be usable but this should do nothing 

151 # and not crash. 

152 decorated_sleeper_nothing(self, duration) 

153 

154 # Use a function decorated for logging. 

155 with self.assertLogs("timer.test_timer", level=logging.DEBUG) as cm: 

156 decorated_sleeper_logger(self, duration) 

157 self.assertEqual(cm.records[0].filename, THIS_FILE, "log message should originate from here") 

158 

159 # And adjust the log level 

160 with self.assertLogs("timer.test_timer", level=logging.INFO): 

161 decorated_sleeper_logger_level(self, duration) 

162 

163 # Use a function decorated for metadata. 

164 self.assertEqual(len(test_metadata), 0) 

165 with self.assertLogs("timer.test_timer", level=logging.DEBUG) as cm: 

166 # Check that we only get a single log message and nothing from 

167 # timeMethod itself. 

168 decorated_sleeper_metadata(self, duration) 

169 logging.getLogger("timer.test_timer").debug("sentinel") 

170 self.assertEqual(len(cm.output), 1) 

171 self.assertIn("decorated_sleeper_metadataStartUserTime", test_metadata) 

172 

173 

174class TimerTestCase(unittest.TestCase): 

175 def testTimer(self): 

176 with self.assertLogs(level="DEBUG") as cm: 

177 with time_this(): 

178 pass 

179 self.assertEqual(cm.records[0].name, "timer") 

180 self.assertEqual(cm.records[0].levelname, "DEBUG") 

181 self.assertEqual(cm.records[0].filename, THIS_FILE) 

182 

183 with self.assertLogs(level="DEBUG") as cm: 

184 with time_this(prefix=None): 

185 pass 

186 self.assertEqual(cm.records[0].name, "root") 

187 self.assertEqual(cm.records[0].levelname, "DEBUG") 

188 self.assertIn("Took", cm.output[0]) 

189 self.assertEqual(cm.records[0].filename, THIS_FILE) 

190 

191 # Change logging level 

192 with self.assertLogs(level="INFO") as cm: 

193 with time_this(level=logging.INFO, prefix=None): 

194 pass 

195 self.assertEqual(cm.records[0].name, "root") 

196 self.assertIn("Took", cm.output[0]) 

197 self.assertIn("seconds", cm.output[0]) 

198 

199 # Use a new logger with a message. 

200 msg = "Test message %d" 

201 test_num = 42 

202 logname = "test" 

203 with self.assertLogs(level="DEBUG") as cm: 

204 with time_this(log=logging.getLogger(logname), msg=msg, args=(42,), prefix=None): 

205 pass 

206 self.assertEqual(cm.records[0].name, logname) 

207 self.assertIn("Took", cm.output[0]) 

208 self.assertIn(msg % test_num, cm.output[0]) 

209 

210 # Prefix the logger. 

211 prefix = "prefix" 

212 with self.assertLogs(level="DEBUG") as cm: 

213 with time_this(prefix=prefix): 

214 pass 

215 self.assertEqual(cm.records[0].name, prefix) 

216 self.assertIn("Took", cm.output[0]) 

217 

218 # Prefix explicit logger. 

219 with self.assertLogs(level="DEBUG") as cm: 

220 with time_this(log=logging.getLogger(logname), prefix=prefix): 

221 pass 

222 self.assertEqual(cm.records[0].name, f"{prefix}.{logname}") 

223 

224 # Trigger a problem. 

225 with self.assertLogs(level="ERROR") as cm: 

226 with self.assertRaises(RuntimeError): 

227 with time_this(log=logging.getLogger(logname), prefix=prefix): 

228 raise RuntimeError("A problem") 

229 self.assertEqual(cm.records[0].name, f"{prefix}.{logname}") 

230 self.assertEqual(cm.records[0].levelname, "ERROR") 

231 

232 

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

234 unittest.main()