Coverage for tests/test_report.py: 21%
118 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-04 09:56 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-04 09:56 +0000
1# This file is part of ctrl_bps.
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"""Tests for reporting mechanism."""
30import io
31import unittest
33from astropy.table import Table
34from lsst.ctrl.bps import (
35 BaseRunReport,
36 DetailedRunReport,
37 SummaryRunReport,
38 WmsJobReport,
39 WmsRunReport,
40 WmsStates,
41)
44class FakeRunReport(BaseRunReport):
45 """A fake run report"""
47 def add(self, run_report, use_global_id=False):
48 id_ = run_report.global_wms_id if use_global_id else run_report.wms_id
49 self._table.add_row([id_, run_report.state.name])
52class FakeRunReportTestCase(unittest.TestCase):
53 """Test shared methods."""
55 def setUp(self):
56 self.fields = [("ID", "S"), ("STATE", "S")]
58 self.report = FakeRunReport(self.fields)
59 self.report.add(WmsRunReport(wms_id="2.0", state=WmsStates.RUNNING))
60 self.report.add(WmsRunReport(wms_id="1.0", state=WmsStates.SUCCEEDED))
62 def testEquality(self):
63 """Test if two reports are identical."""
64 other = FakeRunReport(self.fields)
65 other.add(WmsRunReport(wms_id="2.0", state=WmsStates.RUNNING))
66 other.add(WmsRunReport(wms_id="1.0", state=WmsStates.SUCCEEDED))
67 self.assertEqual(self.report, other)
69 def testInequality(self):
70 """Test if two reports are not identical."""
71 other = FakeRunReport(self.fields)
72 other.add(WmsRunReport(wms_id="1.0", state=WmsStates.FAILED))
73 self.assertNotEqual(self.report, other)
75 def testLength(self):
76 self.assertEqual(len(self.report), 2)
78 def testClear(self):
79 """Test clearing the report."""
80 self.report.clear()
81 self.assertEqual(len(self.report), 0)
83 def testSortWithKnownKey(self):
84 """Test sorting the report using known column."""
85 expected_output = io.StringIO()
86 expected = Table(dtype=self.fields)
87 expected.add_row(["1.0", WmsStates.SUCCEEDED.name])
88 expected.add_row(["2.0", WmsStates.RUNNING.name])
89 print(expected, file=expected_output)
91 actual_output = io.StringIO()
92 self.report.sort("ID")
93 print(self.report, file=actual_output)
95 self.assertEqual(actual_output.getvalue(), expected_output.getvalue())
97 expected_output.close()
98 actual_output.close()
100 def testSortWithUnknownKey(self):
101 """Test sorting the report using unknown column."""
102 with self.assertRaises(AttributeError):
103 self.report.sort("foo")
106class SummaryRunReportTestCase(unittest.TestCase):
107 """Test a summary run report."""
109 def setUp(self):
110 self.fields = [
111 ("X", "S"),
112 ("STATE", "S"),
113 ("%S", "S"),
114 ("ID", "S"),
115 ("OPERATOR", "S"),
116 ("PROJECT", "S"),
117 ("CAMPAIGN", "S"),
118 ("PAYLOAD", "S"),
119 ("RUN", "S"),
120 ]
121 self.run = WmsRunReport(
122 wms_id="1.0",
123 global_wms_id="foo#1.0",
124 path="/path/to/run",
125 label="label",
126 run="run",
127 project="dev",
128 campaign="testing",
129 payload="test",
130 operator="tester",
131 run_summary="foo:1;bar:1",
132 state=WmsStates.RUNNING,
133 jobs=None,
134 total_number_jobs=2,
135 job_state_counts={
136 state: 1 if state in {WmsStates.SUCCEEDED, WmsStates.RUNNING} else 0 for state in WmsStates
137 },
138 job_summary=None,
139 )
140 self.report = SummaryRunReport(self.fields)
142 self.expected = Table(dtype=self.fields)
143 self.expected.add_row(["", "RUNNING", "50", "1.0", "tester", "dev", "testing", "test", "run"])
145 self.expected_output = io.StringIO()
146 self.actual_output = io.StringIO()
148 def tearDown(self):
149 self.expected_output.close()
150 self.actual_output.close()
152 def testAddWithNoFlag(self):
153 """Test adding a report for a run with no issues."""
154 print("\n".join(self.expected.pformat_all()), file=self.expected_output)
156 self.report.add(self.run)
157 print(self.report, file=self.actual_output)
159 self.assertEqual(self.actual_output.getvalue(), self.expected_output.getvalue())
161 def testAddWithFailedFlag(self):
162 """Test adding a run with a failed job."""
163 self.expected["X"][0] = "F"
164 print("\n".join(self.expected.pformat_all()), file=self.expected_output)
166 # Alter the run report to include a failed job.
167 self.run.job_state_counts = {
168 state: 1 if state in {WmsStates.FAILED, WmsStates.SUCCEEDED} else 0 for state in WmsStates
169 }
170 self.report.add(self.run)
171 print(self.report, file=self.actual_output)
173 self.assertEqual(self.actual_output.getvalue(), self.expected_output.getvalue())
175 def testAddWithHeldFlag(self):
176 """Test adding a run with a held job."""
177 self.expected["X"][0] = "H"
178 print("\n".join(self.expected.pformat_all()), file=self.expected_output)
180 # Alter the run report to include a held job.
181 self.run.job_state_counts = {
182 state: 1 if state in {WmsStates.SUCCEEDED, WmsStates.HELD} else 0 for state in WmsStates
183 }
184 self.report.add(self.run)
185 print(self.report, file=self.actual_output)
187 self.assertEqual(self.actual_output.getvalue(), self.expected_output.getvalue())
189 def testAddWithDeletedFlag(self):
190 """Test adding a run with a deleted job."""
191 self.expected["X"][0] = "D"
192 print("\n".join(self.expected.pformat_all()), file=self.expected_output)
194 # Alter the run report to include a deleted job.
195 self.run.job_state_counts = {
196 state: 1 if state in {WmsStates.SUCCEEDED, WmsStates.DELETED} else 0 for state in WmsStates
197 }
198 self.report.add(self.run)
199 print(self.report, file=self.actual_output)
201 self.assertEqual(self.actual_output.getvalue(), self.expected_output.getvalue())
204class DetailedRunReportTestCase(unittest.TestCase):
205 """Test a detailed run report."""
207 def setUp(self):
208 self.fields = [("", "S")] + [(state.name, "I") for state in WmsStates] + [("EXPECTED", "i")]
210 table = Table(dtype=self.fields)
211 table.add_row(
212 ["TOTAL"]
213 + [1 if state in {WmsStates.RUNNING, WmsStates.SUCCEEDED} else 0 for state in WmsStates]
214 + [2]
215 )
216 table.add_row(["foo"] + [1 if state == WmsStates.SUCCEEDED else 0 for state in WmsStates] + [1])
217 table.add_row(["bar"] + [1 if state == WmsStates.RUNNING else 0 for state in WmsStates] + [1])
218 self.expected = DetailedRunReport.from_table(table)
220 self.run = WmsRunReport(
221 wms_id="1.0",
222 global_wms_id="foo#1.0",
223 path="/path/to/run",
224 label="label",
225 run="run",
226 project="dev",
227 campaign="testing",
228 payload="test",
229 operator="tester",
230 run_summary="foo:1;bar:1",
231 state=WmsStates.RUNNING,
232 jobs=[
233 WmsJobReport(wms_id="1.0", name="", label="foo", state=WmsStates.SUCCEEDED),
234 WmsJobReport(wms_id="2.0", name="", label="bar", state=WmsStates.RUNNING),
235 ],
236 total_number_jobs=2,
237 job_state_counts={
238 state: 1 if state in {WmsStates.SUCCEEDED, WmsStates.RUNNING} else 0 for state in WmsStates
239 },
240 job_summary={
241 "foo": {state: 1 if state == WmsStates.SUCCEEDED else 0 for state in WmsStates},
242 "bar": {state: 1 if state == WmsStates.RUNNING else 0 for state in WmsStates},
243 },
244 )
246 self.actual = DetailedRunReport(self.fields)
248 def testAddWithJobSummary(self):
249 """Test adding a run with a job summary."""
250 self.run.jobs = None
251 self.actual.add(self.run)
253 self.assertEqual(self.actual, self.expected)
255 def testAddWithJobs(self):
256 """Test adding a run with a job info, but not job summary."""
257 self.run.job_summary = None
258 self.actual.add(self.run)
260 self.assertEqual(self.actual, self.expected)
262 def testAddWithoutJobInfo(self):
263 """Test adding a run without either a job summary or job info."""
264 self.run.jobs = None
265 self.run.job_summary = None
266 self.actual.add(self.run)
268 self.assertEqual(len(self.actual), 1)
269 self.assertRegex(self.actual.message, r"^WARNING.*incomplete")
271 def testAddWithoutRunSummary(self):
272 """Test adding a run without a run summary."""
273 table = Table(dtype=self.fields)
274 table.add_row(
275 ["TOTAL"]
276 + [1 if state in {WmsStates.RUNNING, WmsStates.SUCCEEDED} else 0 for state in WmsStates]
277 + [2]
278 )
279 table.add_row(["bar"] + [1 if state == WmsStates.RUNNING else 0 for state in WmsStates] + [-1])
280 table.add_row(["foo"] + [1 if state == WmsStates.SUCCEEDED else 0 for state in WmsStates] + [-1])
281 expected = DetailedRunReport.from_table(table)
283 self.run.run_summary = None
284 self.actual.add(self.run)
286 self.assertRegex(self.actual.message, r"^WARNING.*sorted alphabetically")
287 self.assertEqual(self.actual, expected)
290if __name__ == "__main__": 290 ↛ 291line 290 didn't jump to line 291, because the condition on line 290 was never true
291 unittest.main()