Coverage for CIResults/serializers.py: 91%
389 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-19 09:20 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-19 09:20 +0000
1import datetime
2from django.db import transaction
3from collections import namedtuple, OrderedDict
4from rest_framework import serializers
6from .run_import import JsonResult, RunConfigResults, TestsuiteResults, TestsuiteTestResult
7from .models import Bug, Component, Build, Test, Machine, RunConfigTag, RunConfig, ReplicationScript, UnknownFailure
8from .models import TestSuite, TextStatus, IssueFilter, MachineTag, BugTrackerAccount
9from .models import Issue, BugTracker
11from shortener.models import Shortener
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)
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
24 return kwargs
27class DynamicFieldsModelSerializer(serializers.ModelSerializer):
28 """
29 A ModelSerializer that takes an additional `extra_fields` argument that
30 controls which fields should be displayed. Subclass should define a
31 default_fields list.
32 """
33 default_fields = []
35 def __init__(self, *args, **kwargs):
36 extra_fields_to_serialize = kwargs.pop('extra_fields', None)
37 super().__init__(*args, **kwargs)
39 if extra_fields_to_serialize is not None:
40 # Drop extra fields that are not specified in the `extra_fields`
41 # argument.
42 default = set(self.default_fields)
43 existing = set(self.fields)
44 for field_name in existing - default - set(extra_fields_to_serialize):
45 self.fields.pop(field_name)
47 @classmethod
48 def extra_fields(cls) -> list[str]:
49 return list(set(cls.Meta.fields) - set(cls.default_fields))
52class RunConfigTagSerializer(serializers.ModelSerializer):
53 class Meta:
54 model = RunConfigTag
55 fields = ('id', 'description', 'url', 'public', '__str__')
56 read_only_fields = ('public', )
59class TestSuiteSerializer(serializers.ModelSerializer):
60 class Meta:
61 model = TestSuite
62 fields = ('id', '__str__')
65class TestSerializer(serializers.ModelSerializer):
66 testsuite = TestSuiteSerializer()
68 class Meta:
69 model = Test
70 fields = ('id', 'name', 'testsuite', 'public', 'vetted_on', 'first_runconfig', '__str__')
71 read_only_fields = ('public', )
74class MachineTagSerializer(serializers.ModelSerializer):
75 class Meta:
76 model = MachineTag
77 fields = ('id', 'name', 'public')
78 read_only_fields = ('added_on', )
81class MachineSerializer(serializers.ModelSerializer):
82 class Meta:
83 model = Machine
84 fields = ('id', 'public', 'vetted_on', '__str__')
85 read_only_fields = ('public', )
88class RestViewMachineSerializer(serializers.ModelSerializer):
89 tags = serializers.SlugRelatedField(many=True, slug_field='name', queryset=MachineTag.objects.all())
91 class Meta:
92 model = Machine
93 fields = ('id', 'name', 'description', 'public', 'vetted_on', 'aliases', 'tags')
96class ImportMachineError(Exception):
97 pass
100class ImportMachineSerializer(serializers.Serializer):
101 name = serializers.CharField(required=True)
102 description = serializers.CharField(required=False, allow_null=True)
103 public = serializers.BooleanField(required=False, default=False)
104 vetted = serializers.BooleanField(required=False, default=False)
105 alias = serializers.CharField(required=False, allow_null=True)
106 tags = serializers.ListSerializer(child=serializers.CharField(), required=False, default=[])
108 @transaction.atomic
109 def create(self, validated_data):
110 name = validated_data["name"]
111 description = validated_data.get("description")
112 alias = validated_data.get("alias")
113 if alias:
114 try:
115 alias_machine = Machine.objects.get(name=alias)
116 except Machine.DoesNotExist:
117 raise ImportMachineError(
118 "The machine this machine is supposed to alias does not exist. Create it first..."
119 )
120 else:
121 alias_machine = None
123 # List tags to show options
124 tags = {}
125 for tag in MachineTag.objects.all():
126 tags[tag.name] = tag
128 # Find the list of tags that do not exist yet, and create them
129 for tag_name in set(validated_data["tags"]) - set(tags.keys()):
130 tags[tag_name] = MachineTag.objects.create(name=tag_name, public=True)
132 # Now get/create the machine
133 try:
134 machine = Machine.objects.get(name=name)
135 except Machine.DoesNotExist:
136 machine = Machine.objects.create(name=name, public=validated_data["public"])
137 if validated_data["vetted"]:
138 machine.vet()
140 # Description
141 if description:
142 machine.description = description
144 # Alias
145 if alias_machine is not None:
146 machine.aliases = alias_machine
148 # Machine tags
149 tags_to_add = set(validated_data["tags"]) - set([mt.name for mt in machine.tags.all()])
150 for tag_name in tags_to_add:
151 machine.tags.add(tags[tag_name])
153 machine.save()
154 return machine
157class TextStatusSerializer(serializers.ModelSerializer):
158 testsuite = TestSuiteSerializer()
160 class Meta:
161 model = TextStatus
162 fields = ('id', 'name', 'testsuite', '__str__')
165class IssueFilterSerializer(serializers.ModelSerializer):
166 tags = RunConfigTagSerializer(many=True)
167 tests = TestSerializer(many=True)
168 machine_tags = MachineTagSerializer(many=True)
169 machines = MachineSerializer(many=True)
170 statuses = TextStatusSerializer(many=True)
172 class Meta:
173 model = IssueFilter
174 fields = ('id', 'description', 'tags', 'machines', 'machine_tags', 'tests', 'statuses',
175 'stdout_regex', 'stderr_regex', 'dmesg_regex', 'user_query', '__str__')
176 read_only_fields = ('added_on', )
179class RunConfigSerializer(serializers.ModelSerializer):
180 tags = serializers.SlugRelatedField(many=True, slug_field='name', queryset=RunConfigTag.objects.all())
181 builds = serializers.SlugRelatedField(many=True, slug_field='name', queryset=Build.objects.all())
183 class Meta:
184 model = RunConfig
185 fields = ('id', 'name', 'tags', 'url', 'added_on', 'builds', 'environment', 'temporary', '__str__')
186 read_only_fields = ('added_on', )
188 def validate(self, data):
189 components = {}
190 for build in data["builds"]:
191 if build.component not in components:
192 components[build.component] = build
193 else:
194 raise serializers.ValidationError(
195 f"Two builds ({components[build.component]} and {build}) cannot be from the same component"
196 )
197 return data
200class ImportTestResult(serializers.Serializer):
201 status = serializers.CharField()
202 duration = serializers.IntegerField(required=False, allow_null=True)
203 command = serializers.CharField(required=False, default="")
204 stdout = serializers.CharField(required=False, allow_null=True)
205 stderr = serializers.CharField(required=False, allow_null=True)
206 dmesg = serializers.CharField(required=False, allow_null=True)
207 url = serializers.URLField(required=False, allow_null=True)
208 start = serializers.DateTimeField(required=False, default=datetime.datetime.now())
211class ImportTestSuiteRunSerializer(serializers.Serializer):
212 runconfig_name = serializers.CharField(required=True)
213 test_results = serializers.DictField(
214 child=serializers.DictField(
215 child=serializers.DictField(child=ImportTestResult(), required=True), required=True), required=True
216 )
217 test_suite = serializers.CharField(required=True)
219 @transaction.atomic
220 def create(self, validated_data):
221 run_results = []
222 try:
223 test_suite = Build.objects.get(name=validated_data["test_suite"])
224 test_suite_results = TestsuiteResults(
225 runconfig=None,
226 name=test_suite.component.name,
227 build=test_suite,
228 format="json",
229 version=None,
230 result_url_pattern=validated_data.get("result_url_pattern")
231 )
232 except Build.DoesNotExist:
233 raise ValueError(f"Testsuite build {validated_data['test_suite']} does not exist")
234 for machine_name, raw_machine in validated_data.get("test_results").items():
235 for run_id, raw_run in raw_machine.items():
236 testsuite_test_results = []
237 for test_name, raw_test in raw_run.items():
238 testsuite_test_results.append(TestsuiteTestResult(
239 name=test_name,
240 status=raw_test["status"],
241 start_time=raw_test["start"],
242 duration=datetime.timedelta(seconds=raw_test.get("duration", 0)),
243 command=raw_test.get("command"),
244 stdout=raw_test.get("stdout"),
245 stderr=raw_test.get("stderr"),
246 dmesg=raw_test.get("dmesg"),
247 url=raw_test.get("url")
248 ))
249 run_results.append(JsonResult(test_suite_results, machine_name, int(run_id), testsuite_test_results))
251 rc = RunConfigResults(name=validated_data["runconfig_name"], parsed_results=run_results)
252 rc.commit_to_db()
253 return rc
256class MinimalMachineSerializer(serializers.ModelSerializer):
257 tags = serializers.StringRelatedField(many=True)
259 class Meta:
260 model = Machine
261 fields = ['name', 'tags']
264class MinimalRunConfigSerializer(serializers.ModelSerializer):
265 tags = serializers.StringRelatedField(many=True)
267 class Meta:
268 model = RunConfig
269 fields = ['name', 'tags']
272class UnknownFailureSerializer(DynamicFieldsModelSerializer):
273 test = serializers.StringRelatedField(source="result.test.name")
274 status = serializers.StringRelatedField(source="result.status.name")
275 dmesg = serializers.StringRelatedField(source="result.dmesg")
276 stdout = serializers.StringRelatedField(source="result.stdout")
277 stderr = serializers.StringRelatedField(source="result.stderr")
278 runconfig = MinimalRunConfigSerializer(source="result.ts_run.runconfig")
279 machine = MinimalMachineSerializer(source="result.ts_run.machine")
280 testsuite = serializers.StringRelatedField(source="result.ts_run.testsuite.name")
281 default_fields = ["id", "test", "status", "runconfig", "machine", "testsuite"]
283 class Meta:
284 model = UnknownFailure
285 fields = ["id", "test", "status", "dmesg", "stdout", "stderr", "runconfig", "machine", "testsuite"]
288class ComponentSerializer(serializers.ModelSerializer):
289 class Meta:
290 model = Component
291 fields = ('id', 'name', 'description', 'url', 'public', '__str__')
294class BuildSerializer(UpdateMixin, serializers.ModelSerializer):
295 component = serializers.SlugRelatedField(slug_field='name', queryset=Component.objects.all())
296 parents = serializers.SlugRelatedField(many=True, slug_field='name', queryset=Build.objects.all())
298 class Meta:
299 model = Build
300 fields = ('id', 'name', 'component', 'version', 'added_on', 'parents',
301 'repo_type', 'branch', 'repo', 'upstream_url', 'parameters',
302 'build_log', '__str__')
303 read_only_fields = ('id', 'added_on')
304 no_update_fields = ['name', 'component']
307class BuildMinimalSerializer(serializers.ModelSerializer):
308 class Meta:
309 model = Build
310 fields = ('id', 'name', 'added_on', 'parents', 'upstream_url', '__str__')
311 read_only_fields = ('id', 'added_on')
314class RunConfigResultsSerializer(serializers.Serializer):
315 __str__ = serializers.CharField(max_length=255, read_only=True)
316 is_failure = serializers.BooleanField(read_only=True)
317 all_failures_covered = serializers.BooleanField(read_only=True)
318 bugs_covering = serializers.SerializerMethodField()
320 def get_bugs_covering(self, obj):
321 ser = serializers.ListField(child=serializers.CharField(max_length=255, read_only=True))
322 return ser.to_representation([b.short_name for b in obj.bugs_covering])
325class RunConfigResultsDiffSerializer(serializers.Serializer):
326 testsuite = serializers.SerializerMethodField()
327 test = serializers.SerializerMethodField()
328 machine = serializers.SerializerMethodField()
329 result_from = RunConfigResultsSerializer(read_only=True)
330 result_to = RunConfigResultsSerializer(read_only=True)
332 def get_testsuite(self, obj):
333 ser = serializers.CharField(max_length=255, read_only=True)
334 return ser.to_representation(obj.testsuite.name)
336 def get_test(self, obj):
337 ser = serializers.CharField(max_length=255, read_only=True)
338 return ser.to_representation(obj.test.name)
340 def get_machine(self, obj):
341 ser = serializers.CharField(max_length=255, read_only=True)
342 return ser.to_representation(obj.machine.name)
345class BugTrackerSerializer(serializers.ModelSerializer):
346 class Meta:
347 model = BugTracker
348 fields = ('id', 'name', 'short_name', 'project', 'separator', 'url', 'tracker_type', 'polled',
349 'components_followed', 'components_followed_since', 'first_response_SLA')
350 read_only_fields = ('id', 'name', 'short_name', 'project', 'separator', 'url', 'tracker_type', 'polled',
351 'components_followed', 'components_followed_since', 'first_response_SLA')
354def serialize_bug(bug, new_comments=None):
355 def _date_formatter(date_field):
356 return str(date_field) if date_field is not None else None
358 resp = {
359 'url': bug.url,
360 'bug_id': bug.bug_id,
361 'title': bug.title,
362 'description': bug.description,
363 'tracker': str(bug.tracker),
364 'created': _date_formatter(bug.created),
365 'updated': _date_formatter(bug.updated),
366 'polled': _date_formatter(bug.polled),
367 'closed': _date_formatter(bug.closed),
368 'creator': str(bug.creator),
369 'assignee': str(bug.assignee),
370 'product': bug.product,
371 'component': bug.component,
372 'priority': bug.priority,
373 'severity': bug.severity,
374 'features': bug.features_list,
375 'platforms': bug.platforms_list,
376 'status': bug.status,
377 'tags': bug.tags_list,
378 'custom_fields': bug.custom_fields,
379 'new_comments': []
380 }
381 if new_comments:
382 for comm in new_comments:
383 person = comm.db_object.account.person
384 author = person.full_name if person.full_name else person.email
385 resp['new_comments'].append({'author': author,
386 'created': str(comm.db_object.created_on),
387 'body': comm.body})
388 return resp
391class BugCompleteSerializer(serializers.ModelSerializer):
392 tracker = BugTrackerSerializer()
394 class Meta:
395 model = Bug
396 fields = ('id', 'tracker', 'bug_id', 'parent', 'children', 'title', 'description', 'created',
397 'updated', 'polled', 'closed', 'creator', 'assignee', 'product', 'component',
398 'priority', 'severity', 'features', 'platforms', 'status', 'tags', 'comments_polled',
399 'flagged_as_update_pending_on', 'custom_fields')
400 read_only_fields = ('id', 'tracker', 'bug_id', 'parent', 'children', 'title', 'description', 'created',
401 'updated', 'polled', 'closed', 'creator', 'assignee', 'product', 'component',
402 'priority', 'severity', 'features', 'platforms', 'status', 'tags', 'comments_polled',
403 'flagged_as_update_pending_on', 'custom_fields')
406class BugMinimalSerializer(serializers.ModelSerializer):
407 class Meta:
408 model = Bug
409 fields = ('id', 'short_name', 'url')
410 read_only_fields = ('id', 'short_name', 'url')
413class ExecutionTimeSerializer(serializers.Serializer):
414 minimum = serializers.DurationField(read_only=True)
415 maximum = serializers.DurationField(read_only=True)
416 count = serializers.IntegerField(read_only=True)
419class RunConfigDiffSerializer(serializers.Serializer):
420 runcfg_from = RunConfigSerializer(read_only=True)
421 runcfg_to = RunConfigSerializer(read_only=True)
422 results = RunConfigResultsDiffSerializer(read_only=True, many=True)
423 new_tests = serializers.SerializerMethodField()
424 builds = serializers.SerializerMethodField()
425 bugs = BugMinimalSerializer(many=True)
426 status = serializers.CharField(max_length=10, read_only=True)
427 text = serializers.CharField(max_length=16000, read_only=True)
429 class BuildDiff2Serializer(serializers.Serializer):
430 component = ComponentSerializer(read_only=True)
431 from_build = BuildMinimalSerializer(read_only=True)
432 to_build = BuildMinimalSerializer(read_only=True)
434 class RunConfigDiffNewTestsSerializer(serializers.Serializer):
435 test = TestSerializer()
436 statuses = serializers.DictField(child=serializers.IntegerField(read_only=True))
437 exec_time = ExecutionTimeSerializer(read_only=True)
439 def get_builds(self, obj):
440 bd2 = namedtuple('BuildDiff2', ['component', 'from_build', 'to_build'])
441 build_diffs = {k: bd2(k, v.from_build, v.to_build) for k, v in obj.builds.items()}
442 dict_ser = serializers.DictField(child=self.BuildDiff2Serializer())
443 return dict_ser.to_representation(build_diffs)
445 def __statuses(self, statuses):
446 return {k.name: v for k, v in statuses.items()}
448 def get_new_tests(self, obj):
449 NT = namedtuple('NewTest', ['test', 'statuses', 'exec_time'])
450 new_tests = {k: NT(k, {k.name: v for k, v in v.to_statuses.items()}, v.to_exec_times)
451 for k, v in obj.new_tests.tests.items()}
452 dict_ser = serializers.DictField(child=self.RunConfigDiffNewTestsSerializer())
453 return dict_ser.to_representation(new_tests)
456class ReplicationScriptSerializer(serializers.ModelSerializer):
457 created_by = serializers.StringRelatedField()
458 source_tracker = serializers.StringRelatedField()
459 destination_tracker = serializers.StringRelatedField()
461 class Meta:
462 model = ReplicationScript
463 fields = ('name', 'created_by', 'created_on', 'source_tracker', 'destination_tracker', 'script')
466class KnownIssuesSerializer(serializers.Serializer):
467 id = serializers.IntegerField(read_only=True)
468 testsuite = serializers.SerializerMethodField()
469 machine = serializers.SerializerMethodField()
470 run_id = serializers.SerializerMethodField()
471 test = serializers.SerializerMethodField()
472 status = serializers.SerializerMethodField()
473 url = serializers.SerializerMethodField()
475 bugs = serializers.SerializerMethodField()
477 def __init__(self, *args, **kwargs):
478 super().__init__(*args, **kwargs)
480 # Cache the serializers for performance reasons
481 self._char_ser = serializers.CharField(max_length=255, read_only=True)
482 self._int_ser = serializers.IntegerField(read_only=True)
483 self._bug_min_ser = BugMinimalSerializer(many=True)
485 def get_run_id(self, obj):
486 return self._int_ser.to_representation(obj.result.ts_run.run_id)
488 def get_testsuite(self, obj):
489 return self._char_ser.to_representation(obj.result.test.testsuite.name)
491 def get_test(self, obj):
492 return self._char_ser.to_representation(obj.result.test.name)
494 def get_machine(self, obj):
495 return self._char_ser.to_representation(obj.result.ts_run.machine.name)
497 def get_status(self, obj):
498 return self._char_ser.to_representation(obj.result.status.name)
500 def get_url(self, obj):
501 return self._char_ser.to_representation(obj.result.url)
503 def get_bugs(self, obj):
504 return self._bug_min_ser.to_representation(obj.matched_ifa.issue.bugs.all())
507class BugTrackerAccountSerializer(serializers.ModelSerializer):
508 class Meta:
509 model = BugTrackerAccount
510 fields = ('id', 'is_developer')
511 read_only_fields = ('id', )
514class ShortenerSerializer(serializers.ModelSerializer):
515 class Meta:
516 model = Shortener
517 fields = ('id', 'shorthand', 'full', 'added_on', 'last_accessed')
518 read_only_fields = ('id', 'shorthand', 'full', 'added_on', 'last_accessed')
521class RateSerializer(serializers.Serializer):
522 count = serializers.IntegerField()
523 total = serializers.IntegerField()
524 percent = serializers.FloatField()
527class BugSerializer(serializers.ModelSerializer):
528 class Meta:
529 model = Bug
530 fields = ('short_name', 'title', 'url')
531 read_only_fields = ('short_name', 'title', 'url')
534class RestIssueSerializer(serializers.ModelSerializer):
535 class Meta:
536 model = Issue
537 fields = ('__all__')
538 read_only_fields = ('id', 'added_on', 'archived_on', 'runconfigs_covered_count', 'runconfigs_affected_count',
539 'last_seen', 'added_by', 'archived_by', 'last_seen_runconfig')
541 bugs = BugSerializer(many=True)
544class IssueSerializer(serializers.ModelSerializer):
545 class Meta:
546 model = Issue
547 fields = ('id', 'bugs', 'expected')
548 read_only_fields = ('id', 'bugs', 'expected')
550 bugs = BugSerializer(many=True)
553class IssueSerializerMinimal(serializers.ModelSerializer):
554 class Meta:
555 model = Issue
556 fields = ('id', )
557 read_only_fields = ('id', )
560def serialize_issue_hitrate(issues, minimal=False):
561 Serializer = IssueSerializerMinimal if minimal else IssueSerializer
563 ret = []
564 for issue, rate in issues.items():
565 val = Serializer(issue).data
566 val['hit_rate'] = RateSerializer(rate).data
567 ret.append(val)
568 return ret
571def serialize_MetricPassRatePerRunconfig(history):
572 runconfigs = OrderedDict()
573 for runconfig, _statuses in history.runconfigs.items():
574 runconfigs[str(runconfig)] = OrderedDict()
575 for status, rate in _statuses.items():
576 runconfigs[str(runconfig)][str(status)] = RateSerializer(rate).data
578 statuses = OrderedDict()
579 for status, _runconfigs in history.statuses.items():
580 statuses[str(status)] = OrderedDict()
581 for runconfig, rate in _runconfigs.items():
582 statuses[str(status)][str(runconfig)] = RateSerializer(rate).data
584 return {
585 "runconfigs": runconfigs,
586 "statuses": statuses,
587 "most_hit_issues": serialize_issue_hitrate(history.most_hit_issues),
588 "query_key": history.query.query_key,
589 }
592class PassRateStatisticsSerializer(serializers.Serializer):
593 passrate = RateSerializer()
594 runrate = RateSerializer()
595 discarded_rate = RateSerializer()
596 notrun_rate = RateSerializer()
599def serialize_MetricPassRatePerTest(metric_passrate):
600 discarded_status = "discarded (expected)"
602 tests = OrderedDict()
603 for test, results in metric_passrate.tests.items():
604 if results.is_fully_discarded:
605 tests[str(test)] = {
606 "status": discarded_status,
607 "is_pass": False,
608 "is_run": False,
609 "duration": str(results.duration),
610 "issues_hit": serialize_issue_hitrate(results.issue_occurence_rates, minimal=True),
611 }
612 else:
613 tests[str(test)] = {
614 "status": str(results.overall_result),
615 "is_pass": results.is_pass,
616 "is_run": not results.overall_result.is_notrun,
617 "duration": str(results.duration),
618 "issues_hit": serialize_issue_hitrate(results.issue_occurence_rates, minimal=True),
619 }
621 statuses = OrderedDict()
622 for status, rate in metric_passrate.statuses.items():
623 statuses[str(status)] = RateSerializer(rate).data
625 return {
626 "tests": tests,
627 "statuses": statuses,
628 "discarded_status": discarded_status,
629 "machines": [str(m) for m in metric_passrate.machines],
630 "runconfigs": RunConfigSerializer(metric_passrate.runconfigs, many=True).data,
631 "raw_statistics": PassRateStatisticsSerializer(metric_passrate.raw_statistics).data,
632 "statistics": PassRateStatisticsSerializer(metric_passrate.statistics).data,
633 "most_hit_issues": serialize_issue_hitrate(metric_passrate.most_hit_issues),
634 "uncovered_failure_rate": RateSerializer(metric_passrate.uncovered_failure_rate).data,
635 "notrun_rate": RateSerializer(metric_passrate.notrun_rate).data,
636 "most_interrupting_issues": serialize_issue_hitrate(metric_passrate.most_interrupting_issues),
637 "unknown_failure_interruption_rate": RateSerializer(metric_passrate.unknown_failure_interruption_rate).data,
638 "unexplained_interruption_rate": RateSerializer(metric_passrate.unexplained_interruption_rate).data,
639 "query_key": metric_passrate.query.query_key,
640 }