Coverage for tests/test_handlers.py: 23%
199 statements
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-07 10:12 +0000
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-07 10:12 +0000
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/>.
28"""Unit tests for job ClassAd handlers."""
30import logging
31import unittest
32from typing import Any
34from lsst.ctrl.bps.htcondor.handlers import (
35 Chain,
36 Handler,
37 JobCompletedWithExecTicketHandler,
38 JobCompletedWithoutExecTicketHandler,
39 JobHeldByOtherHandler,
40 JobHeldBySignalHandler,
41 JobHeldByUserHandler,
42)
44logger = logging.getLogger("lsst.ctrl.bps.htcondor")
47class DummyHandler(Handler):
48 """A concrete handler that does nothing."""
50 def handle(self, ad: dict[str, Any]) -> dict[str, Any]:
51 pass
54class RaisingHandler(Handler):
55 """A concrete handler that raises KeyError exception."""
57 def handle(self, ad: dict[str, Any]) -> dict[str, Any]:
58 raise KeyError("foo")
61class ChainTestCase(unittest.TestCase):
62 """Test the Chain class."""
64 def setUp(self):
65 pass
67 def tearDown(self):
68 pass
70 def testDefaultInitialization(self):
71 chain = Chain()
72 self.assertEqual(len(chain), 0)
74 def testCustomInitialization(self):
75 handler = DummyHandler()
76 chain = Chain(handlers=[handler])
77 self.assertEqual(len(chain), 1)
78 self.assertIs(chain[0], handler)
80 def testAppendingHandler(self):
81 first = DummyHandler()
82 second = DummyHandler()
83 chain = Chain(handlers=[first])
84 chain.append(second)
85 self.assertEqual(len(chain), 2)
86 self.assertIs(chain[0], first)
87 self.assertIs(chain[1], second)
89 def testAppendingNonHandler(self):
90 handler = "foo"
91 chain = Chain()
92 with self.assertRaises(TypeError):
93 chain.append(handler)
96class JobCompletedWithExecTicketHandlerTestCase(unittest.TestCase):
97 """Test the handler for a completed job with the ticket of execution."""
99 def setUp(self):
100 self.ad = {"ClusterId": 1, "ProcId": 0, "MyType": "JobTerminatedEvent"}
101 self.handler = JobCompletedWithExecTicketHandler()
103 def tearDown(self):
104 pass
106 def testNormalTermination(self):
107 ad = self.ad | {"ToE": {"ExitBySignal": False, "ExitCode": 0}}
108 result = self.handler.handle(ad)
109 self.assertIsNotNone(result)
110 self.assertIn("ExitBySignal", result)
111 self.assertFalse(result["ExitBySignal"])
112 self.assertIn("ExitCode", result)
113 self.assertEqual(result["ExitCode"], 0)
115 def testAbnormalTermination(self):
116 ad = self.ad | {"ToE": {"ExitBySignal": True, "ExitSignal": 9}}
117 result = self.handler.handle(ad)
118 self.assertIsNotNone(result)
119 self.assertIn("ExitBySignal", result)
120 self.assertTrue(result["ExitBySignal"])
121 self.assertIn("ExitSignal", result)
122 self.assertEqual(result["ExitSignal"], 9)
124 def testNotHandlingMissingExecTicket(self):
125 with self.assertLogs(logger=logger, level="DEBUG") as cm:
126 result = self.handler.handle(self.ad)
127 self.assertIsNone(result)
128 self.assertIn("ticket of execution", cm.output[0])
129 self.assertIn("missing", cm.output[0])
131 def testNotHandlingJobNotCompleted(self):
132 ad = self.ad | {"MyType": "foo"}
133 with self.assertLogs(logger=logger, level="DEBUG") as cm:
134 result = self.handler.handle(ad)
135 self.assertIsNone(result)
136 self.assertIn("job not completed", cm.output[0])
139class JobCompletedWithoutExecTicketHandlerTestCase(unittest.TestCase):
140 """Test the handler for a completed job w/o the ticket of execution."""
142 def setUp(self):
143 self.ad = {"ClusterId": 1, "ProcId": 0, "MyType": "JobTerminatedEvent"}
144 self.handler = JobCompletedWithoutExecTicketHandler()
146 def tearDown(self):
147 pass
149 def testNormalTermination(self):
150 ad = self.ad | {"TerminatedNormally": True, "ReturnValue": 0}
151 result = self.handler.handle(ad)
152 self.assertIsNotNone(result)
153 self.assertIn("ExitBySignal", result)
154 self.assertFalse(result["ExitBySignal"])
155 self.assertIn("ExitCode", result)
156 self.assertEqual(result["ExitCode"], 0)
158 def testAbnormalTermination(self):
159 ad = self.ad | {"TerminatedNormally": False, "TerminatedBySignal": 9}
160 result = self.handler.handle(ad)
161 self.assertIsNotNone(result)
162 self.assertIn("ExitBySignal", result)
163 self.assertTrue(result["ExitBySignal"])
164 self.assertIn("ExitSignal", result)
165 self.assertEqual(result["ExitSignal"], 9)
167 def testNotHandlingExecTicketExists(self):
168 ad = self.ad | {"ToE": {"ExitBySignal": False, "ExitCode": 0}}
169 with self.assertLogs(logger=logger, level="DEBUG") as cm:
170 result = self.handler.handle(ad)
171 self.assertIsNone(result)
172 self.assertIn("ticket of execution", cm.output[0])
173 self.assertIn("found", cm.output[0])
175 def testNotHandlingJobNotCompleted(self):
176 ad = self.ad | {"MyType": "foo"}
177 with self.assertLogs(logger=logger, level="DEBUG") as cm:
178 result = self.handler.handle(ad)
179 self.assertIsNone(result)
180 self.assertIn("job not completed", cm.output[0])
183class JobHeldOtherTestCase(unittest.TestCase):
184 """Test the handler for a held job."""
186 def setUp(self):
187 self.ad = {"ClusterId": 1, "ProcId": 0, "MyType": "JobHeldEvent"}
188 self.handler = JobHeldByOtherHandler()
190 def tearDown(self):
191 pass
193 def testHeld(self):
194 ad = self.ad | {"HoldReasonCode": 42}
195 result = self.handler.handle(ad)
196 self.assertIsNotNone(result)
197 self.assertIn("ExitBySignal", result)
198 self.assertFalse(result["ExitBySignal"])
199 self.assertIn("ExitCode", result)
200 self.assertEqual(result["ExitCode"], 42)
202 def testHeldBySignal(self):
203 ad = self.ad | {"HoldReasonCode": 3}
204 with self.assertLogs(logger=logger, level="DEBUG") as cm:
205 result = self.handler.handle(ad)
206 self.assertIsNone(result)
207 self.assertIn("invalid hold reason code", cm.output[0])
208 self.assertIn("HoldReasonCode = 3", cm.output[0])
210 def testHeldByUser(self):
211 ad = self.ad | {"HoldReasonCode": 1}
212 with self.assertLogs(logger=logger, level="DEBUG") as cm:
213 result = self.handler.handle(ad)
214 self.assertIsNone(result)
215 self.assertIn("invalid hold reason code", cm.output[0])
216 self.assertIn("HoldReasonCode = 1", cm.output[0])
218 def testNotHandlingJobNotHeld(self):
219 ad = self.ad | {"MyType": "foo"}
220 with self.assertLogs(logger=logger, level="DEBUG") as cm:
221 result = self.handler.handle(ad)
222 self.assertIsNone(result)
223 self.assertIn("job not held", cm.output[0])
226class JobHeldBySignalHandlerTestCase(unittest.TestCase):
227 """Test the handler for a job held by a signal."""
229 def setUp(self):
230 self.ad = {"ClusterId": 1, "ProcId": 0, "MyType": "JobHeldEvent"}
231 self.handler = JobHeldBySignalHandler()
233 def tearDown(self):
234 pass
236 def testSignalAvailable(self):
237 ad = self.ad | {"HoldReasonCode": 3, "HoldReason": "Job raised a signal 9."}
238 result = self.handler.handle(ad)
239 self.assertIsNotNone(ad)
240 self.assertIn("ExitBySignal", result)
241 self.assertTrue(result["ExitBySignal"])
242 self.assertIn("ExitSignal", result)
243 self.assertEqual(int(result["ExitSignal"]), 9)
245 def testSignalNotAvailable(self):
246 ad = self.ad | {"HoldReasonCode": 3, "HoldReason": "foo"}
247 with self.assertLogs(logger=logger, level="DEBUG") as cm:
248 result = self.handler.handle(ad)
249 self.assertIsNone(result)
250 self.assertIn("signal not found", cm.output[0])
252 def testNotHandlingInvalidHoldReasonCode(self):
253 ad = self.ad | {"HoldReasonCode": 1, "HoldReason": "via condor_hold (by user foo)"}
254 with self.assertLogs(logger=logger, level="DEBUG") as cm:
255 result = self.handler.handle(ad)
256 self.assertIsNone(result)
257 self.assertIn("not held by a signal", cm.output[0])
259 def testNotHandlingJobNotHeld(self):
260 ad = self.ad | {"MyType": "foo"}
261 with self.assertLogs(logger=logger, level="DEBUG") as cm:
262 result = self.handler.handle(ad)
263 self.assertIsNone(result)
264 self.assertIn("job not held", cm.output[0])
267class JobHeldByUserHandlerTestCase(unittest.TestCase):
268 """Test the handler for a job held by the user."""
270 def setUp(self):
271 self.ad = {"ClusterId": 1, "ProcId": 0, "MyType": "JobHeldEvent"}
272 self.handler = JobHeldByUserHandler()
274 def tearDown(self):
275 pass
277 def testHandling(self):
278 ad = self.ad | {"HoldReasonCode": 1}
279 result = self.handler.handle(ad)
280 self.assertIsNotNone(result)
281 self.assertIn("ExitBySignal", result)
282 self.assertFalse(result["ExitBySignal"])
283 self.assertIn("ExitCode", result)
284 self.assertEqual(result["ExitCode"], 0)
286 def testNotHandlingInvalidHoldReaconCode(self):
287 ad = self.ad | {"HoldReasonCode": 3, "HoldReason": "Job raised a signal 9."}
288 with self.assertLogs(logger=logger, level="DEBUG") as cm:
289 result = self.handler.handle(ad)
290 self.assertIsNone(result)
291 self.assertIn("not held by the user", cm.output[0])
293 def testNotHandlingJobNotHeld(self):
294 ad = self.ad | {"MyType": "foo"}
295 with self.assertLogs(logger=logger, level="DEBUG") as cm:
296 result = self.handler.handle(ad)
297 self.assertIsNone(result)
298 self.assertIn("job not held", cm.output[0])