Coverage for tests/test_report.py: 21%
118 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-15 02:49 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-15 02:49 -0800
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 program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22"""Tests for reporting mechanism."""
24import io
25import unittest
27from astropy.table import Table
28from lsst.ctrl.bps import (
29 BaseRunReport,
30 DetailedRunReport,
31 SummaryRunReport,
32 WmsJobReport,
33 WmsRunReport,
34 WmsStates,
35)
38class FakeRunReport(BaseRunReport):
39 """A fake run report"""
41 def add(self, run_report, use_global_id=False):
42 id_ = run_report.global_wms_id if use_global_id else run_report.wms_id
43 self._table.add_row([id_, run_report.state.name])
46class FakeRunReportTestCase(unittest.TestCase):
47 """Test shared methods."""
49 def setUp(self):
50 self.fields = [("ID", "S"), ("STATE", "S")]
52 self.report = FakeRunReport(self.fields)
53 self.report.add(WmsRunReport(wms_id="2.0", state=WmsStates.RUNNING))
54 self.report.add(WmsRunReport(wms_id="1.0", state=WmsStates.SUCCEEDED))
56 def testEquality(self):
57 """Test if two reports are identical."""
58 other = FakeRunReport(self.fields)
59 other.add(WmsRunReport(wms_id="2.0", state=WmsStates.RUNNING))
60 other.add(WmsRunReport(wms_id="1.0", state=WmsStates.SUCCEEDED))
61 self.assertEqual(self.report, other)
63 def testInequality(self):
64 """Test if two reports are not identical."""
65 other = FakeRunReport(self.fields)
66 other.add(WmsRunReport(wms_id="1.0", state=WmsStates.FAILED))
67 self.assertNotEqual(self.report, other)
69 def testLength(self):
70 self.assertEqual(len(self.report), 2)
72 def testClear(self):
73 """Test clearing the report."""
74 self.report.clear()
75 self.assertEqual(len(self.report), 0)
77 def testSortWithKnownKey(self):
78 """Test sorting the report using known column."""
79 expected_output = io.StringIO()
80 expected = Table(dtype=self.fields)
81 expected.add_row(["1.0", WmsStates.SUCCEEDED.name])
82 expected.add_row(["2.0", WmsStates.RUNNING.name])
83 print(expected, file=expected_output)
85 actual_output = io.StringIO()
86 self.report.sort("ID")
87 print(self.report, file=actual_output)
89 self.assertEqual(actual_output.getvalue(), expected_output.getvalue())
91 expected_output.close()
92 actual_output.close()
94 def testSortWithUnknownKey(self):
95 """Test sorting the report using unknown column."""
96 with self.assertRaises(AttributeError):
97 self.report.sort("foo")
100class SummaryRunReportTestCase(unittest.TestCase):
101 """Test a summary run report."""
103 def setUp(self):
104 self.fields = [
105 ("X", "S"),
106 ("STATE", "S"),
107 ("%S", "S"),
108 ("ID", "S"),
109 ("OPERATOR", "S"),
110 ("PROJECT", "S"),
111 ("CAMPAIGN", "S"),
112 ("PAYLOAD", "S"),
113 ("RUN", "S"),
114 ]
115 self.run = WmsRunReport(
116 wms_id="1.0",
117 global_wms_id="foo#1.0",
118 path="/path/to/run",
119 label="label",
120 run="run",
121 project="dev",
122 campaign="testing",
123 payload="test",
124 operator="tester",
125 run_summary="foo:1;bar:1",
126 state=WmsStates.RUNNING,
127 jobs=None,
128 total_number_jobs=2,
129 job_state_counts={
130 state: 1 if state in {WmsStates.SUCCEEDED, WmsStates.RUNNING} else 0 for state in WmsStates
131 },
132 job_summary=None,
133 )
134 self.report = SummaryRunReport(self.fields)
136 self.expected = Table(dtype=self.fields)
137 self.expected.add_row(["", "RUNNING", "50", "1.0", "tester", "dev", "testing", "test", "run"])
139 self.expected_output = io.StringIO()
140 self.actual_output = io.StringIO()
142 def tearDown(self):
143 self.expected_output.close()
144 self.actual_output.close()
146 def testAddWithNoFlag(self):
147 """Test adding a report for a run with no issues."""
148 print("\n".join(self.expected.pformat_all()), file=self.expected_output)
150 self.report.add(self.run)
151 print(self.report, file=self.actual_output)
153 self.assertEqual(self.actual_output.getvalue(), self.expected_output.getvalue())
155 def testAddWithFailedFlag(self):
156 """Test adding a run with a failed job."""
157 self.expected["X"][0] = "F"
158 print("\n".join(self.expected.pformat_all()), file=self.expected_output)
160 # Alter the run report to include a failed job.
161 self.run.job_state_counts = {
162 state: 1 if state in {WmsStates.FAILED, WmsStates.SUCCEEDED} else 0 for state in WmsStates
163 }
164 self.report.add(self.run)
165 print(self.report, file=self.actual_output)
167 self.assertEqual(self.actual_output.getvalue(), self.expected_output.getvalue())
169 def testAddWithHeldFlag(self):
170 """Test adding a run with a held job."""
171 self.expected["X"][0] = "H"
172 print("\n".join(self.expected.pformat_all()), file=self.expected_output)
174 # Alter the run report to include a held job.
175 self.run.job_state_counts = {
176 state: 1 if state in {WmsStates.SUCCEEDED, WmsStates.HELD} else 0 for state in WmsStates
177 }
178 self.report.add(self.run)
179 print(self.report, file=self.actual_output)
181 self.assertEqual(self.actual_output.getvalue(), self.expected_output.getvalue())
183 def testAddWithDeletedFlag(self):
184 """Test adding a run with a deleted job."""
185 self.expected["X"][0] = "D"
186 print("\n".join(self.expected.pformat_all()), file=self.expected_output)
188 # Alter the run report to include a deleted job.
189 self.run.job_state_counts = {
190 state: 1 if state in {WmsStates.SUCCEEDED, WmsStates.DELETED} else 0 for state in WmsStates
191 }
192 self.report.add(self.run)
193 print(self.report, file=self.actual_output)
195 self.assertEqual(self.actual_output.getvalue(), self.expected_output.getvalue())
198class DetailedRunReportTestCase(unittest.TestCase):
199 """Test a detailed run report."""
201 def setUp(self):
202 self.fields = [("", "S")] + [(state.name, "I") for state in WmsStates] + [("EXPECTED", "i")]
204 table = Table(dtype=self.fields)
205 table.add_row(
206 ["TOTAL"]
207 + [1 if state in {WmsStates.RUNNING, WmsStates.SUCCEEDED} else 0 for state in WmsStates]
208 + [2]
209 )
210 table.add_row(["foo"] + [1 if state == WmsStates.SUCCEEDED else 0 for state in WmsStates] + [1])
211 table.add_row(["bar"] + [1 if state == WmsStates.RUNNING else 0 for state in WmsStates] + [1])
212 self.expected = DetailedRunReport.from_table(table)
214 self.run = WmsRunReport(
215 wms_id="1.0",
216 global_wms_id="foo#1.0",
217 path="/path/to/run",
218 label="label",
219 run="run",
220 project="dev",
221 campaign="testing",
222 payload="test",
223 operator="tester",
224 run_summary="foo:1;bar:1",
225 state=WmsStates.RUNNING,
226 jobs=[
227 WmsJobReport(wms_id="1.0", name="", label="foo", state=WmsStates.SUCCEEDED),
228 WmsJobReport(wms_id="2.0", name="", label="bar", state=WmsStates.RUNNING),
229 ],
230 total_number_jobs=2,
231 job_state_counts={
232 state: 1 if state in {WmsStates.SUCCEEDED, WmsStates.RUNNING} else 0 for state in WmsStates
233 },
234 job_summary={
235 "foo": {state: 1 if state == WmsStates.SUCCEEDED else 0 for state in WmsStates},
236 "bar": {state: 1 if state == WmsStates.RUNNING else 0 for state in WmsStates},
237 },
238 )
240 self.actual = DetailedRunReport(self.fields)
242 def testAddWithJobSummary(self):
243 """Test adding a run with a job summary."""
244 self.run.jobs = None
245 self.actual.add(self.run)
247 self.assertEqual(self.actual, self.expected)
249 def testAddWithJobs(self):
250 """Test adding a run with a job info, but not job summary."""
251 self.run.job_summary = None
252 self.actual.add(self.run)
254 self.assertEqual(self.actual, self.expected)
256 def testAddWithoutJobInfo(self):
257 """Test adding a run without either a job summary or job info."""
258 self.run.jobs = None
259 self.run.job_summary = None
260 self.actual.add(self.run)
262 self.assertEqual(len(self.actual), 1)
263 self.assertRegex(self.actual.message, r"^WARNING.*incomplete")
265 def testAddWithoutRunSummary(self):
266 """Test adding a run without a run summary."""
267 table = Table(dtype=self.fields)
268 table.add_row(
269 ["TOTAL"]
270 + [1 if state in {WmsStates.RUNNING, WmsStates.SUCCEEDED} else 0 for state in WmsStates]
271 + [2]
272 )
273 table.add_row(["bar"] + [1 if state == WmsStates.RUNNING else 0 for state in WmsStates] + [-1])
274 table.add_row(["foo"] + [1 if state == WmsStates.SUCCEEDED else 0 for state in WmsStates] + [-1])
275 expected = DetailedRunReport.from_table(table)
277 self.run.run_summary = None
278 self.actual.add(self.run)
280 self.assertRegex(self.actual.message, r"^WARNING.*sorted alphabetically")
281 self.assertEqual(self.actual, expected)
284if __name__ == "__main__": 284 ↛ 285line 284 didn't jump to line 285, because the condition on line 284 was never true
285 unittest.main()