Coverage for tests/test_report.py: 21%

118 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-15 11:01 +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/>. 

27 

28"""Tests for reporting mechanism.""" 

29 

30import io 

31import unittest 

32 

33from astropy.table import Table 

34from lsst.ctrl.bps import ( 

35 BaseRunReport, 

36 DetailedRunReport, 

37 SummaryRunReport, 

38 WmsJobReport, 

39 WmsRunReport, 

40 WmsStates, 

41) 

42 

43 

44class FakeRunReport(BaseRunReport): 

45 """A fake run report""" 

46 

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]) 

50 

51 

52class FakeRunReportTestCase(unittest.TestCase): 

53 """Test shared methods.""" 

54 

55 def setUp(self): 

56 self.fields = [("ID", "S"), ("STATE", "S")] 

57 

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)) 

61 

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) 

68 

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) 

74 

75 def testLength(self): 

76 self.assertEqual(len(self.report), 2) 

77 

78 def testClear(self): 

79 """Test clearing the report.""" 

80 self.report.clear() 

81 self.assertEqual(len(self.report), 0) 

82 

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) 

90 

91 actual_output = io.StringIO() 

92 self.report.sort("ID") 

93 print(self.report, file=actual_output) 

94 

95 self.assertEqual(actual_output.getvalue(), expected_output.getvalue()) 

96 

97 expected_output.close() 

98 actual_output.close() 

99 

100 def testSortWithUnknownKey(self): 

101 """Test sorting the report using unknown column.""" 

102 with self.assertRaises(AttributeError): 

103 self.report.sort("foo") 

104 

105 

106class SummaryRunReportTestCase(unittest.TestCase): 

107 """Test a summary run report.""" 

108 

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) 

141 

142 self.expected = Table(dtype=self.fields) 

143 self.expected.add_row(["", "RUNNING", "50", "1.0", "tester", "dev", "testing", "test", "run"]) 

144 

145 self.expected_output = io.StringIO() 

146 self.actual_output = io.StringIO() 

147 

148 def tearDown(self): 

149 self.expected_output.close() 

150 self.actual_output.close() 

151 

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) 

155 

156 self.report.add(self.run) 

157 print(self.report, file=self.actual_output) 

158 

159 self.assertEqual(self.actual_output.getvalue(), self.expected_output.getvalue()) 

160 

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) 

165 

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) 

172 

173 self.assertEqual(self.actual_output.getvalue(), self.expected_output.getvalue()) 

174 

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) 

179 

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) 

186 

187 self.assertEqual(self.actual_output.getvalue(), self.expected_output.getvalue()) 

188 

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) 

193 

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) 

200 

201 self.assertEqual(self.actual_output.getvalue(), self.expected_output.getvalue()) 

202 

203 

204class DetailedRunReportTestCase(unittest.TestCase): 

205 """Test a detailed run report.""" 

206 

207 def setUp(self): 

208 self.fields = [("", "S")] + [(state.name, "I") for state in WmsStates] + [("EXPECTED", "i")] 

209 

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) 

219 

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 ) 

245 

246 self.actual = DetailedRunReport(self.fields) 

247 

248 def testAddWithJobSummary(self): 

249 """Test adding a run with a job summary.""" 

250 self.run.jobs = None 

251 self.actual.add(self.run) 

252 

253 self.assertEqual(self.actual, self.expected) 

254 

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) 

259 

260 self.assertEqual(self.actual, self.expected) 

261 

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) 

267 

268 self.assertEqual(len(self.actual), 1) 

269 self.assertRegex(self.actual.message, r"^WARNING.*incomplete") 

270 

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) 

282 

283 self.run.run_summary = None 

284 self.actual.add(self.run) 

285 

286 self.assertRegex(self.actual.message, r"^WARNING.*sorted alphabetically") 

287 self.assertEqual(self.actual, expected) 

288 

289 

290if __name__ == "__main__": 290 ↛ 291line 290 didn't jump to line 291, because the condition on line 290 was never true

291 unittest.main()