Coverage for CIResults/serializers.py: 90%

353 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-23 13:11 +0000

1import datetime 

2from django.db import transaction 

3from collections import namedtuple, OrderedDict 

4from rest_framework import serializers 

5 

6from .run_import import JsonResult, RunConfigResults, TestsuiteResults, TestsuiteTestResult 

7from .models import Bug, Component, Build, Test, Machine, RunConfigTag, RunConfig, ReplicationScript 

8from .models import TestSuite, TextStatus, IssueFilter, MachineTag, BugTrackerAccount 

9from .models import Issue, BugTracker 

10 

11from shortener.models import Shortener 

12 

13 

14class UpdateMixin(serializers.ModelSerializer): 

15 def get_extra_kwargs(self): 

16 kwargs = super().get_extra_kwargs() 

17 no_update_fields = getattr(self.Meta, "no_update_fields", None) 

18 

19 if self.instance and no_update_fields: 

20 for field in no_update_fields: 

21 kwargs.setdefault(field, {}) 

22 kwargs[field]["read_only"] = True 

23 

24 return kwargs 

25 

26 

27class RunConfigTagSerializer(serializers.ModelSerializer): 

28 class Meta: 

29 model = RunConfigTag 

30 fields = ('id', 'description', 'url', 'public', '__str__') 

31 read_only_fields = ('public', ) 

32 

33 

34class TestSuiteSerializer(serializers.ModelSerializer): 

35 class Meta: 

36 model = TestSuite 

37 fields = ('id', '__str__') 

38 

39 

40class TestSerializer(serializers.ModelSerializer): 

41 testsuite = TestSuiteSerializer() 

42 

43 class Meta: 

44 model = Test 

45 fields = ('id', 'name', 'testsuite', 'public', 'vetted_on', 'first_runconfig', '__str__') 

46 read_only_fields = ('public', ) 

47 

48 

49class MachineTagSerializer(serializers.ModelSerializer): 

50 class Meta: 

51 model = MachineTag 

52 fields = ('id', 'name', 'public') 

53 read_only_fields = ('added_on', ) 

54 

55 

56class MachineSerializer(serializers.ModelSerializer): 

57 class Meta: 

58 model = Machine 

59 fields = ('id', 'public', 'vetted_on', '__str__') 

60 read_only_fields = ('public', ) 

61 

62 

63class RestViewMachineSerializer(serializers.ModelSerializer): 

64 tags = serializers.SlugRelatedField(many=True, slug_field='name', queryset=MachineTag.objects.all()) 

65 

66 class Meta: 

67 model = Machine 

68 fields = ('id', 'name', 'description', 'public', 'vetted_on', 'aliases', 'tags') 

69 

70 

71class ImportMachineError(Exception): 

72 pass 

73 

74 

75class ImportMachineSerializer(serializers.Serializer): 

76 name = serializers.CharField(required=True) 

77 description = serializers.CharField(required=False, allow_null=True) 

78 public = serializers.BooleanField(required=False, default=False) 

79 vetted = serializers.BooleanField(required=False, default=False) 

80 alias = serializers.CharField(required=False, allow_null=True) 

81 tags = serializers.ListSerializer(child=serializers.CharField(), required=False, default=[]) 

82 

83 @transaction.atomic 

84 def create(self, validated_data): 

85 name = validated_data["name"] 

86 description = validated_data.get("description") 

87 alias = validated_data.get("alias") 

88 if alias: 

89 try: 

90 alias_machine = Machine.objects.get(name=alias) 

91 except Machine.DoesNotExist: 

92 raise ImportMachineError( 

93 "The machine this machine is supposed to alias does not exist. Create it first..." 

94 ) 

95 else: 

96 alias_machine = None 

97 

98 # List tags to show options 

99 tags = {} 

100 for tag in MachineTag.objects.all(): 

101 tags[tag.name] = tag 

102 

103 # Find the list of tags that do not exist yet, and create them 

104 for tag_name in set(validated_data["tags"]) - set(tags.keys()): 

105 tags[tag_name] = MachineTag.objects.create(name=tag_name, public=True) 

106 

107 # Now get/create the machine 

108 try: 

109 machine = Machine.objects.get(name=name) 

110 except Machine.DoesNotExist: 

111 machine = Machine.objects.create(name=name, public=validated_data["public"]) 

112 if validated_data["vetted"]: 

113 machine.vet() 

114 

115 # Description 

116 if description: 

117 machine.description = description 

118 

119 # Alias 

120 if alias_machine is not None: 

121 machine.aliases = alias_machine 

122 

123 # Machine tags 

124 tags_to_add = set(validated_data["tags"]) - set([mt.name for mt in machine.tags.all()]) 

125 for tag_name in tags_to_add: 

126 machine.tags.add(tags[tag_name]) 

127 

128 machine.save() 

129 return machine 

130 

131 

132class TextStatusSerializer(serializers.ModelSerializer): 

133 testsuite = TestSuiteSerializer() 

134 

135 class Meta: 

136 model = TextStatus 

137 fields = ('id', 'name', 'testsuite', '__str__') 

138 

139 

140class IssueFilterSerializer(serializers.ModelSerializer): 

141 tags = RunConfigTagSerializer(many=True) 

142 tests = TestSerializer(many=True) 

143 machine_tags = MachineTagSerializer(many=True) 

144 machines = MachineSerializer(many=True) 

145 statuses = TextStatusSerializer(many=True) 

146 

147 class Meta: 

148 model = IssueFilter 

149 fields = ('id', 'description', 'tags', 'machines', 'machine_tags', 'tests', 'statuses', 

150 'stdout_regex', 'stderr_regex', 'dmesg_regex', 'user_query', '__str__') 

151 read_only_fields = ('added_on', ) 

152 

153 

154class RunConfigSerializer(serializers.ModelSerializer): 

155 tags = serializers.SlugRelatedField(many=True, slug_field='name', queryset=RunConfigTag.objects.all()) 

156 builds = serializers.SlugRelatedField(many=True, slug_field='name', queryset=Build.objects.all()) 

157 

158 class Meta: 

159 model = RunConfig 

160 fields = ('id', 'name', 'tags', 'url', 'added_on', 'builds', 'environment', 'temporary', '__str__') 

161 read_only_fields = ('added_on', ) 

162 

163 def validate(self, data): 

164 components = {} 

165 for build in data["builds"]: 

166 if build.component not in components: 

167 components[build.component] = build 

168 else: 

169 raise serializers.ValidationError( 

170 f"Two builds ({components[build.component]} and {build}) cannot be from the same component" 

171 ) 

172 return data 

173 

174 

175class ImportTestResult(serializers.Serializer): 

176 status = serializers.CharField() 

177 duration = serializers.IntegerField(required=False, allow_null=True) 

178 command = serializers.CharField(required=False, default="") 

179 stdout = serializers.CharField(required=False, allow_null=True) 

180 stderr = serializers.CharField(required=False, allow_null=True) 

181 dmesg = serializers.CharField(required=False, allow_null=True) 

182 url = serializers.URLField(required=False, allow_null=True) 

183 start = serializers.DateTimeField(required=False, default=datetime.datetime.now()) 

184 

185 

186class ImportTestSuiteRunSerializer(serializers.Serializer): 

187 runconfig_name = serializers.CharField(required=True) 

188 test_results = serializers.DictField( 

189 child=serializers.DictField( 

190 child=serializers.DictField(child=ImportTestResult(), required=True), required=True), required=True 

191 ) 

192 test_suite = serializers.CharField(required=True) 

193 

194 @transaction.atomic 

195 def create(self, validated_data): 

196 run_results = [] 

197 try: 

198 test_suite = Build.objects.get(name=validated_data["test_suite"]) 

199 test_suite_results = TestsuiteResults( 

200 runconfig=None, 

201 name=test_suite.component.name, 

202 build=test_suite, 

203 format="json", 

204 version=None, 

205 result_url_pattern=validated_data.get("result_url_pattern") 

206 ) 

207 except Build.DoesNotExist: 

208 raise ValueError(f"Testsuite build {validated_data['test_suite']} does not exist") 

209 for machine_name, raw_machine in validated_data.get("test_results").items(): 

210 for run_id, raw_run in raw_machine.items(): 

211 testsuite_test_results = [] 

212 for test_name, raw_test in raw_run.items(): 

213 testsuite_test_results.append(TestsuiteTestResult( 

214 name=test_name, 

215 status=raw_test["status"], 

216 start_time=raw_test["start"], 

217 duration=datetime.timedelta(seconds=raw_test.get("duration", 0)), 

218 command=raw_test.get("command"), 

219 stdout=raw_test.get("stdout"), 

220 stderr=raw_test.get("stderr"), 

221 dmesg=raw_test.get("dmesg"), 

222 url=raw_test.get("url") 

223 )) 

224 run_results.append(JsonResult(test_suite_results, machine_name, int(run_id), testsuite_test_results)) 

225 

226 rc = RunConfigResults(name=validated_data["runconfig_name"], parsed_results=run_results) 

227 rc.commit_to_db() 

228 return rc 

229 

230 

231class ComponentSerializer(serializers.ModelSerializer): 

232 class Meta: 

233 model = Component 

234 fields = ('id', 'name', 'description', 'url', 'public', '__str__') 

235 

236 

237class BuildSerializer(UpdateMixin, serializers.ModelSerializer): 

238 component = serializers.SlugRelatedField(slug_field='name', queryset=Component.objects.all()) 

239 parents = serializers.SlugRelatedField(many=True, slug_field='name', queryset=Build.objects.all()) 

240 

241 class Meta: 

242 model = Build 

243 fields = ('id', 'name', 'component', 'version', 'added_on', 'parents', 

244 'repo_type', 'branch', 'repo', 'upstream_url', 'parameters', 

245 'build_log', '__str__') 

246 read_only_fields = ('id', 'added_on') 

247 no_update_fields = ['name', 'component'] 

248 

249 

250class BuildMinimalSerializer(serializers.ModelSerializer): 

251 class Meta: 

252 model = Build 

253 fields = ('id', 'name', 'added_on', 'parents', 'upstream_url', '__str__') 

254 read_only_fields = ('id', 'added_on') 

255 

256 

257class RunConfigResultsSerializer(serializers.Serializer): 

258 __str__ = serializers.CharField(max_length=255, read_only=True) 

259 is_failure = serializers.BooleanField(read_only=True) 

260 all_failures_covered = serializers.BooleanField(read_only=True) 

261 bugs_covering = serializers.SerializerMethodField() 

262 

263 def get_bugs_covering(self, obj): 

264 ser = serializers.ListField(child=serializers.CharField(max_length=255, read_only=True)) 

265 return ser.to_representation([b.short_name for b in obj.bugs_covering]) 

266 

267 

268class RunConfigResultsDiffSerializer(serializers.Serializer): 

269 testsuite = serializers.SerializerMethodField() 

270 test = serializers.SerializerMethodField() 

271 machine = serializers.SerializerMethodField() 

272 result_from = RunConfigResultsSerializer(read_only=True) 

273 result_to = RunConfigResultsSerializer(read_only=True) 

274 

275 def get_testsuite(self, obj): 

276 ser = serializers.CharField(max_length=255, read_only=True) 

277 return ser.to_representation(obj.testsuite.name) 

278 

279 def get_test(self, obj): 

280 ser = serializers.CharField(max_length=255, read_only=True) 

281 return ser.to_representation(obj.test.name) 

282 

283 def get_machine(self, obj): 

284 ser = serializers.CharField(max_length=255, read_only=True) 

285 return ser.to_representation(obj.machine.name) 

286 

287 

288class BugTrackerSerializer(serializers.ModelSerializer): 

289 class Meta: 

290 model = BugTracker 

291 fields = ('id', 'name', 'short_name', 'project', 'separator', 'url', 'tracker_type', 'polled', 

292 'components_followed', 'components_followed_since', 'first_response_SLA') 

293 read_only_fields = ('id', 'name', 'short_name', 'project', 'separator', 'url', 'tracker_type', 'polled', 

294 'components_followed', 'components_followed_since', 'first_response_SLA') 

295 

296 

297def serialize_bug(bug, new_comments=None): 

298 def _date_formatter(date_field): 

299 return str(date_field) if date_field is not None else None 

300 

301 resp = { 

302 'url': bug.url, 

303 'bug_id': bug.bug_id, 

304 'title': bug.title, 

305 'description': bug.description, 

306 'tracker': str(bug.tracker), 

307 'created': _date_formatter(bug.created), 

308 'updated': _date_formatter(bug.updated), 

309 'polled': _date_formatter(bug.polled), 

310 'closed': _date_formatter(bug.closed), 

311 'creator': str(bug.creator), 

312 'assignee': str(bug.assignee), 

313 'product': bug.product, 

314 'component': bug.component, 

315 'priority': bug.priority, 

316 'severity': bug.severity, 

317 'features': bug.features_list, 

318 'platforms': bug.platforms_list, 

319 'status': bug.status, 

320 'tags': bug.tags_list, 

321 'custom_fields': bug.custom_fields, 

322 'new_comments': [] 

323 } 

324 if new_comments: 

325 for comm in new_comments: 

326 person = comm.db_object.account.person 

327 author = person.full_name if person.full_name else person.email 

328 resp['new_comments'].append({'author': author, 

329 'created': str(comm.db_object.created_on), 

330 'body': comm.body}) 

331 return resp 

332 

333 

334class BugCompleteSerializer(serializers.ModelSerializer): 

335 tracker = BugTrackerSerializer() 

336 

337 class Meta: 

338 model = Bug 

339 fields = ('id', 'tracker', 'bug_id', 'parent', 'children', 'title', 'description', 'created', 

340 'updated', 'polled', 'closed', 'creator', 'assignee', 'product', 'component', 

341 'priority', 'severity', 'features', 'platforms', 'status', 'tags', 'comments_polled', 

342 'flagged_as_update_pending_on', 'custom_fields') 

343 read_only_fields = ('id', 'tracker', 'bug_id', 'parent', 'children', 'title', 'description', 'created', 

344 'updated', 'polled', 'closed', 'creator', 'assignee', 'product', 'component', 

345 'priority', 'severity', 'features', 'platforms', 'status', 'tags', 'comments_polled', 

346 'flagged_as_update_pending_on', 'custom_fields') 

347 

348 

349class BugMinimalSerializer(serializers.ModelSerializer): 

350 class Meta: 

351 model = Bug 

352 fields = ('id', 'short_name', 'url') 

353 read_only_fields = ('id', 'short_name', 'url') 

354 

355 

356class ExecutionTimeSerializer(serializers.Serializer): 

357 minimum = serializers.DurationField(read_only=True) 

358 maximum = serializers.DurationField(read_only=True) 

359 count = serializers.IntegerField(read_only=True) 

360 

361 

362class RunConfigDiffSerializer(serializers.Serializer): 

363 runcfg_from = RunConfigSerializer(read_only=True) 

364 runcfg_to = RunConfigSerializer(read_only=True) 

365 results = RunConfigResultsDiffSerializer(read_only=True, many=True) 

366 new_tests = serializers.SerializerMethodField() 

367 builds = serializers.SerializerMethodField() 

368 bugs = BugMinimalSerializer(many=True) 

369 status = serializers.CharField(max_length=10, read_only=True) 

370 text = serializers.CharField(max_length=16000, read_only=True) 

371 

372 class BuildDiff2Serializer(serializers.Serializer): 

373 component = ComponentSerializer(read_only=True) 

374 from_build = BuildMinimalSerializer(read_only=True) 

375 to_build = BuildMinimalSerializer(read_only=True) 

376 

377 class RunConfigDiffNewTestsSerializer(serializers.Serializer): 

378 test = TestSerializer() 

379 statuses = serializers.DictField(child=serializers.IntegerField(read_only=True)) 

380 exec_time = ExecutionTimeSerializer(read_only=True) 

381 

382 def get_builds(self, obj): 

383 bd2 = namedtuple('BuildDiff2', ['component', 'from_build', 'to_build']) 

384 build_diffs = {k: bd2(k, v.from_build, v.to_build) for k, v in obj.builds.items()} 

385 dict_ser = serializers.DictField(child=self.BuildDiff2Serializer()) 

386 return dict_ser.to_representation(build_diffs) 

387 

388 def __statuses(self, statuses): 

389 return {k.name: v for k, v in statuses.items()} 

390 

391 def get_new_tests(self, obj): 

392 NT = namedtuple('NewTest', ['test', 'statuses', 'exec_time']) 

393 new_tests = {k: NT(k, {k.name: v for k, v in v.to_statuses.items()}, v.to_exec_times) 

394 for k, v in obj.new_tests.tests.items()} 

395 dict_ser = serializers.DictField(child=self.RunConfigDiffNewTestsSerializer()) 

396 return dict_ser.to_representation(new_tests) 

397 

398 

399class ReplicationScriptSerializer(serializers.ModelSerializer): 

400 created_by = serializers.StringRelatedField() 

401 source_tracker = serializers.StringRelatedField() 

402 destination_tracker = serializers.StringRelatedField() 

403 

404 class Meta: 

405 model = ReplicationScript 

406 fields = ('name', 'created_by', 'created_on', 'source_tracker', 'destination_tracker', 'script') 

407 

408 

409class KnownIssuesSerializer(serializers.Serializer): 

410 id = serializers.IntegerField(read_only=True) 

411 testsuite = serializers.SerializerMethodField() 

412 machine = serializers.SerializerMethodField() 

413 run_id = serializers.SerializerMethodField() 

414 test = serializers.SerializerMethodField() 

415 status = serializers.SerializerMethodField() 

416 url = serializers.SerializerMethodField() 

417 

418 bugs = serializers.SerializerMethodField() 

419 

420 def __init__(self, *args, **kwargs): 

421 super().__init__(*args, **kwargs) 

422 

423 # Cache the serializers for performance reasons 

424 self._char_ser = serializers.CharField(max_length=255, read_only=True) 

425 self._int_ser = serializers.IntegerField(read_only=True) 

426 self._bug_min_ser = BugMinimalSerializer(many=True) 

427 

428 def get_run_id(self, obj): 

429 return self._int_ser.to_representation(obj.result.ts_run.run_id) 

430 

431 def get_testsuite(self, obj): 

432 return self._char_ser.to_representation(obj.result.test.testsuite.name) 

433 

434 def get_test(self, obj): 

435 return self._char_ser.to_representation(obj.result.test.name) 

436 

437 def get_machine(self, obj): 

438 return self._char_ser.to_representation(obj.result.ts_run.machine.name) 

439 

440 def get_status(self, obj): 

441 return self._char_ser.to_representation(obj.result.status.name) 

442 

443 def get_url(self, obj): 

444 return self._char_ser.to_representation(obj.result.url) 

445 

446 def get_bugs(self, obj): 

447 return self._bug_min_ser.to_representation(obj.matched_ifa.issue.bugs.all()) 

448 

449 

450class BugTrackerAccountSerializer(serializers.ModelSerializer): 

451 class Meta: 

452 model = BugTrackerAccount 

453 fields = ('id', 'is_developer') 

454 read_only_fields = ('id', ) 

455 

456 

457class ShortenerSerializer(serializers.ModelSerializer): 

458 class Meta: 

459 model = Shortener 

460 fields = ('id', 'shorthand', 'full', 'added_on', 'last_accessed') 

461 read_only_fields = ('id', 'shorthand', 'full', 'added_on', 'last_accessed') 

462 

463 

464class RateSerializer(serializers.Serializer): 

465 count = serializers.IntegerField() 

466 total = serializers.IntegerField() 

467 percent = serializers.FloatField() 

468 

469 

470class BugSerializer(serializers.ModelSerializer): 

471 class Meta: 

472 model = Bug 

473 fields = ('short_name', 'title', 'url') 

474 read_only_fields = ('short_name', 'title', 'url') 

475 

476 

477class RestIssueSerializer(serializers.ModelSerializer): 

478 class Meta: 

479 model = Issue 

480 fields = ('__all__') 

481 read_only_fields = ('id', 'added_on', 'archived_on', 'runconfigs_covered_count', 'runconfigs_affected_count', 

482 'last_seen', 'added_by', 'archived_by', 'last_seen_runconfig') 

483 

484 bugs = BugSerializer(many=True) 

485 

486 

487class IssueSerializer(serializers.ModelSerializer): 

488 class Meta: 

489 model = Issue 

490 fields = ('id', 'bugs', 'expected') 

491 read_only_fields = ('id', 'bugs', 'expected') 

492 

493 bugs = BugSerializer(many=True) 

494 

495 

496class IssueSerializerMinimal(serializers.ModelSerializer): 

497 class Meta: 

498 model = Issue 

499 fields = ('id', ) 

500 read_only_fields = ('id', ) 

501 

502 

503def serialize_issue_hitrate(issues, minimal=False): 

504 Serializer = IssueSerializerMinimal if minimal else IssueSerializer 

505 

506 ret = [] 

507 for issue, rate in issues.items(): 

508 val = Serializer(issue).data 

509 val['hit_rate'] = RateSerializer(rate).data 

510 ret.append(val) 

511 return ret 

512 

513 

514def serialize_MetricPassRatePerRunconfig(history): 

515 runconfigs = OrderedDict() 

516 for runconfig, _statuses in history.runconfigs.items(): 

517 runconfigs[str(runconfig)] = OrderedDict() 

518 for status, rate in _statuses.items(): 

519 runconfigs[str(runconfig)][str(status)] = RateSerializer(rate).data 

520 

521 statuses = OrderedDict() 

522 for status, _runconfigs in history.statuses.items(): 

523 statuses[str(status)] = OrderedDict() 

524 for runconfig, rate in _runconfigs.items(): 

525 statuses[str(status)][str(runconfig)] = RateSerializer(rate).data 

526 

527 return { 

528 "runconfigs": runconfigs, 

529 "statuses": statuses, 

530 "most_hit_issues": serialize_issue_hitrate(history.most_hit_issues), 

531 "query_key": history.query.query_key, 

532 } 

533 

534 

535class PassRateStatisticsSerializer(serializers.Serializer): 

536 passrate = RateSerializer() 

537 runrate = RateSerializer() 

538 discarded_rate = RateSerializer() 

539 notrun_rate = RateSerializer() 

540 

541 

542def serialize_MetricPassRatePerTest(metric_passrate): 

543 discarded_status = "discarded (expected)" 

544 

545 tests = OrderedDict() 

546 for test, results in metric_passrate.tests.items(): 

547 if results.is_fully_discarded: 

548 tests[str(test)] = { 

549 "status": discarded_status, 

550 "is_pass": False, 

551 "is_run": False, 

552 "duration": str(results.duration), 

553 "issues_hit": serialize_issue_hitrate(results.issue_occurence_rates, minimal=True), 

554 } 

555 else: 

556 tests[str(test)] = { 

557 "status": str(results.overall_result), 

558 "is_pass": results.is_pass, 

559 "is_run": not results.overall_result.is_notrun, 

560 "duration": str(results.duration), 

561 "issues_hit": serialize_issue_hitrate(results.issue_occurence_rates, minimal=True), 

562 } 

563 

564 statuses = OrderedDict() 

565 for status, rate in metric_passrate.statuses.items(): 

566 statuses[str(status)] = RateSerializer(rate).data 

567 

568 return { 

569 "tests": tests, 

570 "statuses": statuses, 

571 "discarded_status": discarded_status, 

572 "machines": [str(m) for m in metric_passrate.machines], 

573 "runconfigs": RunConfigSerializer(metric_passrate.runconfigs, many=True).data, 

574 "raw_statistics": PassRateStatisticsSerializer(metric_passrate.raw_statistics).data, 

575 "statistics": PassRateStatisticsSerializer(metric_passrate.statistics).data, 

576 "most_hit_issues": serialize_issue_hitrate(metric_passrate.most_hit_issues), 

577 "uncovered_failure_rate": RateSerializer(metric_passrate.uncovered_failure_rate).data, 

578 "notrun_rate": RateSerializer(metric_passrate.notrun_rate).data, 

579 "most_interrupting_issues": serialize_issue_hitrate(metric_passrate.most_interrupting_issues), 

580 "unknown_failure_interruption_rate": RateSerializer(metric_passrate.unknown_failure_interruption_rate).data, 

581 "unexplained_interruption_rate": RateSerializer(metric_passrate.unexplained_interruption_rate).data, 

582 "query_key": metric_passrate.query.query_key, 

583 }