Passed
Push — master ( b3b73f...4bb500 )
by Alexander
02:47
created

TestRun.stats_executions_status()   A

Complexity

Conditions 2

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 24
rs 9.7
c 0
b 0
f 0
cc 2
nop 1
1
# -*- coding: utf-8 -*-
2
from collections import namedtuple
3
4
import vinaigrette
5
from colorfield.fields import ColorField
6
from django.conf import settings
7
from django.db import models
8
from django.urls import reverse
9
from django.utils.translation import gettext_lazy as _
10
from django.utils.translation import override
11
12
from tcms.core.contrib.linkreference.models import LinkReference
13
from tcms.core.history import KiwiHistoricalRecords
14
from tcms.core.models.base import UrlMixin
15
16
TestExecutionStatusSubtotal = namedtuple(
17
    "TestExecutionStatusSubtotal",
18
    [
19
        "CompletedPercentage",
20
        "FailurePercentage",
21
        "SuccessPercentage",
22
    ],
23
)
24
25
26
class TestRun(models.Model, UrlMixin):
27
    history = KiwiHistoricalRecords()
28
29
    # todo: this field should be removed in favor of plan.product_version
30
    # no longer shown in edit forms
31
    product_version = models.ForeignKey(
32
        "management.Version", related_name="version_run", on_delete=models.CASCADE
33
    )
34
35
    start_date = models.DateTimeField(auto_now_add=True, db_index=True)
36
    stop_date = models.DateTimeField(null=True, blank=True, db_index=True)
37
    planned_start = models.DateTimeField(db_index=True, null=True, blank=True)
38
    planned_stop = models.DateTimeField(db_index=True, null=True, blank=True)
39
40
    summary = models.TextField()
41
    notes = models.TextField(blank=True)
42
43
    plan = models.ForeignKey(
44
        "testplans.TestPlan", related_name="run", on_delete=models.CASCADE
45
    )
46
    build = models.ForeignKey(
47
        "management.Build", related_name="build_run", on_delete=models.CASCADE
48
    )
49
    manager = models.ForeignKey(
50
        settings.AUTH_USER_MODEL, related_name="manager", on_delete=models.CASCADE
51
    )
52
    default_tester = models.ForeignKey(
53
        settings.AUTH_USER_MODEL,
54
        null=True,
55
        blank=True,
56
        related_name="default_tester",
57
        on_delete=models.CASCADE,
58
    )
59
60
    tag = models.ManyToManyField(
61
        "management.Tag", through="testruns.TestRunTag", related_name="run"
62
    )
63
64
    cc = models.ManyToManyField(settings.AUTH_USER_MODEL, through="testruns.TestRunCC")
65
66
    def __str__(self):
67
        return self.summary
68
69
    def _get_absolute_url(self):
70
        return reverse(
71
            "testruns-get",
72
            args=[
73
                self.pk,
74
            ],
75
        )
76
77
    def get_absolute_url(self):
78
        return self._get_absolute_url()
79
80
    def get_notify_addrs(self):
81
        """
82
        Get the all related mails from the run
83
        """
84
        send_to = [self.manager.email]
85
        send_to.extend(self.cc.values_list("email", flat=True))
86
        if self.default_tester_id:
87
            send_to.append(self.default_tester.email)
88
89
        for execution in self.executions.select_related("assignee").all():
90
            if execution.assignee_id:
91
                send_to.append(execution.assignee.email)
92
93
        send_to = set(send_to)
94
        # don't email author of last change
95
        send_to.discard(
96
            getattr(
97
                self.history.latest().history_user,  # pylint: disable=no-member
98
                "email",
99
                "",
100
            )
101
        )
102
        return list(send_to)
103
104
    def create_execution(
105
        self,
106
        case,
107
        assignee=None,
108
        build=None,
109
        sortkey=0,
110
    ):
111
        assignee = (
112
            assignee
113
            or (case.default_tester_id and case.default_tester)
114
            or (self.default_tester_id and self.default_tester)
115
        )
116
117
        return self.executions.create(
118
            case=case,
119
            assignee=assignee,
120
            tested_by=None,
121
            # usually IDLE but users can customize statuses
122
            status=TestExecutionStatus.objects.filter(weight=0).first(),
123
            case_text_version=case.history.latest().history_id,
124
            build=build or self.build,
125
            sortkey=sortkey,
126
            stop_date=None,
127
            start_date=None,
128
        )
129
130
    def add_tag(self, tag):
131
        return TestRunTag.objects.get_or_create(run=self, tag=tag)
132
133
    def add_cc(self, user):
134
        return TestRunCC.objects.get_or_create(
135
            run=self,
136
            user=user,
137
        )
138
139
    def remove_tag(self, tag):
140
        TestRunTag.objects.filter(run=self, tag=tag).delete()
141
142
    def remove_cc(self, user):
143
        TestRunCC.objects.filter(run=self, user=user).delete()
144
145
    @override("en")
146
    def stats_executions_status(self):
147
        """
148
        Get statistics based on executions' status
149
150
        :return: the statistics including the number of each status mapping,
151
                 total number of executions, complete percent, and failure percent.
152
        :rtype: namedtuple
153
        """
154
        total_count = self.executions.count()
155
        if total_count:
156
            complete_count = self.executions.exclude(status__weight=0).count()
157
            complete_percent = complete_count * 100.0 / total_count
158
159
            failing_count = self.executions.filter(status__weight__lt=0).count()
160
            failing_percent = failing_count * 100.0 / total_count
161
        else:
162
            complete_percent = 0.0
163
            failing_percent = 0.0
164
165
        return TestExecutionStatusSubtotal(
166
            complete_percent,
167
            failing_percent,
168
            complete_percent - failing_percent,
169
        )
170
171
172
class TestExecutionStatus(models.Model, UrlMixin):
173
    class Meta:
174
        # used in the admin view
175
        verbose_name_plural = _("Test execution statuses")
176
177
    name = models.CharField(max_length=60, blank=True, unique=True)
178
    weight = models.IntegerField(default=0)
179
    icon = models.CharField(max_length=64)
180
    color = ColorField()
181
182
    def __str__(self):
183
        return self.name
184
185
186
# register model for DB translations
187
vinaigrette.register(TestExecutionStatus, ["name"])
188
189
190
class TestExecution(models.Model, UrlMixin):
191
    history = KiwiHistoricalRecords()
192
193
    assignee = models.ForeignKey(
194
        settings.AUTH_USER_MODEL,
195
        blank=True,
196
        null=True,
197
        related_name="execution_assignee",
198
        on_delete=models.CASCADE,
199
    )
200
    tested_by = models.ForeignKey(
201
        settings.AUTH_USER_MODEL,
202
        blank=True,
203
        null=True,
204
        related_name="execution_tester",
205
        on_delete=models.CASCADE,
206
    )
207
    case_text_version = models.IntegerField()
208
    start_date = models.DateTimeField(null=True, blank=True, db_index=True)
209
    stop_date = models.DateTimeField(null=True, blank=True, db_index=True)
210
    sortkey = models.IntegerField(null=True, blank=True)
211
212
    @property
213
    def actual_duration(self):
214
        return self.stop_date - self.start_date
215
216
    run = models.ForeignKey(
217
        TestRun, related_name="executions", on_delete=models.CASCADE
218
    )
219
    case = models.ForeignKey(
220
        "testcases.TestCase", related_name="executions", on_delete=models.CASCADE
221
    )
222
    status = models.ForeignKey(TestExecutionStatus, on_delete=models.CASCADE)
223
    build = models.ForeignKey("management.Build", on_delete=models.CASCADE)
224
225
    class Meta:
226
        unique_together = ("case", "run", "case_text_version")
227
228
    def __str__(self):
229
        return "%s: %s" % (self.pk, self.case_id)
230
231
    def links(self):
232
        return LinkReference.objects.filter(execution=self.pk)
233
234
    def get_bugs(self):
235
        return self.links().filter(is_defect=True)
236
237
    def _get_absolute_url(self):
238
        # NOTE: this returns the URL to the TestRun containing this TestExecution!
239
        return reverse("testruns-get", args=[self.run_id])
240
241
242
class TestRunTag(models.Model):
243
    tag = models.ForeignKey("management.Tag", on_delete=models.CASCADE)
244
    run = models.ForeignKey(TestRun, related_name="tags", on_delete=models.CASCADE)
245
246
247
class TestRunCC(models.Model):
248
    run = models.ForeignKey(TestRun, related_name="cc_list", on_delete=models.CASCADE)
249
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
250
251
    class Meta:
252
        unique_together = ("run", "user")
253