Hide keyboard shortcuts

Hot-keys 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

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 unittest 

13import logging 

14import time 

15import datetime 

16import os.path 

17from dataclasses import dataclass 

18 

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

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 

63 def testLogPairs(self): 

64 # Test the non-obj case. 

65 logger = logging.getLogger("test") 

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

67 metadata = {} 

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

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

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

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

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

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

74 

75 def assertTimer(self, duration, task): 

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

77 task.sleeper(duration) 

78 task.sleeper(duration) 

79 counter = 2 

80 

81 has_logger = getattr(task, "log", None) is not None \ 

82 and task.log is not None 

83 has_metadata = getattr(task, "metadata", None) is not None \ 

84 and task.metadata is not None 

85 

86 if has_logger: 

87 counter += 1 

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

89 task.sleeper(duration) 

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

91 

92 if has_metadata: 

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

94 

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

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

97 delta = end - start 

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

99 self.assertGreaterEqual(delta_sec, duration) 

100 

101 def testTaskLike(self): 

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

103 

104 # Call with different parameters. 

105 parameters = ((logging.getLogger("task"), {}), 

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

107 (None, {}), 

108 (None, None)) 

109 

110 duration = 0.1 

111 for log, metadata in parameters: 

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

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

114 self.assertTimer(duration, task) 

115 

116 def testDecorated(self): 

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

118 duration = 0.1 

119 

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

121 # and not crash. 

122 decorated_sleeper_nothing(self, duration) 

123 

124 # Use a function decorated for logging. 

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

126 decorated_sleeper_logger(self, duration) 

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

128 

129 # And adjust the log level 

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

131 decorated_sleeper_logger_level(self, duration) 

132 

133 # Use a function decorated for metadata. 

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

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

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

137 # timeMethod itself. 

138 decorated_sleeper_metadata(self, duration) 

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

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

141 self.assertIn("decorated_sleeper_metadataStartUserTime", test_metadata) 

142 

143 

144class TimerTestCase(unittest.TestCase): 

145 

146 def testTimer(self): 

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

148 with time_this(): 

149 pass 

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

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

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

153 

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

155 with time_this(prefix=None): 

156 pass 

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

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

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

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

161 

162 # Change logging level 

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

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

165 pass 

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

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

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

169 

170 # Use a new logger with a message. 

171 msg = "Test message %d" 

172 test_num = 42 

173 logname = "test" 

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

175 with time_this(log=logging.getLogger(logname), 

176 msg=msg, args=(42,), prefix=None): 

177 pass 

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

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

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

181 

182 # Prefix the logger. 

183 prefix = "prefix" 

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

185 with time_this(prefix=prefix): 

186 pass 

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

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

189 

190 # Prefix explicit logger. 

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

192 with time_this(log=logging.getLogger(logname), 

193 prefix=prefix): 

194 pass 

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

196 

197 # Trigger a problem. 

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

199 with self.assertRaises(RuntimeError): 

200 with time_this(log=logging.getLogger(logname), 

201 prefix=prefix): 

202 raise RuntimeError("A problem") 

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

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

205 

206 

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

208 unittest.main()