diff options
author | Stephen Finucane <stephen.finucane@intel.com> | 2015-07-23 11:53:13 +0100 |
---|---|---|
committer | Stephen Finucane <stephen.finucane@intel.com> | 2015-11-05 17:08:02 +0000 |
commit | 15f26de93ce13ae46bb0052403bc4c192ae5724b (patch) | |
tree | 0767105ef67b4e332653883c8123f25570f9573d | |
parent | fc708e1db1274f030004fbe4f395c55c4c29a5bf (diff) | |
download | patchwork-15f26de93ce13ae46bb0052403bc4c192ae5724b.tar patchwork-15f26de93ce13ae46bb0052403bc4c192ae5724b.tar.gz |
models: Add properties related to checks
Add properties for 'Patch' to get the unique checks associated with
a patch, the total number of each type of check and the combined state
of the check. These will be necessary to display this information to
the user.
Signed-off-by: Stephen Finucane <stephen.finucane@intel.com>
-rw-r--r-- | patchwork/models.py | 70 | ||||
-rw-r--r-- | patchwork/tests/test_checks.py | 162 |
2 files changed, 232 insertions, 0 deletions
diff --git a/patchwork/models.py b/patchwork/models.py index 80f1eff..a2b9498 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -329,6 +329,76 @@ class Patch(models.Model): str = fname_re.sub('-', self.name) return str.strip('-') + '.patch' + @property + def combined_check_state(self): + """Return the combined state for all checks. + + Generate the combined check's state for this patch. This check + is one of the following, based on the value of each unique + check: + + * failure, if any context's latest check reports as failure + * warning, if any context's latest check reports as warning + * pending, if there are no checks, or a context's latest + Check reports as pending + * success, if latest checks for all contexts reports as + success + """ + states = [check.state for check in self.checks] + + if not states: + return Check.STATE_PENDING + + for state in [Check.STATE_FAIL, Check.STATE_WARNING, + Check.STATE_PENDING]: # order sensitive + if state in states: + return state + + return Check.STATE_SUCCESS + + @property + def checks(self): + """Return the list of unique checks. + + Generate a list of checks associated with this patch for each + type of Check. Only "unique" checks are considered, + identified by their 'context' field. This means, given n + checks with the same 'context', the newest check is the only + one counted regardless of its value. The end result will be a + association of types to number of unique checks for said + type. + """ + unique = {} + + for check in self.check_set.all(): + ctx = check.context + + # recheck condition - ignore the older result + if ctx in unique and unique[ctx].date > check.date: + continue + + unique[ctx] = check + + return unique.values() + + @property + def check_count(self): + """Generate a list of unique checks for each patch. + + Compile a list of checks associated with this patch for each + type of check. Only "unique" checks are considered, identified + by their 'context' field. This means, given n checks with the + same 'context', the newest check is the only one counted + regardless of its value. The end result will be a association + of types to number of unique checks for said type. + """ + counts = {key: 0 for key, _ in Check.STATE_CHOICES} + + for check in self.checks: + counts[check.state] += 1 + + return counts + @models.permalink def get_absolute_url(self): return ('patchwork.views.patch.patch', (), {'patch_id': self.id}) diff --git a/patchwork/tests/test_checks.py b/patchwork/tests/test_checks.py new file mode 100644 index 0000000..6d4f7e3 --- /dev/null +++ b/patchwork/tests/test_checks.py @@ -0,0 +1,162 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2015 Intel Corporation +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from datetime import datetime as dt, timedelta + +from django.conf import settings +from django.db import connection +from django.test import TransactionTestCase + +from patchwork.models import Patch, Check +from patchwork.tests.utils import defaults, create_user + + +class PatchChecksTest(TransactionTestCase): + fixtures = ['default_tags', 'default_states'] + + def setUp(self): + project = defaults.project + defaults.project.save() + defaults.patch_author_person.save() + self.patch = Patch(project=project, + msgid='x', name=defaults.patch_name, + submitter=defaults.patch_author_person, + content='') + self.patch.save() + self.user = create_user() + + def create_check(self, **kwargs): + check_values = { + 'patch': self.patch, + 'user': self.user, + 'date': dt.now(), + 'state': Check.STATE_SUCCESS, + 'target_url': 'http://example.com/', + 'description': '', + 'context': 'intel/jenkins-ci', + } + + for key in check_values: + if key in kwargs: + check_values[key] = kwargs[key] + + check = Check(**check_values) + check.save() + return check + + def assertCheckEqual(self, patch, check_state): + self.assertEqual(self.patch.combined_check_state, check_state) + + def assertChecksEqual(self, patch, checks=None): + if not checks: + checks = [] + + self.assertEqual(len(self.patch.checks), len(checks)) + self.assertEqual( + sorted(self.patch.checks, key=lambda check: check.id), + sorted(checks, key=lambda check: check.id)) + + def assertCheckCountEqual(self, patch, total, state_counts=None): + if not state_counts: + state_counts = {} + + counts = self.patch.check_count + + self.assertEqual(self.patch.check_set.count(), total) + + for state in state_counts.keys(): + self.assertEqual(counts[state], state_counts[state]) + + # also check the ones we didn't explicitly state + for state, _ in Check.STATE_CHOICES: + if state not in state_counts: + self.assertEqual(counts[state], 0) + + def tearDown(self): + self.patch.delete() + + def test_checks__no_checks(self): + self.assertChecksEqual(self.patch, []) + + def test_checks__single_check(self): + check = self.create_check() + self.assertChecksEqual(self.patch, [check]) + + def test_checks__multiple_checks(self): + check_a = self.create_check() + check_b = self.create_check(context='new-context/test1') + self.assertChecksEqual(self.patch, [check_a, check_b]) + + def test_checks__duplicate_checks(self): + check_a = self.create_check(date=(dt.now() - timedelta(days=1))) + check_b = self.create_check() + # this isn't a realistic scenario (dates shouldn't be set by user so + # they will always increment), but it's useful to verify the removal + # of older duplicates by the function + check_c = self.create_check(date=(dt.now() - timedelta(days=2))) + self.assertChecksEqual(self.patch, [check_b]) + + def test_check_count__no_checks(self): + self.assertCheckCountEqual(self.patch, 0) + + def test_check_count__single_check(self): + self.create_check() + self.assertCheckCountEqual(self.patch, 1, {Check.STATE_SUCCESS: 1}) + + def test_check_count__multiple_checks(self): + self.create_check(date=(dt.now() - timedelta(days=1))) + self.create_check(context='new/test1') + self.assertCheckCountEqual(self.patch, 2, {Check.STATE_SUCCESS: 2}) + + def test_check_count__duplicate_check_same_state(self): + self.create_check(date=(dt.now() - timedelta(days=1))) + self.assertCheckCountEqual(self.patch, 1, {Check.STATE_SUCCESS: 1}) + + self.create_check() + self.assertCheckCountEqual(self.patch, 2, {Check.STATE_SUCCESS: 1}) + + def test_check_count__duplicate_check_new_state(self): + self.create_check(date=(dt.now() - timedelta(days=1))) + self.assertCheckCountEqual(self.patch, 1, {Check.STATE_SUCCESS: 1}) + + self.create_check(state=Check.STATE_FAIL) + self.assertCheckCountEqual(self.patch, 2, {Check.STATE_FAIL: 1}) + + def test_check__no_checks(self): + self.assertCheckEqual(self.patch, Check.STATE_PENDING) + + def test_check__single_check(self): + self.create_check() + self.assertCheckEqual(self.patch, Check.STATE_SUCCESS) + + def test_check__failure_check(self): + self.create_check() + self.create_check(context='new/test1', state=Check.STATE_FAIL) + self.assertCheckEqual(self.patch, Check.STATE_FAIL) + + def test_check__warning_check(self): + self.create_check() + self.create_check(context='new/test1', state=Check.STATE_WARNING) + self.assertCheckEqual(self.patch, Check.STATE_WARNING) + + def test_check__success_check(self): + self.create_check() + self.create_check(context='new/test1') + self.assertCheckEqual(self.patch, Check.STATE_SUCCESS) + |