Coverage for CIResults/serializers.py: 90%
353 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-23 13:11 +0000
« 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
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
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 RunConfigTagSerializer(serializers.ModelSerializer):
28 class Meta:
29 model = RunConfigTag
30 fields = ('id', 'description', 'url', 'public', '__str__')
31 read_only_fields = ('public', )
34class TestSuiteSerializer(serializers.ModelSerializer):
35 class Meta:
36 model = TestSuite
37 fields = ('id', '__str__')
40class TestSerializer(serializers.ModelSerializer):
41 testsuite = TestSuiteSerializer()
43 class Meta:
44 model = Test
45 fields = ('id', 'name', 'testsuite', 'public', 'vetted_on', 'first_runconfig', '__str__')
46 read_only_fields = ('public', )
49class MachineTagSerializer(serializers.ModelSerializer):
50 class Meta:
51 model = MachineTag
52 fields = ('id', 'name', 'public')
53 read_only_fields = ('added_on', )
56class MachineSerializer(serializers.ModelSerializer):
57 class Meta:
58 model = Machine
59 fields = ('id', 'public', 'vetted_on', '__str__')
60 read_only_fields = ('public', )
63class RestViewMachineSerializer(serializers.ModelSerializer):
64 tags = serializers.SlugRelatedField(many=True, slug_field='name', queryset=MachineTag.objects.all())
66 class Meta:
67 model = Machine
68 fields = ('id', 'name', 'description', 'public', 'vetted_on', 'aliases', 'tags')
71class ImportMachineError(Exception):
72 pass
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=[])
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
98 # List tags to show options
99 tags = {}
100 for tag in MachineTag.objects.all():
101 tags[tag.name] = tag
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)
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()
115 # Description
116 if description:
117 machine.description = description
119 # Alias
120 if alias_machine is not None:
121 machine.aliases = alias_machine
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])
128 machine.save()
129 return machine
132class TextStatusSerializer(serializers.ModelSerializer):
133 testsuite = TestSuiteSerializer()
135 class Meta:
136 model = TextStatus
137 fields = ('id', 'name', 'testsuite', '__str__')
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)
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', )
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())
158 class Meta:
159 model = RunConfig
160 fields = ('id', 'name', 'tags', 'url', 'added_on', 'builds', 'environment', 'temporary', '__str__')
161 read_only_fields = ('added_on', )
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
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())
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)
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))
226 rc = RunConfigResults(name=validated_data["runconfig_name"], parsed_results=run_results)
227 rc.commit_to_db()
228 return rc
231class ComponentSerializer(serializers.ModelSerializer):
232 class Meta:
233 model = Component
234 fields = ('id', 'name', 'description', 'url', 'public', '__str__')
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())
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']
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')
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()
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])
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)
275 def get_testsuite(self, obj):
276 ser = serializers.CharField(max_length=255, read_only=True)
277 return ser.to_representation(obj.testsuite.name)
279 def get_test(self, obj):
280 ser = serializers.CharField(max_length=255, read_only=True)
281 return ser.to_representation(obj.test.name)
283 def get_machine(self, obj):
284 ser = serializers.CharField(max_length=255, read_only=True)
285 return ser.to_representation(obj.machine.name)
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')
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
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
334class BugCompleteSerializer(serializers.ModelSerializer):
335 tracker = BugTrackerSerializer()
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')
349class BugMinimalSerializer(serializers.ModelSerializer):
350 class Meta:
351 model = Bug
352 fields = ('id', 'short_name', 'url')
353 read_only_fields = ('id', 'short_name', 'url')
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)
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)
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)
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)
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)
388 def __statuses(self, statuses):
389 return {k.name: v for k, v in statuses.items()}
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)
399class ReplicationScriptSerializer(serializers.ModelSerializer):
400 created_by = serializers.StringRelatedField()
401 source_tracker = serializers.StringRelatedField()
402 destination_tracker = serializers.StringRelatedField()
404 class Meta:
405 model = ReplicationScript
406 fields = ('name', 'created_by', 'created_on', 'source_tracker', 'destination_tracker', 'script')
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()
418 bugs = serializers.SerializerMethodField()
420 def __init__(self, *args, **kwargs):
421 super().__init__(*args, **kwargs)
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)
428 def get_run_id(self, obj):
429 return self._int_ser.to_representation(obj.result.ts_run.run_id)
431 def get_testsuite(self, obj):
432 return self._char_ser.to_representation(obj.result.test.testsuite.name)
434 def get_test(self, obj):
435 return self._char_ser.to_representation(obj.result.test.name)
437 def get_machine(self, obj):
438 return self._char_ser.to_representation(obj.result.ts_run.machine.name)
440 def get_status(self, obj):
441 return self._char_ser.to_representation(obj.result.status.name)
443 def get_url(self, obj):
444 return self._char_ser.to_representation(obj.result.url)
446 def get_bugs(self, obj):
447 return self._bug_min_ser.to_representation(obj.matched_ifa.issue.bugs.all())
450class BugTrackerAccountSerializer(serializers.ModelSerializer):
451 class Meta:
452 model = BugTrackerAccount
453 fields = ('id', 'is_developer')
454 read_only_fields = ('id', )
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')
464class RateSerializer(serializers.Serializer):
465 count = serializers.IntegerField()
466 total = serializers.IntegerField()
467 percent = serializers.FloatField()
470class BugSerializer(serializers.ModelSerializer):
471 class Meta:
472 model = Bug
473 fields = ('short_name', 'title', 'url')
474 read_only_fields = ('short_name', 'title', 'url')
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')
484 bugs = BugSerializer(many=True)
487class IssueSerializer(serializers.ModelSerializer):
488 class Meta:
489 model = Issue
490 fields = ('id', 'bugs', 'expected')
491 read_only_fields = ('id', 'bugs', 'expected')
493 bugs = BugSerializer(many=True)
496class IssueSerializerMinimal(serializers.ModelSerializer):
497 class Meta:
498 model = Issue
499 fields = ('id', )
500 read_only_fields = ('id', )
503def serialize_issue_hitrate(issues, minimal=False):
504 Serializer = IssueSerializerMinimal if minimal else IssueSerializer
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
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
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
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 }
535class PassRateStatisticsSerializer(serializers.Serializer):
536 passrate = RateSerializer()
537 runrate = RateSerializer()
538 discarded_rate = RateSerializer()
539 notrun_rate = RateSerializer()
542def serialize_MetricPassRatePerTest(metric_passrate):
543 discarded_status = "discarded (expected)"
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 }
564 statuses = OrderedDict()
565 for status, rate in metric_passrate.statuses.items():
566 statuses[str(status)] = RateSerializer(rate).data
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 }