Coverage for CIResults / tests / test_models.py: 100%

963 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-10 09:22 +0000

1from unittest.mock import patch, MagicMock, PropertyMock 

2from django.test import TestCase 

3from django.utils import timezone 

4from django.core.exceptions import ValidationError 

5from django.contrib.auth import get_user_model 

6 

7from CIResults.models import BugTracker, Bug, BugTrackerSLA, Person, BugTrackerAccount, BugComment 

8from CIResults.models import Test, Machine, RunConfigTag, RunConfig, TestSuite, TextStatus 

9from CIResults.models import TestResult, IssueFilter, IssueFilterAssociated, Issue, RunFilterStatistic 

10from CIResults.models import MachineTag, TestsuiteRun, UnknownFailure, KnownFailure, Component, Build 

11from CIResults.models import Rate, ReplicationScript, script_validator 

12 

13from model_bakery import baker 

14 

15import datetime 

16import pytz 

17 

18 

19class BugTrackerSLATests(TestCase): 

20 def test___str__(self): 

21 tracker = BugTracker(name="BugTracker") 

22 entry = BugTrackerSLA(tracker=tracker, priority="high", SLA=datetime.timedelta(weeks=10, seconds=23)) 

23 self.assertEqual(str(entry), "BugTracker: high -> 70 days, 0:00:23") 

24 

25 

26class PersonTests(TestCase): 

27 def test_full(self): 

28 p = Person(full_name="John Doe", email="john.doe@isp.earth") 

29 self.assertEqual(str(p), "John Doe <john.doe@isp.earth>") 

30 

31 def test_full_name_only_only(self): 

32 p = Person(full_name="John Doe") 

33 self.assertEqual(str(p), "John Doe") 

34 

35 def test_email_only(self): 

36 p = Person(email="john.doe@isp.earth") 

37 self.assertEqual(str(p), "john.doe@isp.earth") 

38 

39 def test_no_information(self): 

40 p = Person() 

41 self.assertEqual(str(p), "(No name or email)") 

42 

43 

44class TestBugTrackerAccount(TestCase): 

45 def test_str(self): 

46 person = Person(full_name="John Doe", email="john.doe@isp.earth") 

47 self.assertEqual(str(BugTrackerAccount(person=person)), str(person)) 

48 

49 

50class BugTrackerTests(TestCase): 

51 def test_str(self): 

52 tracker = BugTracker(name="Freedesktop.org", short_name="fdo", 

53 separator="#", public=True, 

54 url="https://bugs.freedesktop.org/", 

55 bug_base_url="https://bugs.freedesktop.org/show_bug.cgi?id=") 

56 self.assertEqual(str(tracker), "Freedesktop.org") 

57 

58 @patch('CIResults.models.BugTrackerSLA.objects.filter', 

59 return_value=[MagicMock(priority="P1", SLA=datetime.timedelta(seconds=1)), 

60 MagicMock(priority="P2", SLA=datetime.timedelta(seconds=3)), 

61 MagicMock(priority="P3", SLA=datetime.timedelta(seconds=2))]) 

62 def test_SLAs_cached(self, filter_mocked): 

63 tracker = BugTracker(name="Tracker1", public=True) 

64 slas = tracker.SLAs_cached 

65 

66 filter_mocked.assert_called_with(tracker=tracker) 

67 self.assertEqual(slas, {"p1": filter_mocked.return_value[0].SLA, 

68 "p2": filter_mocked.return_value[1].SLA, 

69 "p3": filter_mocked.return_value[2].SLA}) 

70 

71 @patch('CIResults.models.BugTracker.tracker') 

72 def test_poll(self, tracker_mock): 

73 bug = MagicMock() 

74 bug.save = MagicMock() 

75 tracker = BugTracker() 

76 tracker.tracker._get_tracker_time = MagicMock(return_value=timezone.now()) 

77 tracker.poll(bug) 

78 tracker_mock.poll.assert_called_with(bug, False) 

79 bug.save.assert_not_called() 

80 self.assertLess(timezone.now() - bug.polled, datetime.timedelta(seconds=.1)) 

81 

82 @patch('CIResults.bugtrackers.Bugzilla') 

83 def test_tracker__bugzilla(self, bugzilla_mock): 

84 self.assertEqual(BugTracker(tracker_type="bugzilla").tracker, bugzilla_mock.return_value) 

85 

86 @patch('CIResults.bugtrackers.Jira') 

87 def test_tracker__jira(self, jira_mock): 

88 self.assertEqual(BugTracker(tracker_type="jira").tracker, jira_mock.return_value) 

89 

90 @patch('CIResults.bugtrackers.GitLab') 

91 def test_tracker__gitlab(self, gitlab_mock): 

92 self.assertEqual(BugTracker(tracker_type="gitlab").tracker, gitlab_mock.return_value) 

93 

94 @patch('CIResults.bugtrackers.Untracked') 

95 def test_tracker__jira_untracked(self, untracked_mock): 

96 self.assertEqual(BugTracker(tracker_type="jira_untracked").tracker, untracked_mock.return_value) 

97 

98 def test_tracker__invalid_name(self): 

99 tracker = BugTracker(tracker_type="invalid_name") 

100 self.assertRaisesMessage(ValueError, "The bugtracker type 'invalid_name' is unknown", 

101 getattr, tracker, "tracker") 

102 

103 @patch.object(BugTracker, 'tracker', PropertyMock()) 

104 def test_open_statuses(self): 

105 tracker = BugTracker(tracker_type="tracker") 

106 self.assertEqual(tracker.open_statuses, BugTracker.tracker.open_statuses) 

107 

108 @patch('CIResults.models.Bug.objects.filter', return_value=[MagicMock(), MagicMock(), MagicMock()]) 

109 @patch('CIResults.models.BugTracker.poll') 

110 def test_poll_all(self, poll_mocked, bugs_mocked): 

111 tracker = BugTracker(tracker_type="untracked", public=True) 

112 tracker.poll_all() 

113 

114 # Check that filter got called with the right argument 

115 bugs_mocked.assert_called_with(tracker=tracker) 

116 

117 # Check that every bug we set up got polled 

118 self.assertEqual(poll_mocked.call_count, 3) 

119 for i in range(3): 

120 poll_mocked.assert_any_call(bugs_mocked.return_value[i]) 

121 

122 self.assertLess(abs((timezone.now() - tracker.polled).total_seconds()), .1) 

123 

124 @patch('CIResults.models.BugTracker.poll') 

125 def test_poll_all__custom_list(self, poll_mocked): 

126 bugs = [MagicMock(), MagicMock(), MagicMock()] 

127 

128 tracker = BugTracker(tracker_type="untracked", public=True) 

129 tracker.poll_all(bugs=bugs) 

130 

131 # Check that every bug we set up got polled 

132 self.assertEqual(poll_mocked.call_count, 3) 

133 for i in range(3): 

134 poll_mocked.assert_any_call(bugs[i]) 

135 

136 self.assertLess(abs((timezone.now() - tracker.polled).total_seconds()), 1) 

137 

138 @patch('CIResults.models.Bug.objects.filter', return_value=[MagicMock(), MagicMock(), MagicMock()]) 

139 @patch('CIResults.models.BugTracker.poll') 

140 def test_poll_all_interrupt(self, poll_mocked, bugs_mocked): 

141 tracker = BugTracker() 

142 

143 stop_event = MagicMock(is_set=MagicMock(side_effect=[False, False, True])) 

144 tracker.poll_all(stop_event) 

145 

146 # Check that filter got called with the right argument 

147 bugs_mocked.assert_called_with(tracker=tracker) 

148 

149 # Check that every bug we set up got polled 

150 self.assertEqual(poll_mocked.call_count, 2) 

151 for i in range(2): 

152 poll_mocked.assert_any_call(bugs_mocked.return_value[i]) 

153 

154 def test_components_followed_list(self): 

155 tracker = BugTracker(public=True) 

156 self.assertEqual(tracker.components_followed_list, []) 

157 

158 tracker.components_followed = "COMPONENT1,COMPONENT2, COMPONENT3" 

159 self.assertEqual(tracker.components_followed_list, ["COMPONENT1", "COMPONENT2", "COMPONENT3"]) 

160 

161 def test_get_or_create_bugs(self): 

162 tracker = BugTracker.objects.create(name='tracker', tracker_type='jira_untracked', public=True) 

163 Bug.objects.create(tracker=tracker, bug_id='1') 

164 Bug.objects.create(tracker=tracker, bug_id='3') 

165 

166 self.assertEqual(len(Bug.objects.all()), 2) 

167 tracker.get_or_create_bugs(set(['1', '2', '3'])) 

168 self.assertEqual(len(Bug.objects.all()), 3) 

169 

170 @patch('CIResults.models.BugTracker.open_statuses', new_callable=PropertyMock) 

171 def test_is_bug_open(self, open_statuses_mock): 

172 open_statuses_mock.return_value = ["status1", "status3"] 

173 tracker = BugTracker(public=True) 

174 bug_status1 = Bug(tracker=tracker, status="status1") 

175 bug_status2 = Bug(tracker=tracker, status="status2") 

176 bug_status3 = Bug(tracker=tracker, status="status3") 

177 bug_no_status = Bug(tracker=tracker, status=None) 

178 

179 for bug, is_open in [(bug_status1, True), (bug_status2, False), (bug_status3, True), 

180 (bug_no_status, False)]: 

181 self.assertEqual(tracker.is_bug_open(bug), is_open, 

182 "{}: Should be opened? {}".format(bug.status, is_open)) 

183 

184 @patch('CIResults.models.Bug.objects.filter', return_value=set([MagicMock(spec=Bug, bug_id='existing1'), 

185 MagicMock(spec=Bug, bug_id='existing2')])) 

186 def test_open_bugs__without_followed_list(self, bugs_filter_mocked): 

187 tracker = BugTracker.objects.create(name='tracker', tracker_type='jira_untracked', public=True) 

188 

189 self.assertEqual(tracker.open_bugs(), bugs_filter_mocked.return_value) 

190 bugs_filter_mocked.assert_called_with(tracker=tracker, status__in=tracker.open_statuses) 

191 

192 @patch('CIResults.models.BugTracker.get_or_create_bugs', return_value=set([MagicMock(spec=Bug, 

193 bug_id='searched_bug')])) 

194 @patch('CIResults.bugtrackers.Untracked.search_bugs_ids') 

195 @patch('CIResults.models.Bug.objects.filter', return_value=set([MagicMock(spec=Bug, bug_id='existing1'), 

196 MagicMock(spec=Bug, bug_id='existing2')])) 

197 def test_open_bugs__with_followed_list(self, bugs_filter_mocked, search_bugs_ids_mocked, 

198 get_or_create_bugs_mocked): 

199 tracker = BugTracker.objects.create(name='tracker', tracker_type='jira_untracked', public=True, 

200 components_followed="product1,product2") 

201 

202 self.assertEqual(tracker.open_bugs(), bugs_filter_mocked.return_value | get_or_create_bugs_mocked.return_value) 

203 bugs_filter_mocked.assert_called_with(tracker=tracker, status__in=tracker.open_statuses) 

204 search_bugs_ids_mocked.assert_called_with(components=["product1", "product2"], status=tracker.open_statuses) 

205 

206 @patch('CIResults.models.BugTracker.open_bugs') 

207 def test_followed_bugs(self, open_bugs_mock): 

208 tracker1 = BugTracker.objects.create(name='tracker1', tracker_type='jira_untracked', public=True) 

209 tracker2 = BugTracker.objects.create(name='tracker2', tracker_type='jira_untracked', public=True) 

210 

211 # Create the "open bugs" 

212 open_bugs_mock.return_value = set([Bug.objects.create(bug_id='1234', tracker=tracker1), 

213 Bug.objects.create(bug_id='1235', tracker=tracker1)]) 

214 

215 # Create the bugs associated to the issues 

216 bug1 = Bug.objects.create(bug_id='1', tracker=tracker1) 

217 bug2 = Bug.objects.create(bug_id='2', tracker=tracker2) 

218 bug_old_issue = Bug.objects.create(bug_id='OLD_ISSUE', tracker=tracker1) 

219 Bug.objects.create(bug_id='UNREFERENCED', tracker=tracker2) 

220 

221 active = Issue.objects.create() 

222 active.bugs.add(bug1, bug2) 

223 

224 archived = Issue.objects.create(archived_on=timezone.now()) 

225 archived.bugs.add(bug2, bug_old_issue) 

226 

227 # Make sure that only the expected bugs are returned for each tracker 

228 self.assertEqual(tracker1.followed_bugs(), set([bug1]) | open_bugs_mock.return_value) 

229 self.assertEqual(tracker2.followed_bugs(), set([bug2]) | open_bugs_mock.return_value) 

230 

231 @patch('CIResults.models.BugTracker.get_or_create_bugs') 

232 @patch('CIResults.bugtrackers.Untracked.search_bugs_ids') 

233 @patch('CIResults.models.BugTracker.bugs_in_issues') 

234 @patch('CIResults.models.timezone') 

235 def test_updated_bugs(self, tz_mock, bugs_in_issues_mock, search_bugs_mock, get_or_create_mock): 

236 tracker1 = BugTracker.objects.create(name='tracker1', tracker_type='jira_untracked', 

237 public=True, components_followed="FOO") 

238 tracker1.tracker.open_statuses = ["Open"] 

239 tracker1.polled = datetime.datetime.fromtimestamp(0, pytz.utc) 

240 tracker1.tracker._get_tracker_time = MagicMock(return_value=tracker1.polled + datetime.timedelta(seconds=20)) 

241 tz_mock.now.return_value = tracker1.polled + datetime.timedelta(seconds=10) 

242 

243 all_upd_bugs_ids = set(["bug2", "bug3", "bug4"]) 

244 open_bugs_ids = set(["bug1", "bug3"]) 

245 

246 issue_bug1 = Bug.objects.create(bug_id='bug4', status="Closed", tracker=tracker1) 

247 issue_bug2 = Bug.objects.create(bug_id='bug5', status="Closed", tracker=tracker1) 

248 

249 Bug.objects.create(bug_id='bug1', status="Open", tracker=tracker1) 

250 Bug.objects.create(bug_id='bug2', status="Closed", tracker=tracker1) 

251 Bug.objects.create(bug_id='bug3', status="Open", tracker=tracker1) 

252 

253 search_bugs_mock.side_effect = [all_upd_bugs_ids, 

254 open_bugs_ids] 

255 

256 bugs_in_issues_mock.return_value = set([issue_bug1, issue_bug2]) 

257 tracker1.updated_bugs() 

258 self.assertEqual(datetime.datetime.fromtimestamp(0, pytz.utc) + datetime.timedelta(seconds=10), 

259 search_bugs_mock.call_args_list[0][1]['updated_since']) 

260 

261 """ 

262 bug1 - (not returned) open but not updated 

263 bug2 - (not returned) updated but not open 

264 bug3 - (returned) updated and open 

265 bug4 - (returned) not open, but updated and associated to issue 

266 bug5 - (not returned) not open or updated 

267 """ 

268 get_or_create_mock.assert_called_with(set(["bug3", "bug4"])) 

269 

270 @patch('CIResults.models.BugTracker.get_or_create_bugs') 

271 @patch('CIResults.bugtrackers.Untracked.search_bugs_ids') 

272 @patch('CIResults.models.BugTracker.bugs_in_issues') 

273 @patch('CIResults.models.timezone') 

274 def test_unreplicated_bugs(self, tz_mock, bugs_in_issues_mock, search_bugs_mock, get_or_create_mock): 

275 tracker1 = BugTracker.objects.create(name='tracker1', tracker_type='jira_untracked', 

276 public=True, components_followed="FOO") 

277 tracker1.tracker.open_statuses = ["Open"] 

278 

279 issue_bug1 = Bug.objects.create(bug_id='bug1', status="Open", tracker=tracker1) 

280 issue_bug2 = Bug.objects.create(bug_id='bug2', status="Closed", tracker=tracker1) 

281 

282 Bug.objects.create(bug_id='bug3', status="Open", tracker=tracker1, parent=issue_bug1) 

283 Bug.objects.create(bug_id='bug4', status="Closed", tracker=tracker1, parent=issue_bug2) 

284 Bug.objects.create(bug_id='bug5', status="Open", tracker=tracker1) 

285 

286 tracker1.unreplicated_bugs() 

287 get_or_create_mock.assert_called_with(set(["bug5"])) 

288 

289 

290class BugTests(TestCase): 

291 Model = Bug 

292 

293 def setUp(self): 

294 self.tracker = BugTracker(name="Freedesktop.org", short_name="fdo", 

295 separator="#", public=True, 

296 url="https://bugs.freedesktop.org/", 

297 bug_base_url="https://bugs.freedesktop.org/show_bug.cgi?id=") 

298 self.bug = Bug(tracker=self.tracker, bug_id="1234", title="random title", 

299 created=timezone.now() - datetime.timedelta(days=4)) 

300 

301 def test_short_name(self): 

302 self.assertEqual(self.bug.short_name, "fdo#1234") 

303 

304 def test_url(self): 

305 self.assertEqual(self.bug.url, 

306 "https://bugs.freedesktop.org/show_bug.cgi?id=1234") 

307 

308 def test_features_list(self): 

309 self.assertEqual(self.bug.features_list, []) 

310 self.bug.features = "feature1,feature2 , feature3" 

311 self.assertEqual(self.bug.features_list, ["feature1", "feature2", "feature3"]) 

312 

313 def test_platforms_list(self): 

314 self.assertEqual(self.bug.platforms_list, []) 

315 self.bug.platforms = "platform1,platform2 , platform3" 

316 self.assertEqual(self.bug.platforms_list, ["platform1", "platform2", "platform3"]) 

317 

318 def test_tags_list(self): 

319 self.assertEqual(self.bug.tags_list, []) 

320 self.bug.tags = "tag1,tag 2 , tag3" 

321 self.assertEqual(self.bug.tags_list, ["tag1", "tag 2", "tag3"]) 

322 

323 def test_has_new_comments(self): 

324 self.bug.updated = timezone.now() 

325 

326 # comments_polled = None 

327 self.assertTrue(self.bug.has_new_comments) 

328 

329 # comments_polled < updated 

330 self.bug.comments_polled = self.bug.updated - datetime.timedelta(seconds=1) 

331 self.assertTrue(self.bug.has_new_comments) 

332 

333 # comments_polled > updated 

334 self.bug.comments_polled = self.bug.updated + datetime.timedelta(seconds=1) 

335 self.assertFalse(self.bug.has_new_comments) 

336 

337 def test_add_first_seen_in(self): 

338 runconfig1 = RunConfig.objects.create(name="run1", temporary=True) 

339 runconfig2 = RunConfig.objects.create(name="run2", temporary=False) 

340 testsuite = TestSuite.objects.create(name="testsuite1", public=True) 

341 machine = Machine.objects.create(name="machine", public=True) 

342 ts_run = TestsuiteRun.objects.create( 

343 testsuite=testsuite, 

344 runconfig=runconfig1, 

345 machine=machine, 

346 run_id=0, 

347 start="2023-12-29 12:00", 

348 duration=datetime.timedelta(days=1), 

349 ) 

350 test = Test.objects.create(name="test", testsuite=testsuite, public=True) 

351 status = TextStatus.objects.create(name="status", testsuite=testsuite) 

352 test_result = TestResult.objects.create( 

353 test=test, 

354 ts_run=ts_run, 

355 status=status, 

356 stdout="h\n{} stdout1234\nYoo!".format(status.name), 

357 stderr="h\n{} stderr1234\nasdf".format(status.name), 

358 dmesg="h\n{} dmesg1234\nqwer".format(status.name), 

359 start="2023-12-29 12:00", 

360 duration=datetime.timedelta(days=1), 

361 ) 

362 issue = Issue.objects.create() 

363 filter = IssueFilter.objects.create(description="abc") 

364 ifa = IssueFilterAssociated.objects.create(issue=issue, filter=filter) 

365 KnownFailure.objects.create(result=test_result, matched_ifa=ifa) 

366 self.bug.add_first_seen_in(issue, False) 

367 self.assertEqual(self.bug.first_seen_in, runconfig1) 

368 

369 ts_run2 = TestsuiteRun.objects.create( 

370 testsuite=testsuite, 

371 runconfig=runconfig2, 

372 machine=machine, 

373 run_id=0, 

374 start="2023-12-29 12:00", 

375 duration=datetime.timedelta(days=1), 

376 ) 

377 test2 = Test.objects.create(name="test2", testsuite=testsuite, public=True) 

378 test_result2 = TestResult.objects.create( 

379 test=test2, 

380 ts_run=ts_run2, 

381 status=status, 

382 stdout="h\n{} stdout1234\nYoo!".format(status.name), 

383 stderr="h\n{} stderr1234\nasdf".format(status.name), 

384 dmesg="h\n{} dmesg1234\nqwer".format(status.name), 

385 start="2023-12-29 12:00", 

386 duration=datetime.timedelta(days=1), 

387 ) 

388 issue2 = Issue.objects.create() 

389 filter2 = IssueFilter.objects.create(description="abc") 

390 ifa2 = IssueFilterAssociated.objects.create(issue=issue2, filter=filter2) 

391 KnownFailure.objects.create(result=test_result2, matched_ifa=ifa2) 

392 self.bug.add_first_seen_in(issue2, False) 

393 self.assertEqual(self.bug.first_seen_in, runconfig1) 

394 

395 @patch('CIResults.models.BugComment.objects.filter') 

396 def test_comments_cached(self, filter_mock): 

397 self.assertEqual(self.bug.comments_cached, filter_mock.return_value.prefetch_related()) 

398 filter_mock.assert_called_with(bug=self.bug) 

399 

400 def test_SLA(self): 

401 tracker = BugTracker.objects.create(name="BugTracker", public=True) 

402 sla_high = BugTrackerSLA.objects.create(tracker=tracker, priority="HIGH", SLA=datetime.timedelta(seconds=23)) 

403 sla_low = BugTrackerSLA.objects.create(tracker=tracker, priority="low", SLA=datetime.timedelta(seconds=30)) 

404 

405 self.assertEqual(tracker.SLAs_cached, {"low": sla_low.SLA, "high": sla_high.SLA}) 

406 

407 bug = Bug(tracker=tracker, priority="low") 

408 self.assertEqual(bug.SLA, sla_low.SLA) 

409 

410 bug = Bug(tracker=tracker, priority="LOW") 

411 self.assertEqual(bug.SLA, sla_low.SLA) 

412 

413 bug = Bug(tracker=tracker, priority="invalid") 

414 self.assertEqual(bug.SLA, datetime.timedelta.max) 

415 

416 def test_SLA_deadline__triage_needed(self): 

417 # Check that if no developer has updated the bug, our deadline is set to the tracker's first_response_SLA 

418 self.tracker.save() 

419 self.bug.save() 

420 self.tracker.first_response_SLA = datetime.timedelta(days=2.1) 

421 self.assertEqual(self.bug.SLA_deadline, self.bug.created + self.tracker.first_response_SLA) 

422 

423 def test_SLA_deadline__normal_SLA(self): 

424 # Check that when we have a developer comment, we follow the SLA 

425 self.bug.SLA = datetime.timedelta(days=1) 

426 self.bug.last_updated_by_developer = timezone.now() 

427 self.assertAlmostEqual(self.bug.SLA_deadline.timestamp(), 

428 (self.bug.last_updated_by_developer + self.bug.SLA).timestamp(), 

429 places=1) 

430 

431 def test_SLA_deadline__infinite_SLA(self): 

432 # In the event where the SLA is infine, verify that we always set the deadline a year in advance 

433 self.bug.SLA = datetime.timedelta.max 

434 self.bug.last_updated_by_developer = timezone.now() - datetime.timedelta(days=30) 

435 self.assertAlmostEqual(self.bug.SLA_deadline.timestamp(), 

436 (timezone.now() + datetime.timedelta(days=365, seconds=1)).timestamp(), 

437 places=1) 

438 

439 def test_SLA_remaining_time__one_day_left(self): 

440 self.bug.SLA_deadline = timezone.now() - datetime.timedelta(days=1) 

441 self.assertAlmostEqual(self.bug.SLA_remaining_time.total_seconds(), 

442 datetime.timedelta(days=-1).total_seconds(), places=1) 

443 

444 def test_SLA_remaining_time__one_day_over(self): 

445 self.bug.SLA_deadline = timezone.now() + datetime.timedelta(days=1) 

446 self.assertLessEqual(abs(self.bug.SLA_remaining_time.total_seconds() - 

447 datetime.timedelta(days=1).total_seconds()), 1) 

448 

449 def test_SLA_remaining_str__one_day_over(self): 

450 self.bug.SLA_remaining_time = datetime.timedelta(days=-1) 

451 exp = "1 day, 0:00:00 ago" 

452 self.assertEqual(exp, self.bug.SLA_remaining_str) 

453 

454 def test_SLA_remaining_str__one_day_left(self): 

455 self.bug.SLA_remaining_time = datetime.timedelta(days=1) 

456 exp = "in 1 day, 0:00:00" 

457 self.assertEqual(exp, self.bug.SLA_remaining_str) 

458 

459 def test_effective_priority(self): 

460 self.bug.SLA_remaining_time = datetime.timedelta(days=3) 

461 self.bug.SLA = datetime.timedelta(hours=2.5) 

462 self.assertAlmostEqual(self.bug.effective_priority, -self.bug.SLA_remaining_time / self.bug.SLA) 

463 

464 def test_is_being_updated__never_flagged(self): 

465 self.assertFalse(self.bug.is_being_updated) 

466 

467 def test_is_being_updated__not_expired(self): 

468 self.bug.flagged_as_update_pending_on = timezone.now() 

469 self.assertTrue(self.bug.is_being_updated) 

470 

471 def test_is_being_updated__expired(self): 

472 self.bug.flagged_as_update_pending_on = timezone.now() - self.bug.UPDATE_PENDING_TIMEOUT 

473 self.assertFalse(self.bug.is_being_updated) 

474 

475 def test_update_pending_expires_in__never_flagged(self): 

476 self.assertEqual(self.bug.update_pending_expires_in, None) 

477 

478 @patch('django.utils.timezone.now', 

479 return_value=datetime.datetime.strptime('2019-01-01T00:00:05', "%Y-%m-%dT%H:%M:%S")) 

480 def test_update_pending_expires_in__not_expired(self, now_mocked): 

481 self.bug.flagged_as_update_pending_on = timezone.now() 

482 self.assertEqual(self.bug.update_pending_expires_in, self.bug.UPDATE_PENDING_TIMEOUT) 

483 

484 @patch('django.utils.timezone.now', 

485 return_value=datetime.datetime.strptime('2019-01-01T00:00:05', "%Y-%m-%dT%H:%M:%S")) 

486 def test_update_pending_expires_in__expired(self, now_mocked): 

487 self.bug.flagged_as_update_pending_on = timezone.now() - 2 * self.bug.UPDATE_PENDING_TIMEOUT 

488 self.assertEqual(self.bug.update_pending_expires_in, -self.bug.UPDATE_PENDING_TIMEOUT) 

489 

490 @patch('CIResults.models.BugTracker.poll') 

491 def test_poll(self, poll_mock): 

492 self.bug.poll() 

493 poll_mock.assert_called_with(self.bug, False) 

494 

495 def test_create(self): 

496 tracker = BugTracker.objects.create(name="Tracker2", tracker_type="jira", url="http://foo", 

497 project="TEST2", public=True) 

498 bug = Bug(tracker=tracker, title="random title") 

499 bug.tracker.tracker.create_bug = MagicMock(return_value=1) 

500 bug.create() 

501 self.assertEqual(bug.bug_id, 1) 

502 

503 def test_create_error(self): 

504 tracker = BugTracker.objects.create(name="Tracker2", tracker_type="jira", url="http://foo", 

505 project="TEST2", public=True) 

506 bug = Bug(tracker=tracker, title="random title") 

507 bug.tracker.tracker.create_bug = MagicMock(side_effect=ValueError) 

508 bug.create() 

509 self.assertNotEqual(bug.bug_id, 1) 

510 

511 def test_save_with_dict_in_custom_field(self): 

512 tracker = BugTracker.objects.create(name="BugTracker", public=True) 

513 with self.assertRaisesMessage(ValueError, 

514 'Values stored in custom_fields cannot be tuples, lists, dictionaries'): 

515 Bug.objects.create(tracker=tracker, custom_fields={"field": {"toto": "gaga"}}) 

516 

517 def test_save_with_list_in_custom_field(self): 

518 tracker = BugTracker.objects.create(name="BugTracker", public=True) 

519 with self.assertRaisesMessage(ValueError, 

520 'Values stored in custom_fields cannot be tuples, lists, dictionaries'): 

521 Bug.objects.create(tracker=tracker, custom_fields={"field": [0, 1, 2, 3]}) 

522 

523 def test_save_with_tuples_in_custom_field(self): 

524 tracker = BugTracker.objects.create(name="BugTracker", public=True) 

525 with self.assertRaisesMessage(ValueError, 

526 'Values stored in custom_fields cannot be tuples, lists, dictionaries'): 

527 Bug.objects.create(tracker=tracker, custom_fields={"field": (0, 1, 2, 3)}) 

528 

529 def test_update_from_dict(self): 

530 upd_dict = {'severity': 'High', 'priority': 'Medium', 'non-existant': 42, 

531 'id': 42, 'bug_id': 42, 'tracker_id': 42, 'tracker': 42, 

532 'parent_id': 42, 'parent': 42} 

533 self.bug.update_from_dict(upd_dict) 

534 

535 self.assertEqual(self.bug.severity, 'High') 

536 self.assertEqual(self.bug.priority, 'Medium') 

537 self.assertNotIn(42, self.bug.__dict__.values()) 

538 

539 def test_str(self): 

540 self.assertEqual(str(self.bug), "fdo#1234 - random title") 

541 

542 

543class TestReplicationScript(TestCase): 

544 def test_str(self): 

545 tracker = BugTracker.objects.create(name="Freedesktop.org", short_name="fdo", separator="#", public=False) 

546 tracker2 = BugTracker.objects.create(name="JIRA", short_name="jira", separator="#", public=False) 

547 rep_script = ReplicationScript.objects.create(name="My Script", source_tracker=tracker, 

548 destination_tracker=tracker2) 

549 self.assertEqual(str(rep_script), "<replication script 'My Script'>") 

550 

551 def test_script_validator(self): 

552 val_script = "def foo(): pass" 

553 res = script_validator(val_script) 

554 self.assertEqual(res, val_script) 

555 

556 def test_script_validator_error(self): 

557 inval_script = "def foo(" 

558 with self.assertRaises(ValidationError): 

559 script_validator(inval_script) 

560 

561 

562class TestBugComment(TestCase): 

563 def test_str(self): 

564 account = BugTrackerAccount(person=Person(full_name="John Doe", email="john.doe@isp.earth")) 

565 

566 tracker = BugTracker(name="Freedesktop.org", short_name="fdo", separator="#") 

567 bug = Bug(tracker=tracker, bug_id="1234", title="random title", 

568 created=timezone.now() - datetime.timedelta(days=4)) 

569 

570 comment = BugComment(bug=bug, account=account) 

571 self.assertEqual(str(comment), "{}'s comment by {}".format(bug, account)) 

572 

573 

574class TestBuild(TestCase): 

575 def setUp(self): 

576 self.component = Component.objects.create(name='component', public=True) 

577 

578 def test_url(self): 

579 self.assertEqual(Build(component=self.component, name='build').url, 

580 '') 

581 self.assertEqual(Build(component=self.component, name='build', version='version').url, 

582 'version') 

583 self.assertEqual(Build(component=self.component, name='build', repo='git://repo.com/repo', 

584 version='version').url, 

585 'version @ git://repo.com/repo') 

586 self.assertEqual(Build(component=self.component, name='build', upstream_url='https://repo.com/commit/version', 

587 repo='git://repo.com/repo', version='version').url, 

588 'https://repo.com/commit/version') 

589 

590 

591class VettableObjectMixin: 

592 def setUpVettableObject(self, vettable_object): 

593 self.vettable_object = vettable_object 

594 

595 def test_vet(self): 

596 self.assertFalse(self.vettable_object.vetted) 

597 self.vettable_object.vet() 

598 self.assertTrue(self.vettable_object.vetted) 

599 

600 self.assertRaisesMessage(ValueError, 'The object is already vetted', 

601 self.vettable_object.vet) 

602 

603 def test_suppress(self): 

604 self.vettable_object.vetted_on = timezone.now() 

605 

606 self.assertTrue(self.vettable_object.vetted) 

607 self.vettable_object.suppress() 

608 self.assertFalse(self.vettable_object.vetted) 

609 

610 self.assertRaisesMessage(ValueError, 'The object is already suppressed', 

611 self.vettable_object.suppress) 

612 

613 

614class TestTests(TestCase, VettableObjectMixin): 

615 def setUp(self): 

616 self.ts = TestSuite.objects.create(name="testsuite", description="nothing", public=True) 

617 self.test = Test.objects.create(testsuite=self.ts, name="test", public=True) 

618 

619 self.setUpVettableObject(self.test) 

620 

621 def test__str__(self): 

622 self.assertEqual(str(self.test), "{}: {}".format(self.ts.name, self.test.name)) 

623 

624 @patch('CIResults.models.IssueFilterAssociated.objects.filter') 

625 def test_in_active_ifas(self, mock_filter): 

626 self.test.in_active_ifas 

627 mock_filter.assert_called_with(deleted_on=None, filter__tests__in=[self.test]) 

628 

629 @patch.object(Test, 'in_active_ifas', [MagicMock(), MagicMock()]) 

630 def test_rename_public_test_to_existing_private_test(self): 

631 self.test.vetted_on = timezone.now() 

632 self.test.first_runconfig = RunConfig(name="test_runcfg") 

633 

634 new_test = Test.objects.create(testsuite=self.ts, name="test2", public=False, vetted_on=timezone.now(), 

635 first_runconfig=RunConfig.objects.create(name="test2_runcfg", temporary=False)) 

636 

637 self.test.rename(new_test.name) 

638 

639 # Fetch again the new test and check it got updated 

640 new_test = Test.objects.get(name="test2") 

641 self.assertTrue(new_test.public) 

642 self.assertEqual(new_test.testsuite, self.test.testsuite) 

643 self.assertEqual(new_test.first_runconfig.name, "test2_runcfg") # This field should not have changed 

644 self.assertEqual(new_test.vetted_on, self.test.vetted_on) 

645 

646 self.assertEqual(len(self.test.in_active_ifas), 2) 

647 for ifa in self.test.in_active_ifas: 

648 ifa.filter.tests.add.assert_called_with(new_test) 

649 

650 @patch.object(Test, 'in_active_ifas', [MagicMock(), MagicMock()]) 

651 def test_rename_test_to_new_bug(self): 

652 self.test.public = False 

653 self.test.vetted_on = timezone.now() 

654 self.test.first_runconfig = RunConfig(name="test_runcfg") 

655 

656 self.test.rename('test2') 

657 

658 # Fetchthe new test and check the fields got copied 

659 new_test = Test.objects.get(name="test2") 

660 self.assertFalse(new_test.public) 

661 self.assertEqual(new_test.testsuite, self.ts) 

662 self.assertEqual(new_test.first_runconfig, None) 

663 self.assertEqual(new_test.vetted_on, self.test.vetted_on) 

664 

665 self.assertEqual(len(self.test.in_active_ifas), 2) 

666 for ifa in self.test.in_active_ifas: 

667 ifa.filter.tests.add.assert_called_with(new_test) 

668 

669 

670class MachineTagTests(TestCase): 

671 def test_machines(self): 

672 tag = MachineTag.objects.create(name='tag', public=True) 

673 machine1 = Machine.objects.create(name='machine1', public=True) 

674 machine1.tags.add(tag) 

675 

676 machine2 = Machine.objects.create(name='machine2', public=True) 

677 machine2.tags.add(tag) 

678 

679 Machine.objects.create(name='machine3', public=True) 

680 

681 self.assertEqual(tag.machines, [machine1, machine2]) 

682 

683 def test_str(self): 

684 self.assertEqual(str(MachineTag(name='tag')), "tag") 

685 

686 

687class MachineTests(TestCase, VettableObjectMixin): 

688 def setUp(self): 

689 self.machine = Machine.objects.create(name="machine", public=True) 

690 self.setUpVettableObject(self.machine) 

691 

692 @patch('CIResults.models.Machine.tags') 

693 def test_tags_cached(self, tags_cached_mocked): 

694 self.assertEqual(self.machine.tags_cached, tags_cached_mocked.all.return_value) 

695 

696 def test__str__(self): 

697 self.assertEqual(str(self.machine), self.machine.name) 

698 

699 

700class RunConfigTests(TestCase): 

701 def setUp(self): 

702 self.testsuite = TestSuite.objects.create(name="testsuite", public=True) 

703 self.s_pass = TextStatus.objects.create(name="pass", testsuite=self.testsuite) 

704 self.s_fail = TextStatus.objects.create(name="fail", testsuite=self.testsuite) 

705 self.s_broken = TextStatus.objects.create(name="broken", testsuite=self.testsuite) 

706 self.testsuite.acceptable_statuses.add(self.s_pass) 

707 

708 self.runconfig = RunConfig.objects.create(name="run", temporary=True) 

709 

710 def test_public_no_tags(self): 

711 self.assertTrue(self.runconfig.public) 

712 

713 def test_public_all_public(self): 

714 public_tag1 = RunConfigTag.objects.create(name="public_tag1", public=True) 

715 public_tag2 = RunConfigTag.objects.create(name="public_tag2", public=True) 

716 

717 self.runconfig.tags.add(public_tag1) 

718 self.runconfig.tags.add(public_tag2) 

719 self.assertTrue(self.runconfig.public) 

720 

721 def test_public_when_one_tag_is_private(self): 

722 public_tag1 = RunConfigTag.objects.create(name="public_tag1", public=True) 

723 public_tag2 = RunConfigTag.objects.create(name="public_tag2", public=True) 

724 private_tag = RunConfigTag.objects.create(name="private_tag", public=False) 

725 

726 self.runconfig.tags.add(public_tag1) 

727 self.runconfig.tags.add(public_tag2) 

728 self.runconfig.tags.add(private_tag) 

729 self.assertFalse(self.runconfig.public) 

730 

731 def test_update_statistics(self): 

732 self.machine = Machine.objects.create(name="machine", public=True) 

733 self.ts_run = TestsuiteRun.objects.create(testsuite=self.testsuite, runconfig=self.runconfig, 

734 machine=self.machine, run_id=0, 

735 start=timezone.now(), duration=datetime.timedelta(hours=1)) 

736 self.tests = [] 

737 self.testresults = [] 

738 for i in range(4): 

739 self.tests.append(Test.objects.create(name="test{}".format(i), 

740 testsuite=self.testsuite, 

741 public=True)) 

742 

743 status = self.s_pass if i < 2 else self.s_fail 

744 self.testresults.append(TestResult.objects.create(test=self.tests[i], 

745 ts_run=self.ts_run, 

746 status=status, 

747 start=timezone.now(), 

748 duration=datetime.timedelta(seconds=3))) 

749 

750 self.issue = Issue.objects.create(description="Issue", filer="me@me.de") 

751 

752 # Create a filter that should not cover anything, and other that cover 

753 # and match a different count 

754 self.filters = [] 

755 f = IssueFilter.objects.create(description="Covers nothing") 

756 f.statuses.add(self.s_broken) 

757 for i in range(len(self.tests)): 

758 f = IssueFilter.objects.create(description="Filter{}".format(i)) 

759 f.tests.add(self.tests[i]) 

760 if i + 1 < len(self.tests): 

761 f.tests.add(self.tests[i + 1]) 

762 IssueFilterAssociated.objects.create(filter=f, issue=self.issue) 

763 

764 # Try computing statistics as a temporary run first 

765 stats = self.runconfig.update_statistics() 

766 self.assertEqual(len(stats), 0) 

767 

768 # Now check if the stastistics match with a non-temporary run 

769 self.runconfig.temporary = False 

770 stats = self.runconfig.update_statistics() 

771 stats.sort(key=lambda x: x.filter.id) 

772 

773 expected_results = [(0, 2), (1, 2), (2, 2), (1, 1)] 

774 for i, stat in enumerate(stats): 

775 self.assertEqual(stat.matched_count, expected_results[i][0]) 

776 self.assertEqual(stat.covered_count, expected_results[i][1]) 

777 

778 # TODO: Test runcfg_history and runcfg_history_offset 

779 

780 

781class TestSuiteTests(TestCase): 

782 def setUp(self): 

783 self.testsuite = TestSuite(name="testsuite1", public=True) 

784 self.testsuite.save() 

785 

786 def test_str(self): 

787 self.assertEqual(str(self.testsuite), "testsuite1") 

788 

789 def test_is_failure(self): 

790 statuses = [] 

791 for i in range(4): 

792 status = TextStatus(name="status{}".format(i), 

793 testsuite=self.testsuite) 

794 status.save() 

795 statuses.append(status) 

796 

797 self.testsuite.acceptable_statuses.add(statuses[2]) 

798 self.testsuite.acceptable_statuses.add(statuses[3]) 

799 

800 for i in range(0, 2): 

801 self.assertTrue(self.testsuite.is_failure(statuses[i])) 

802 for i in range(2, 4): 

803 self.assertFalse(self.testsuite.is_failure(statuses[i])) 

804 

805 

806class IssueTests(TestCase): 

807 Model = Issue 

808 

809 def setUp(self): 

810 self.user = get_user_model().objects.create(username='blabla') 

811 

812 created_on = timezone.now() - datetime.timedelta(seconds=1) 

813 self.issue = Issue.objects.create(description="blabla", 

814 filer="test@test.de", 

815 added_on=timezone.now()) 

816 

817 self.filters = [] 

818 self.filtersAssoc = [] 

819 for i in range(4): 

820 # Create the a filter and add it to the issue 

821 filter = IssueFilter.objects.create(description="Filter {}".format(i), 

822 added_on=created_on) 

823 self.filters.append(filter) 

824 

825 # Create the association between the filter and the issue 

826 # WARNING: if the index is 2, close it immediately 

827 deleted_on = None if i != 2 else timezone.now() 

828 assoc = IssueFilterAssociated.objects.create(filter=filter, issue=self.issue, 

829 added_on=created_on, added_by=self.user, 

830 deleted_on=deleted_on) 

831 self.filtersAssoc.append(assoc) 

832 

833 # Create multiple runconfigs 

834 self.runconfigs = [] 

835 for i in range(5): 

836 r = RunConfig.objects.create(name="Runconfig {}".format(i), 

837 temporary=False, 

838 added_on=created_on + (i - 2) * datetime.timedelta(seconds=1)) 

839 self.runconfigs.append(r) 

840 

841 for filter in self.filters: 

842 RunFilterStatistic.objects.create(runconfig=r, filter=filter, 

843 covered_count=0 if i < 2 else 10, 

844 matched_count=0 if i < 3 else 3) 

845 

846 self.issue.update_statistics() 

847 

848 def test_active_filters(self): 

849 # Check that all the current filters are here (excluding the filter2 

850 # because it is got deleted already 

851 filters = set([self.filtersAssoc[0], self.filtersAssoc[1], self.filtersAssoc[3]]) 

852 self.assertEqual(filters, set(self.issue.active_filters)) 

853 

854 # Now check that archiving does not change the result 

855 self.issue.archive(self.user) 

856 self.assertEqual(filters, set(self.issue.active_filters)) 

857 

858 def test_runconfigs_covered(self): 

859 runconfigs = set([self.runconfigs[2], self.runconfigs[3], self.runconfigs[4]]) 

860 self.assertEqual(runconfigs, self.issue.runconfigs_covered) 

861 

862 # Now check that archiving does not change the result 

863 self.issue.archive(self.user) 

864 self.assertEqual(runconfigs, self.issue.runconfigs_covered) 

865 

866 def test_runconfigs_affected(self): 

867 runconfigs = set([self.runconfigs[3], self.runconfigs[4]]) 

868 self.assertEqual(runconfigs, self.issue.runconfigs_affected) 

869 

870 # Now check that archiving does not change the result 

871 self.issue.archive(self.user) 

872 self.assertEqual(runconfigs, self.issue.runconfigs_affected) 

873 

874 def test_last_seen(self): 

875 self.assertEqual(self.issue.last_seen, self.runconfigs[-1].added_on) 

876 

877 def test_failure_rate(self): 

878 failure_rate = self.issue.failure_rate 

879 self.assertAlmostEqual(2 / 3, failure_rate.rate) 

880 self.assertEqual("2 / 3 runs (66.7%)", str(failure_rate)) 

881 

882 def test_matches(self): 

883 # Intercept the calls to CIResults.models.IssueFilter.matches 

884 patcher_api_call = patch('CIResults.models.IssueFilter.matches') 

885 mock_matches = patcher_api_call.start() 

886 mock_matches.return_value = False 

887 self.addCleanup(patcher_api_call.stop) 

888 

889 self.issue.matches(None) 

890 self.assertEqual(mock_matches.call_count, 3) 

891 

892 # Now archive the issue, and check matches returns False without 

893 # calling $filter.matches() 

894 mock_matches.reset_mock() 

895 self.issue.archive(self.user) 

896 self.assertFalse(self.issue.matches(None)) 

897 self.assertEqual(mock_matches.call_count, 0) 

898 

899 def __count_active_filters__(self): 

900 count = 0 

901 for e in IssueFilterAssociated.objects.filter(issue=self.issue): 

902 if e.deleted_on is None: 

903 count += 1 

904 else: 

905 self.assertLess(timezone.now() - e.deleted_on, 

906 datetime.timedelta(seconds=10), 

907 "The deleted_on time is incorrect") 

908 self.assertEqual(e.added_by, self.user) 

909 return count 

910 

911 def __check_comment_posted(self, render_and_leave_comment_on_all_bugs_mock, substring): 

912 render_and_leave_comment_on_all_bugs_mock.assert_called_once() 

913 args, kwargs = render_and_leave_comment_on_all_bugs_mock.call_args_list[0] 

914 self.assertIn(substring, args[0]) 

915 

916 @patch('CIResults.models.Issue.render_and_leave_comment_on_all_bugs') 

917 def test_archive(self, render_and_leave_comment_on_all_bugs_mock): 

918 # Check the default state 

919 self.assertFalse(self.issue.archived) 

920 self.assertEqual(self.issue.archived_on, None) 

921 self.assertEqual(self.__count_active_filters__(), 3) 

922 

923 # Archive the issue 

924 self.issue.archive(self.user) 

925 

926 # Check that the filters' association has been updated 

927 self.assertEqual(self.__count_active_filters__(), 0) 

928 

929 # Check that archived_on has been updated 

930 self.assertTrue(self.issue.archived) 

931 self.assertNotEqual(self.issue.archived_on, None) 

932 self.assertLess(timezone.now() - self.issue.archived_on, 

933 datetime.timedelta(seconds=1), 

934 "The archived_on time is incorrect") 

935 self.assertEqual(self.issue.archived_by, self.user) 

936 

937 # Check that we posted a comment on the bugs associated 

938 self.__check_comment_posted(render_and_leave_comment_on_all_bugs_mock, "archived") 

939 

940 # Check that archiving the issue again generates an error 

941 self.assertRaisesMessage(ValueError, "The issue is already archived", 

942 self.issue.archive, self.user) 

943 

944 @patch('CIResults.models.Issue.render_and_leave_comment_on_all_bugs') 

945 def test_restore(self, render_and_leave_comment_on_all_bugs_mock): 

946 # Archive the issue 

947 # TODO: Load a fixture with an archived issue and an unknown failure 

948 self.issue.archived_on = timezone.now() 

949 

950 # Restore the issue before checking the restoration process 

951 self.issue.restore() 

952 

953 # Check that the filters' association has been updated 

954 self.assertEqual(self.__count_active_filters__(), 3) 

955 

956 # Check that archived_on has been updated 

957 self.assertFalse(self.issue.archived) 

958 self.assertEqual(self.issue.archived_on, None) 

959 self.assertEqual(self.issue.archived_by, None) 

960 

961 # TODO: Make sure the unknown failure became a known failure, associated 

962 # to this issue. To be done after migrating to the fixture 

963 

964 # Check that we posted a comment on the bugs associated 

965 self.__check_comment_posted(render_and_leave_comment_on_all_bugs_mock, "restored") 

966 

967 # Check that restoring the issue again generates an error 

968 self.assertRaisesMessage(ValueError, "The issue is not currently archived", 

969 self.issue.restore) 

970 

971 def test_set_bugs(self): 

972 # Create some bugs 

973 bugs = [] 

974 tracker = BugTracker.objects.create(name="Tracker", tracker_type="jira_untracked", public=True) 

975 for i in range(5): 

976 bugs.append(Bug(tracker=tracker, bug_id=str(i), title="bug {}".format(i))) 

977 

978 # Add some bugs 

979 self.issue.set_bugs(bugs[2:4]) 

980 self.assertEqual(set(self.issue.bugs_cached), set(bugs[2:4])) 

981 

982 # Now try to update the bugs 

983 self.issue.set_bugs(bugs[0:3]) 

984 self.assertEqual(set(self.issue.bugs_cached), set(bugs[0:3])) 

985 

986 # Archive the issue, and see if updating the bug generates an assert 

987 self.issue.archive(self.user) 

988 self.assertRaisesMessage(ValueError, "The issue is archived, and thus read-only", 

989 self.issue.set_bugs, []) 

990 

991 def test___filter_add__(self): 

992 filter = baker.make(IssueFilter) 

993 self.issue.__filter_add__(filter, self.user) 

994 self.assertEqual( 

995 IssueFilterAssociated.objects.filter(filter=filter, issue=self.issue, added_by=self.user).count(), 1 

996 ) 

997 

998 @patch("CIResults.models.timezone.now") 

999 def test__assign_to_known_failures(self, timezone_now_mock): 

1000 date_reported = timezone.make_aware(datetime.datetime(2024, 1, 1), timezone.get_default_timezone()) 

1001 timezone_now_mock.return_value = date_reported 

1002 

1003 unknown_failures = baker.make(UnknownFailure, _quantity=3) 

1004 ifa = baker.make(IssueFilterAssociated) 

1005 

1006 date_now = timezone.make_aware(datetime.datetime(2024, 1, 2), timezone.get_default_timezone()) 

1007 timezone_now_mock.return_value = date_now 

1008 known_failures = self.issue._assign_to_known_failures(unknown_failures, ifa) 

1009 

1010 self.assertEqual(len(known_failures), 3) 

1011 self.assertEqual(UnknownFailure.objects.count(), 0) 

1012 for failure in known_failures: 

1013 self.assertEqual(failure.manually_associated_on, date_now) 

1014 self.assertEqual(failure.filing_delay, date_now - date_reported) 

1015 

1016 def test_replace_filter(self): 

1017 old_filter = IssueFilter.objects.create(description="old filter") 

1018 new_filter = IssueFilter.objects.create(description="new filter") 

1019 

1020 # Add the old filter once before deleting it 

1021 self.issue.set_filters([old_filter], self.user) 

1022 self.issue.set_filters([], self.user) 

1023 

1024 self.assertEqual(IssueFilterAssociated.objects.exclude(deleted_on=None).filter(filter=old_filter).count(), 1) 

1025 

1026 # Re-add the old filter, then replace it with the new one 

1027 self.issue.set_filters([old_filter], self.user) 

1028 self.issue.replace_filter(old_filter, new_filter, self.user) 

1029 

1030 self.assertEqual(IssueFilterAssociated.objects.exclude(deleted_on=None).filter(filter=old_filter).count(), 2) 

1031 self.assertEqual(IssueFilterAssociated.objects.filter(deleted_on=None, filter=new_filter).count(), 1) 

1032 

1033 # Archive the issue, and see if replacing the filter generates an assert 

1034 self.issue.archive(self.user) 

1035 self.assertRaisesMessage(ValueError, "The issue is archived, and thus read-only", 

1036 self.issue.replace_filter, new_filter, old_filter, self.user) 

1037 

1038 def test_set_filters(self): 

1039 # Check the current amount of filters 

1040 self.assertEqual(set([e.filter for e in self.issue.active_filters]), 

1041 set([self.filters[0], self.filters[1], self.filters[3]])) 

1042 self.assertEqual(set([e.filter for e in self.issue.all_filters]), set(self.filters)) 

1043 

1044 # Now try to update the filters 

1045 self.issue.set_filters(self.filters[0:3], self.user) 

1046 self.assertEqual(set([e.filter for e in self.issue.active_filters]), set(self.filters[0:3])) 

1047 self.assertEqual(set([e.filter for e in self.issue.all_filters]), set(self.filters)) 

1048 

1049 # Check that the deleted_on field has been updated for the filter 3 

1050 expected_len = [1, 1, 2, 1] 

1051 expected_deleted_none = [True, True, False, False] 

1052 for i in range(len(self.filters)): 

1053 db_assocs = IssueFilterAssociated.objects.filter(filter=self.filters[i], issue=self.issue) 

1054 self.assertEqual(len(db_assocs), expected_len[i], i) 

1055 if len(db_assocs) > 0: 

1056 self.assertEqual(db_assocs[0].deleted_on is None, expected_deleted_none[i], i) 

1057 

1058 # Archive the issue, and see if updating the filter generates an assert 

1059 self.issue.archive(self.user) 

1060 self.assertRaisesMessage(ValueError, "The issue is archived, and thus read-only", 

1061 self.issue.set_filters, [], self.user) 

1062 

1063 def test_str(self): 

1064 self.assertEqual(str(self.issue), "Issue: <empty>") 

1065 

1066 tracker = BugTracker(name="Freedesktop.org", short_name="fdo", 

1067 separator="#", public=True, 

1068 url="https://bugs.freedesktop.org/", 

1069 bug_base_url="https://bugs.freedesktop.org/show_bug.cgi?id=") 

1070 tracker.save() 

1071 bug1 = Bug.objects.create(tracker=tracker, bug_id="1234", title="random title") 

1072 bug2 = Bug.objects.create(tracker=tracker, bug_id="1235", title="random title") 

1073 

1074 self.issue.bugs.add(bug1) 

1075 self.assertEqual(str(self.issue), "Issue: fdo#1234 - random title") 

1076 

1077 self.issue.bugs.add(bug2) 

1078 self.assertEqual(str(self.issue), "Issue: [fdo#1234, fdo#1235]") 

1079 

1080 

1081class IssueFilterAssociatedTests(TestCase): 

1082 def setUp(self): 

1083 self.filter = IssueFilter.objects.create(description="Filter") 

1084 self.issue = Issue.objects.create(filer="m@x.org") 

1085 

1086 def test_delete(self): 

1087 # Create an association and check that the field deleted_on is not set 

1088 assoc = IssueFilterAssociated(filter=self.filter, issue=self.issue) 

1089 self.assertEqual(assoc.deleted_on, None) 

1090 self.assertEqual(assoc.id, None) 

1091 

1092 # Delete the association, and verify that it actually got saved to the DB 

1093 # by checking if the id has been set 

1094 user = get_user_model().objects.create(username='blabla') 

1095 assoc.delete(user, datetime.datetime.fromtimestamp(0, tz=pytz.utc)) 

1096 self.assertEqual(assoc.deleted_by, user) 

1097 self.assertNotEqual(assoc.id, None) 

1098 self.assertEqual(assoc.deleted_on, datetime.datetime.fromtimestamp(0, tz=pytz.utc)) 

1099 

1100 # Try deleting again with a new timestamp, and check that it did not change 

1101 assoc.delete(datetime.datetime.fromtimestamp(1, tz=pytz.utc)) 

1102 self.assertEqual(assoc.deleted_on, datetime.datetime.fromtimestamp(0, tz=pytz.utc)) 

1103 

1104 # Test what happens when we omit the delete's now argument 

1105 assoc = IssueFilterAssociated(filter=self.filter, issue=self.issue) 

1106 assoc.delete(user) 

1107 self.assertLess(abs(timezone.now() - assoc.deleted_on), datetime.timedelta(seconds=1)) 

1108 self.assertEqual(assoc.deleted_by, user) 

1109 

1110 # TODO: Test all the statistics! 

1111 

1112 

1113class TextStatusTests(TestCase, VettableObjectMixin): 

1114 def setUp(self): 

1115 self.testsuite = TestSuite.objects.create(name="testsuite", public=True) 

1116 self.status = TextStatus.objects.create(testsuite=self.testsuite, name="status") 

1117 self.setUpVettableObject(self.status) 

1118 

1119 self.pass_status = TextStatus.objects.create(testsuite=self.testsuite, name="pass") 

1120 self.testsuite.acceptable_statuses.add(self.pass_status) 

1121 

1122 self.notrun = TextStatus.objects.create(testsuite=self.testsuite, name="notrun") 

1123 self.testsuite.notrun_status = self.notrun 

1124 

1125 def test_color__with_specified_color(self): 

1126 self.assertEqual(TextStatus(color_hex="#123456").color, "#123456") 

1127 

1128 def test_color__default(self): 

1129 self.assertEqual(TextStatus().color, "#e9be2c") 

1130 

1131 def test_is_failure(self): 

1132 self.assertTrue(self.status.is_failure) 

1133 self.assertFalse(self.pass_status.is_failure) 

1134 

1135 def test_is_notrun(self): 

1136 self.assertTrue(self.notrun.is_notrun) 

1137 self.assertFalse(self.status.is_notrun) 

1138 

1139 def test_actual_severity(self): 

1140 self.assertEqual(TextStatus(severity=42).actual_severity, 42) 

1141 

1142 self.assertEqual(self.notrun.actual_severity, 0) 

1143 self.assertEqual(self.pass_status.actual_severity, 1) 

1144 self.assertEqual(self.status.actual_severity, 2) 

1145 

1146 def test_str(self): 

1147 self.assertEqual(str(self.status), "testsuite: status") 

1148 

1149 

1150class IssueFilterTests(TestCase): 

1151 def setUp(self): 

1152 self.tag = [] 

1153 self.runconfigs = [] 

1154 self.machine = [] 

1155 self.test = [] 

1156 self.status = [] 

1157 self.ts_run = [] 

1158 self.testresult = [] 

1159 

1160 self.testsuite = TestSuite(name="testsuite1", public=True) 

1161 self.testsuite.save() 

1162 

1163 # Create 4 instances of random objects 

1164 for i in range(4): 

1165 tag = RunConfigTag(public=True, name="tag{}".format(i)) 

1166 tag.save() 

1167 self.tag.append(tag) 

1168 

1169 machine = Machine(name="machine{}".format(i), public=True) 

1170 machine.save() 

1171 self.machine.append(machine) 

1172 

1173 test = Test(name="test{}".format(i), testsuite=self.testsuite, 

1174 public=True) 

1175 test.save() 

1176 self.test.append(test) 

1177 

1178 status = TextStatus(name="status{}".format(i), testsuite=self.testsuite) 

1179 status.save() 

1180 self.status.append(status) 

1181 

1182 # Tell which results are acceptable for the testsuite 

1183 self.testsuite.acceptable_statuses.add(self.status[1]) 

1184 self.testsuite.acceptable_statuses.add(self.status[2]) 

1185 

1186 # Create the runconfig and ts_runs 

1187 for i in range(3): 

1188 runconfig = RunConfig(name="runconfig{}".format(i), temporary=False) 

1189 runconfig.save() 

1190 

1191 # Add $i tags 

1192 if i > 0: 

1193 runconfig.tags.add(self.tag[i - 1]) 

1194 

1195 # Create a testsuite run for all machines 

1196 for machine in self.machine: 

1197 self.ts_run.append( 

1198 TestsuiteRun( 

1199 testsuite=self.testsuite, 

1200 runconfig=runconfig, 

1201 machine=machine, 

1202 run_id=0, 

1203 start="2023-12-29 12:00", 

1204 duration=datetime.timedelta(days=1), 

1205 ) 

1206 ) 

1207 self.runconfigs.append(runconfig) 

1208 

1209 # Create the test results 

1210 for test in self.test: 

1211 for ts_run in self.ts_run: 

1212 ts_run.save() 

1213 for status in self.status: 

1214 test_result = TestResult( 

1215 test=test, 

1216 ts_run=ts_run, 

1217 status=status, 

1218 stdout="h\n{} stdout1234\nYoo!".format(status.name), 

1219 stderr="h\n{} stderr1234\nasdf".format(status.name), 

1220 dmesg="h\n{} dmesg1234\nqwer".format(status.name), 

1221 start="2023-12-29 12:00", 

1222 duration=datetime.timedelta(days=1), 

1223 ) 

1224 test_result.save() 

1225 self.testresult.append(test_result) 

1226 

1227 self.filter = IssueFilter() 

1228 self.filter.save() 

1229 

1230 def test_empty(self): 

1231 for testresult in self.testresult: 

1232 self.assertTrue(self.filter.covers(testresult)) 

1233 self.assertTrue(self.filter.matches(testresult)) 

1234 

1235 # Test the conversion to user filters 

1236 expected = "" 

1237 self.assertEqual(self.filter.equivalent_user_query, expected) 

1238 self.assertEqual(self.filter._to_user_query(covers=True, matches=False), expected) 

1239 self.assertEqual(self.filter._to_user_query(covers=False, matches=True), expected) 

1240 

1241 def test_runconfig_tag_only(self): 

1242 self.filter.tags.add(self.tag[0]) 

1243 self.filter.tags.add(self.tag[2]) 

1244 

1245 filter_tags = set([self.tag[0].id, self.tag[2].id]) 

1246 for testresult in self.testresult: 

1247 tags = set([t.id for t in testresult.ts_run.runconfig.tags.all()]) 

1248 should_match = not filter_tags.isdisjoint(tags) 

1249 self.assertTrue(self.filter.covers(testresult) == should_match) 

1250 self.assertTrue(self.filter.matches(testresult) == should_match) 

1251 

1252 # Test the conversion to user filters 

1253 expected = 'runconfig_tag IS IN ["tag0", "tag2"]' 

1254 self.assertEqual(self.filter.equivalent_user_query, expected) 

1255 self.assertEqual(self.filter._to_user_query(covers=True, matches=False), expected) 

1256 self.assertEqual(self.filter._to_user_query(covers=False, matches=True), "") 

1257 

1258 def test_machine_and_machine_tags(self): 

1259 tag1 = MachineTag.objects.create(name="Tag1", public=True) 

1260 self.machine[0].tags.add(tag1) 

1261 self.machine[2].tags.add(tag1) 

1262 

1263 self.filter.machine_tags.add(tag1) 

1264 

1265 for testresult in self.testresult: 

1266 machine = testresult.ts_run.machine 

1267 should_match = (machine == self.machine[0] or machine == self.machine[2]) 

1268 self.assertTrue(self.filter.covers(testresult) == should_match) 

1269 self.assertTrue(self.filter.matches(testresult) == should_match) 

1270 

1271 # Test the conversion to user filters 

1272 expected = 'machine_tag IS IN ["Tag1"]' 

1273 self.assertEqual(self.filter.equivalent_user_query, expected) 

1274 self.assertEqual(self.filter._to_user_query(covers=True, matches=False), expected) 

1275 self.assertEqual(self.filter._to_user_query(covers=False, matches=True), "") 

1276 

1277 def test_machine_tag_only(self): 

1278 # Test that if a user asks for a tag that contains no machines, we do not match anything 

1279 tag1 = MachineTag.objects.create(name="Tag1", public=True) 

1280 self.filter.machine_tags.add(tag1) 

1281 

1282 for testresult in self.testresult: 

1283 self.assertFalse(self.filter.covers(testresult)) 

1284 self.assertFalse(self.filter.matches(testresult)) 

1285 

1286 # Test the conversion to user filters 

1287 expected = 'machine_tag IS IN ["Tag1"]' 

1288 self.assertEqual(self.filter.equivalent_user_query, expected) 

1289 self.assertEqual(self.filter._to_user_query(covers=True, matches=False), expected) 

1290 self.assertEqual(self.filter._to_user_query(covers=False, matches=True), "") 

1291 

1292 def test_machine_only(self): 

1293 self.filter.machines.add(self.machine[0]) 

1294 self.filter.machines.add(self.machine[2]) 

1295 

1296 for testresult in self.testresult: 

1297 machine = testresult.ts_run.machine 

1298 should_match = (machine == self.machine[0] or machine == self.machine[2]) 

1299 self.assertTrue(self.filter.covers(testresult) == should_match) 

1300 self.assertTrue(self.filter.matches(testresult) == should_match) 

1301 

1302 # Test the conversion to user filters 

1303 machine_names = [mach.name for mach in self.filter.machines_cached] 

1304 expected = 'machine_name IS IN ["{}", "{}"]'.format(machine_names[0], machine_names[1]) 

1305 self.assertEqual(self.filter.equivalent_user_query, expected) 

1306 self.assertEqual(self.filter._to_user_query(covers=True, matches=False), expected) 

1307 self.assertEqual(self.filter._to_user_query(covers=False, matches=True), "") 

1308 

1309 def test_test_only(self): 

1310 # Create another status from another testsuite 

1311 testsuite2 = TestSuite.objects.create(name="testsuite2", public=True) 

1312 test_ts2 = Test.objects.create(name="test-ts2", testsuite=testsuite2, public=True) 

1313 

1314 self.filter.tests.add(self.test[0], self.test[2], test_ts2) 

1315 

1316 for testresult in self.testresult: 

1317 test = testresult.test 

1318 should_match = (test == self.test[0] or test == self.test[2]) 

1319 self.assertTrue(self.filter.covers(testresult) == should_match) 

1320 self.assertTrue(self.filter.matches(testresult) == should_match) 

1321 

1322 # Generate all the valid queries that could be generated 

1323 query_opts = [] 

1324 q1 = '(testsuite_name = "testsuite1" AND test_name IS IN ["{}", "{}"])' 

1325 q2 = '(testsuite_name = "testsuite2" AND test_name IS IN ["test-ts2"])' 

1326 query_pattern = '({} OR {})' 

1327 query_opts.append(query_pattern.format(q1.format("test0", "test2"), q2)) 

1328 query_opts.append(query_pattern.format(q1.format("test2", "test0"), q2)) 

1329 query_opts.append(query_pattern.format(q2, q1.format("test2", "test0"))) 

1330 query_opts.append(query_pattern.format(q2, q1.format("test0", "test2"))) 

1331 

1332 # Test the conversion to user filters 

1333 self.assertIn(self.filter.equivalent_user_query, query_opts) 

1334 self.assertEqual(self.filter._to_user_query(covers=False, matches=True), "") 

1335 self.assertIn(self.filter._to_user_query(covers=True, matches=False), query_opts) 

1336 

1337 def test_results_only(self): 

1338 # Create another status from another testsuite 

1339 testsuite2 = TestSuite.objects.create(name="testsuite2", public=True) 

1340 status_ts2 = TextStatus.objects.create(name="status-ts2", testsuite=testsuite2) 

1341 

1342 self.filter.statuses.add(self.status[0], self.status[2], status_ts2) 

1343 

1344 for testresult in self.testresult: 

1345 status = testresult.status 

1346 should_match = (status == self.status[0] or status == self.status[2]) 

1347 self.assertTrue(self.filter.covers(testresult)) 

1348 self.assertTrue(self.filter.matches(testresult) == should_match, testresult) 

1349 

1350 # Generate all the valid queries that could be generated 

1351 query_opts = [] 

1352 q1 = '(testsuite_name = "testsuite1" AND status_name IS IN ["{}", "{}"])' 

1353 q2 = '(testsuite_name = "testsuite2" AND status_name IS IN ["status-ts2"])' 

1354 query_pattern = '({} OR {})' 

1355 query_opts.append(query_pattern.format(q1.format("status0", "status2"), q2)) 

1356 query_opts.append(query_pattern.format(q1.format("status2", "status0"), q2)) 

1357 query_opts.append(query_pattern.format(q2, q1.format("status2", "status0"))) 

1358 query_opts.append(query_pattern.format(q2, q1.format("status0", "status2"))) 

1359 

1360 # Test the conversion to user filters 

1361 self.assertIn(self.filter.equivalent_user_query, query_opts) 

1362 self.assertIn(self.filter._to_user_query(covers=False, matches=True), query_opts) 

1363 self.assertEqual(self.filter._to_user_query(covers=True, matches=False), "") 

1364 

1365 def test_escaping_of_single_quote(self): 

1366 self.filter.stdout_regex = r"test's log" 

1367 

1368 # Test the conversion to user filters and ensure that the single quote is escaped 

1369 expected = r"stdout ~= 'test\'s log'" 

1370 self.assertEqual(self.filter._to_user_query(covers=False, matches=True), expected) 

1371 

1372 def test_stdout_only(self): 

1373 self.filter.stdout_regex = r"result[12] stdout\d+" 

1374 for testresult in self.testresult: 

1375 stdout_line = testresult.stdout.split('\n')[1] 

1376 should_match = (stdout_line == "result1 stdout1234" or stdout_line == "result2 stdout1234") 

1377 self.assertTrue(self.filter.covers(testresult)) 

1378 self.assertTrue(self.filter.matches(testresult) == should_match, stdout_line) 

1379 

1380 # Test the conversion to user filters 

1381 expected = r"stdout ~= 'result[12] stdout\d+'" 

1382 self.assertEqual(self.filter.equivalent_user_query, expected) 

1383 self.assertEqual(self.filter._to_user_query(covers=True, matches=False), "") 

1384 self.assertEqual(self.filter._to_user_query(covers=False, matches=True), expected) 

1385 

1386 def test_stderr_only(self): 

1387 self.filter.stderr_regex = r"result[12] stderr\d+" 

1388 for testresult in self.testresult: 

1389 stderr_line = testresult.stderr.split('\n')[1] 

1390 should_match = (stderr_line == "result1 stderr1234" or stderr_line == "result2 stderr1234") 

1391 self.assertTrue(self.filter.covers(testresult)) 

1392 self.assertTrue(self.filter.matches(testresult) == should_match, stderr_line) 

1393 

1394 # Test the conversion to user filters 

1395 expected = r"stderr ~= 'result[12] stderr\d+'" 

1396 self.assertEqual(self.filter.equivalent_user_query, expected) 

1397 self.assertEqual(self.filter._to_user_query(covers=True, matches=False), "") 

1398 self.assertEqual(self.filter._to_user_query(covers=False, matches=True), expected) 

1399 

1400 def test_dmesg_only(self): 

1401 self.filter.dmesg_regex = r"result[12] dmesg\d+" 

1402 for testresult in self.testresult: 

1403 dmesg_line = testresult.dmesg.split('\n')[1] 

1404 should_match = (dmesg_line == "result1 dmesg1234" or dmesg_line == "result2 dmesg1234") 

1405 self.assertTrue(self.filter.covers(testresult)) 

1406 self.assertTrue(self.filter.matches(testresult) == should_match, dmesg_line) 

1407 

1408 # Test the conversion to user filters 

1409 expected = r"dmesg ~= 'result[12] dmesg\d+'" 

1410 self.assertEqual(self.filter.equivalent_user_query, expected) 

1411 self.assertEqual(self.filter._to_user_query(covers=True, matches=False), "") 

1412 self.assertEqual(self.filter._to_user_query(covers=False, matches=True), expected) 

1413 

1414 def test_user_query_filter(self): 

1415 self.filter.user_query = f'machine_name IS IN ["{self.machine[0]}", "{self.machine[2]}"]' 

1416 

1417 for testresult in self.testresult: 

1418 machine = testresult.ts_run.machine 

1419 should_match = (machine == self.machine[0] or machine == self.machine[2]) 

1420 self.assertTrue(self.filter.covers(testresult) == should_match) 

1421 self.assertTrue(self.filter.matches(testresult) == should_match) 

1422 

1423 def test_replace(self): 

1424 user = get_user_model().objects.create(username='blabla') 

1425 

1426 issues = [] 

1427 for i in range(3): 

1428 issue = Issue.objects.create(description="") 

1429 issue.set_filters([self.filter], user) 

1430 issues.append(issue) 

1431 if i == 1: 

1432 issue.archive(user) 

1433 

1434 new_filter = IssueFilter.objects.create(description="new filter") 

1435 

1436 self.assertEqual(IssueFilterAssociated.objects.filter(filter=self.filter).count(), 3) 

1437 self.assertEqual(IssueFilterAssociated.objects.filter(filter=self.filter, deleted_by=user).count(), 1) 

1438 self.assertEqual(IssueFilterAssociated.objects.filter(deleted_on=None, filter=self.filter).count(), 2) 

1439 self.assertEqual(IssueFilterAssociated.objects.filter(filter=new_filter).count(), 0) 

1440 self.assertFalse(self.filter.hidden) 

1441 

1442 self.filter.replace(new_filter, user) 

1443 

1444 self.assertEqual(IssueFilterAssociated.objects.filter(filter=self.filter).count(), 3) 

1445 self.assertEqual(IssueFilterAssociated.objects.filter(deleted_on=None, filter=self.filter).count(), 0) 

1446 self.assertEqual(IssueFilterAssociated.objects.filter(filter=new_filter).count(), 2) 

1447 self.assertEqual(IssueFilterAssociated.objects.filter(filter=self.filter, deleted_by=user).count(), 3) 

1448 self.assertTrue(self.filter.hidden) 

1449 

1450 # Verify that the all the machines specified by tags are listed when calling test_machines_cached 

1451 def test_machines_cached(self): 

1452 # Create a machine tag and tag machine1 with it 

1453 tag1 = MachineTag.objects.create(name="TAG1", public=True) 

1454 self.machine[1].tags.add(tag1) 

1455 self.machine[3].tags.add(tag1) 

1456 

1457 # Now add the tag1 to the list of machines tags, and machine 0 as a machine 

1458 self.filter.machine_tags.add(tag1) 

1459 self.filter.machines.add(self.machine[0]) 

1460 

1461 # Now check that the filter lists the machines 1 and 2 when calling test_machines_cached 

1462 self.assertEqual(self.filter.machines_cached, set([self.machine[0], self.machine[1], self.machine[3]])) 

1463 

1464 

1465class RateTests(TestCase): 

1466 def test_rate(self): 

1467 self.assertAlmostEqual(Rate('', 1, 2).rate, 0.5) 

1468 self.assertEqual(Rate('', 0, 0).rate, 0) 

1469 

1470 def test_str(self): 

1471 self.assertEqual(str(Rate('tests', 1, 2)), '1 / 2 tests (50.0%)') 

1472 

1473 

1474class KnownFailureTests(TestCase): 

1475 def test_covered_runconfigs_since(self): 

1476 # Create a list of runconfigs that will be used by the "runconfig_covered" 

1477 runconfigs = list() 

1478 for i in range(6): 

1479 runconfigs.append(RunConfig.objects.create(name="Runconfig{}".format(i), temporary=True)) 

1480 

1481 # Create a failure linked to an Issue with all the runconfigs as covered, and the ifa's 

1482 # covered runconfigs are all but the last one. Associate the runconfig index 1 as the runconfig 

1483 # where the failure happened 

1484 failure = KnownFailure() 

1485 failure.result = TestResult(ts_run=TestsuiteRun(runconfig=runconfigs[1])) 

1486 failure.matched_ifa = IssueFilterAssociated(issue=Issue()) 

1487 failure.matched_ifa.runconfigs_covered = runconfigs[:-1] 

1488 failure.matched_ifa.issue.runconfigs_covered = runconfigs 

1489 

1490 # Check that when the runconfig is not found, we return None 

1491 self.assertEqual(KnownFailure._runconfig_index(runconfigs, RunConfig()), None) 

1492 

1493 # Check that the number of runconfigs since the failure happened is 

1494 self.assertEqual(failure.covered_runconfigs_since_for_issue, 4) # len(issue.runconfig_covered) - 2 

1495 self.assertEqual(failure.covered_runconfigs_since_for_filter, 3) # len(ifa.runconfig_covered) - 2 

1496 

1497 

1498class UnknownFailureTests(TestCase): 

1499 @patch('CIResults.models.UnknownFailure.matched_archived_ifas') 

1500 def test_matched_archived_ifas_cached(self, matched_archived_ifas_mocked): 

1501 self.assertEqual(UnknownFailure().matched_archived_ifas_cached, matched_archived_ifas_mocked.all.return_value) 

1502 

1503 def test_matched_issues(self): 

1504 failure = UnknownFailure() 

1505 failure.matched_archived_ifas_cached = [MagicMock(spec=IssueFilterAssociated), 

1506 MagicMock(spec=IssueFilterAssociated)] 

1507 self.assertEqual(failure.matched_issues, 

1508 set([e.issue for e in failure.matched_archived_ifas_cached])) 

1509 

1510 @patch('CIResults.models.UnknownFailure.result') 

1511 def test_str(self, results_mocked): 

1512 failure = UnknownFailure() 

1513 self.assertEqual(str(failure), str(failure.result)) 

1514 

1515 

1516class RunFilterStatisticTests(TestCase): 

1517 def test_str(self): 

1518 f = IssueFilter(description="My filter") 

1519 runconfig = RunConfig(name='RunConfig') 

1520 

1521 self.assertEqual(str(RunFilterStatistic(runconfig=runconfig, filter=f, covered_count=0, matched_count=0)), 

1522 'My filter on RunConfig: match rate 0/0 (0.00%)') 

1523 self.assertEqual(str(RunFilterStatistic(runconfig=runconfig, filter=f, covered_count=10, matched_count=5)), 

1524 'My filter on RunConfig: match rate 5/10 (50.00%)')