Coverage for tests/test_htcondor_service.py: 27%

98 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-10 03:42 -0700

1# This file is part of ctrl_bps_htcondor. 

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# This software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

15# This program is free software: you can redistribute it and/or modify 

16# it under the terms of the GNU General Public License as published by 

17# the Free Software Foundation, either version 3 of the License, or 

18# (at your option) any later version. 

19# 

20# This program is distributed in the hope that it will be useful, 

21# but WITHOUT ANY WARRANTY; without even the implied warranty of 

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

25# You should have received a copy of the GNU General Public License 

26# along with this program. If not, see <https://www.gnu.org/licenses/>. 

27 

28"""Unit tests for the HTCondor WMS service class and related functions.""" 

29 

30import logging 

31import pathlib 

32import tempfile 

33import unittest 

34 

35import htcondor 

36from lsst.ctrl.bps.htcondor.htcondor_service import _get_exit_code_summary 

37from lsst.ctrl.bps.htcondor.lssthtc import _tweak_log_info 

38 

39logger = logging.getLogger("lsst.ctrl.bps.htcondor") 

40 

41 

42class GetExitCodeSummaryTestCase(unittest.TestCase): 

43 """Test the function responsible for creating exit code summary.""" 

44 

45 def setUp(self): 

46 self.jobs = { 

47 "1.0": { 

48 "JobStatus": htcondor.JobStatus.IDLE, 

49 "bps_job_label": "foo", 

50 }, 

51 "2.0": { 

52 "JobStatus": htcondor.JobStatus.RUNNING, 

53 "bps_job_label": "foo", 

54 }, 

55 "3.0": { 

56 "JobStatus": htcondor.JobStatus.REMOVED, 

57 "bps_job_label": "foo", 

58 }, 

59 "4.0": { 

60 "ExitCode": 0, 

61 "ExitBySignal": False, 

62 "JobStatus": htcondor.JobStatus.COMPLETED, 

63 "bps_job_label": "bar", 

64 }, 

65 "5.0": { 

66 "ExitCode": 1, 

67 "ExitBySignal": False, 

68 "JobStatus": htcondor.JobStatus.COMPLETED, 

69 "bps_job_label": "bar", 

70 }, 

71 "6.0": { 

72 "ExitBySignal": True, 

73 "ExitSignal": 11, 

74 "JobStatus": htcondor.JobStatus.HELD, 

75 "bps_job_label": "baz", 

76 }, 

77 "7.0": { 

78 "ExitBySignal": False, 

79 "ExitCode": 42, 

80 "JobStatus": htcondor.JobStatus.HELD, 

81 "bps_job_label": "baz", 

82 }, 

83 "8.0": { 

84 "JobStatus": htcondor.JobStatus.TRANSFERRING_OUTPUT, 

85 "bps_job_label": "qux", 

86 }, 

87 "9.0": { 

88 "JobStatus": htcondor.JobStatus.SUSPENDED, 

89 "bps_job_label": "qux", 

90 }, 

91 } 

92 

93 def tearDown(self): 

94 pass 

95 

96 def testMainScenario(self): 

97 actual = _get_exit_code_summary(self.jobs) 

98 expected = {"foo": [], "bar": [1], "baz": [11, 42], "qux": []} 

99 self.assertEqual(actual, expected) 

100 

101 def testUnknownStatus(self): 

102 jobs = { 

103 "1.0": { 

104 "JobStatus": -1, 

105 "bps_job_label": "foo", 

106 } 

107 } 

108 with self.assertLogs(logger=logger, level="DEBUG") as cm: 

109 _get_exit_code_summary(jobs) 

110 self.assertIn("lsst.ctrl.bps.htcondor", cm.records[0].name) 

111 self.assertIn("Unknown", cm.output[0]) 

112 self.assertIn("JobStatus", cm.output[0]) 

113 

114 def testUnknownKey(self): 

115 jobs = { 

116 "1.0": { 

117 "JobStatus": htcondor.JobStatus.COMPLETED, 

118 "UnknownKey": None, 

119 "bps_job_label": "foo", 

120 } 

121 } 

122 with self.assertLogs(logger=logger, level="DEBUG") as cm: 

123 _get_exit_code_summary(jobs) 

124 self.assertIn("lsst.ctrl.bps.htcondor", cm.records[0].name) 

125 self.assertIn("Attribute", cm.output[0]) 

126 self.assertIn("not found", cm.output[0]) 

127 

128 

129class TweakJobInfoTestCase(unittest.TestCase): 

130 """Test the function responsible for massaging job information.""" 

131 

132 def setUp(self): 

133 self.log_file = tempfile.NamedTemporaryFile(prefix="test_", suffix=".log") 

134 self.log_name = pathlib.Path(self.log_file.name) 

135 self.job = { 

136 "Cluster": 1, 

137 "Proc": 0, 

138 "Iwd": str(self.log_name.parent), 

139 "Owner": self.log_name.owner(), 

140 "MyType": None, 

141 "TerminatedNormally": True, 

142 } 

143 

144 def tearDown(self): 

145 self.log_file.close() 

146 

147 def testDirectAssignments(self): 

148 _tweak_log_info(self.log_name, self.job) 

149 self.assertEqual(self.job["ClusterId"], self.job["Cluster"]) 

150 self.assertEqual(self.job["ProcId"], self.job["Proc"]) 

151 self.assertEqual(self.job["Iwd"], str(self.log_name.parent)) 

152 self.assertEqual(self.job["Owner"], self.log_name.owner()) 

153 

154 def testJobStatusAssignmentJobAbortedEvent(self): 

155 job = self.job | {"MyType": "JobAbortedEvent"} 

156 _tweak_log_info(self.log_name, job) 

157 self.assertTrue("JobStatus" in job) 

158 self.assertEqual(job["JobStatus"], htcondor.JobStatus.REMOVED) 

159 

160 def testJobStatusAssignmentExecuteEvent(self): 

161 job = self.job | {"MyType": "ExecuteEvent"} 

162 _tweak_log_info(self.log_name, job) 

163 self.assertTrue("JobStatus" in job) 

164 self.assertEqual(job["JobStatus"], htcondor.JobStatus.RUNNING) 

165 

166 def testJobStatusAssignmentSubmitEvent(self): 

167 job = self.job | {"MyType": "SubmitEvent"} 

168 _tweak_log_info(self.log_name, job) 

169 self.assertTrue("JobStatus" in job) 

170 self.assertEqual(job["JobStatus"], htcondor.JobStatus.IDLE) 

171 

172 def testJobStatusAssignmentJobHeldEvent(self): 

173 job = self.job | {"MyType": "JobHeldEvent"} 

174 _tweak_log_info(self.log_name, job) 

175 self.assertTrue("JobStatus" in job) 

176 self.assertEqual(job["JobStatus"], htcondor.JobStatus.HELD) 

177 

178 def testJobStatusAssignmentJobTerminatedEvent(self): 

179 job = self.job | {"MyType": "JobTerminatedEvent"} 

180 _tweak_log_info(self.log_name, job) 

181 self.assertTrue("JobStatus" in job) 

182 self.assertEqual(job["JobStatus"], htcondor.JobStatus.COMPLETED) 

183 

184 def testJobStatusAssignmentPostScriptTerminatedEvent(self): 

185 job = self.job | {"MyType": "PostScriptTerminatedEvent"} 

186 _tweak_log_info(self.log_name, job) 

187 self.assertTrue("JobStatus" in job) 

188 self.assertEqual(job["JobStatus"], htcondor.JobStatus.COMPLETED) 

189 

190 def testAddingExitStatusSuccess(self): 

191 job = self.job | { 

192 "MyType": "JobTerminatedEvent", 

193 "ToE": {"ExitBySignal": False, "ExitCode": 1}, 

194 } 

195 _tweak_log_info(self.log_name, job) 

196 self.assertIn("ExitBySignal", job) 

197 self.assertIs(job["ExitBySignal"], False) 

198 self.assertIn("ExitCode", job) 

199 self.assertEqual(job["ExitCode"], 1) 

200 

201 def testAddingExitStatusFailure(self): 

202 job = self.job | { 

203 "MyType": "JobHeldEvent", 

204 } 

205 with self.assertLogs(logger=logger, level="ERROR") as cm: 

206 _tweak_log_info(self.log_name, job) 

207 self.assertIn("Could not determine exit status", cm.output[0]) 

208 

209 def testLoggingUnknownLogEvent(self): 

210 job = self.job | {"MyType": "Foo"} 

211 with self.assertLogs(logger=logger, level="DEBUG") as cm: 

212 _tweak_log_info(self.log_name, job) 

213 self.assertIn("Unknown log event", cm.output[1]) 

214 

215 def testMissingKey(self): 

216 job = self.job 

217 del job["Cluster"] 

218 with self.assertRaises(KeyError) as cm: 

219 _tweak_log_info(self.log_name, job) 

220 self.assertEqual(str(cm.exception), "'Cluster'")