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
« 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/>.
28"""Unit tests for the HTCondor WMS service class and related functions."""
30import logging
31import pathlib
32import tempfile
33import unittest
35import htcondor
36from lsst.ctrl.bps.htcondor.htcondor_service import _get_exit_code_summary
37from lsst.ctrl.bps.htcondor.lssthtc import _tweak_log_info
39logger = logging.getLogger("lsst.ctrl.bps.htcondor")
42class GetExitCodeSummaryTestCase(unittest.TestCase):
43 """Test the function responsible for creating exit code summary."""
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 }
93 def tearDown(self):
94 pass
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)
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])
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])
129class TweakJobInfoTestCase(unittest.TestCase):
130 """Test the function responsible for massaging job information."""
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 }
144 def tearDown(self):
145 self.log_file.close()
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())
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)
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)
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)
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)
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)
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)
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)
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])
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])
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'")