Changeset - 36d81185efe4
[Not reviewed]
default
0 1 1
Thomas De Schampheleire - 11 years ago 2015-04-17 14:49:37
thomas.de.schampheleire@gmail.com
changeset_status: add unit tests for calculation of overall status

Add unit tests for the calculation of the review status of a changeset/pull
request. To allow this, some reorganization of the ChangesetStatusModel is
needed.
2 files changed with 61 insertions and 8 deletions:
0 comments (0 inline, 0 general)
kallithea/model/changeset_status.py
Show inline comments
 
@@ -21,125 +21,140 @@ Original author and date, and relevant c
 
# 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 

	
 
import logging
 
from collections import  defaultdict
 
from sqlalchemy.orm import joinedload
 

	
 
from kallithea.model import BaseModel
 
from kallithea.model.db import ChangesetStatus, PullRequest
 
from kallithea.lib.exceptions import StatusChangeOnClosedPullRequestError
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ChangesetStatusModel(BaseModel):
 

	
 
    cls = ChangesetStatus
 

	
 
    def __get_changeset_status(self, changeset_status):
 
        return self._get_instance(ChangesetStatus, changeset_status)
 

	
 
    def __get_pull_request(self, pull_request):
 
        return self._get_instance(PullRequest, pull_request)
 

	
 
    def _get_status_query(self, repo, revision, pull_request,
 
                          with_revisions=False):
 
        repo = self._get_repo(repo)
 

	
 
        q = ChangesetStatus.query()\
 
            .filter(ChangesetStatus.repo == repo)
 
        if not with_revisions:
 
            q = q.filter(ChangesetStatus.version == 0)
 

	
 
        if revision:
 
            q = q.filter(ChangesetStatus.revision == revision)
 
        elif pull_request:
 
            pull_request = self.__get_pull_request(pull_request)
 
            q = q.filter(ChangesetStatus.pull_request == pull_request)
 
        else:
 
            raise Exception('Please specify revision or pull_request')
 
        q = q.order_by(ChangesetStatus.version.asc())
 
        return q
 

	
 
    def _calculate_status(self, statuses):
 
        """
 
        Given a list of statuses, calculate the resulting status, according to
 
        the policy: approve if consensus.
 
        """
 

	
 
        approved_votes = 0
 
        for st in statuses:
 
            if st and st.status == ChangesetStatus.STATUS_APPROVED:
 
                approved_votes += 1
 

	
 
        result = ChangesetStatus.STATUS_UNDER_REVIEW
 
        if approved_votes and approved_votes == len(statuses):
 
            result = ChangesetStatus.STATUS_APPROVED
 

	
 
        return result
 

	
 
    def calculate_pull_request_result(self, pull_request):
 
        """
 
        Policy: approve if consensus. Only approve and reject counts as valid votes.
 
        Return a tuple (reviewers, pending reviewers, pull request status)
 
        Only approve and reject counts as valid votes.
 
        """
 

	
 
        # collect latest votes from all voters
 
        cs_statuses = dict()
 
        for st in reversed(self.get_statuses(pull_request.org_repo,
 
                                             pull_request=pull_request,
 
                                             with_revisions=True)):
 
            cs_statuses[st.author.username] = st
 

	
 
        # collect votes from official reviewers
 
        pull_request_reviewers = []
 
        pull_request_pending_reviewers = []
 
        approved_votes = 0
 
        relevant_statuses = []
 
        for r in pull_request.reviewers:
 
            st = cs_statuses.get(r.user.username)
 
            if st and st.status == ChangesetStatus.STATUS_APPROVED:
 
                approved_votes += 1
 
            relevant_statuses.append(st)
 
            if not st or st.status in (ChangesetStatus.STATUS_NOT_REVIEWED,
 
                                       ChangesetStatus.STATUS_UNDER_REVIEW):
 
                st = None
 
                pull_request_pending_reviewers.append(r.user)
 
            pull_request_reviewers.append((r.user, st))
 

	
 
        # calculate result
 
        result = ChangesetStatus.STATUS_UNDER_REVIEW
 
        if approved_votes and approved_votes == len(pull_request.reviewers):
 
            result = ChangesetStatus.STATUS_APPROVED
 
        result = self._calculate_status(relevant_statuses)
 

	
 
        return (pull_request_reviewers,
 
                pull_request_pending_reviewers,
 
                result)
 

	
 
    def get_statuses(self, repo, revision=None, pull_request=None,
 
                     with_revisions=False):
 
        q = self._get_status_query(repo, revision, pull_request,
 
                                   with_revisions)
 
        q = q.options(joinedload('author'))
 
        return q.all()
 

	
 
    def get_status(self, repo, revision=None, pull_request=None, as_str=True):
 
        """
 
        Returns latest status of changeset for given revision or for given
 
        pull request. Statuses are versioned inside a table itself and
 
        version == 0 is always the current one
 

	
 
        :param repo:
 
        :param revision: 40char hash or None
 
        :param pull_request: pull_request reference
 
        :param as_str: return status as string not object
 
        """
 
        q = self._get_status_query(repo, revision, pull_request)
 

	
 
        # need to use first here since there can be multiple statuses
 
        # returned from pull_request
 
        status = q.first()
 
        if as_str:
 
            return str(status.status) if status else ChangesetStatus.DEFAULT
 
        return status
 

	
 
    def set_status(self, repo, status, user, comment, revision=None,
 
                   pull_request=None, dont_allow_on_closed_pull_request=False):
 
        """
 
        Creates new status for changeset or updates the old ones bumping their
 
        version, leaving the current status at the value of 'status'.
 

	
 
        :param repo:
 
        :param status:
 
        :param user:
 
        :param comment:
 
        :param revision:
 
        :param pull_request:
 
        :param dont_allow_on_closed_pull_request: don't allow a status change
 
            if last status was for pull request and it's closed. We shouldn't
 
            mess around this manually
 
        """
kallithea/tests/models/test_changeset_status.py
Show inline comments
 
new file 100644
 
from kallithea.tests import *
 
from kallithea.model.changeset_status import ChangesetStatusModel
 
from kallithea.model.db import ChangesetStatus
 

	
 
# shorthands
 
STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
 
STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
 
STATUS_NOT_REVIEWED = ChangesetStatus.STATUS_NOT_REVIEWED
 
STATUS_UNDER_REVIEW = ChangesetStatus.STATUS_UNDER_REVIEW
 

	
 
class ChangesetStatusMock(object):
 

	
 
    def __init__(self, status):
 
        self.status = status
 

	
 
S = ChangesetStatusMock
 

	
 
class TestChangesetStatusCalculation(BaseTestCase):
 

	
 
    def setUp(self):
 
        self.m = ChangesetStatusModel()
 

	
 
    @parameterized.expand([
 
        ('empty list', STATUS_UNDER_REVIEW, []),
 
        ('approve', STATUS_APPROVED, [S(STATUS_APPROVED)]),
 
        ('approve2', STATUS_APPROVED, [S(STATUS_APPROVED), S(STATUS_APPROVED)]),
 
        ('approve_reject', STATUS_UNDER_REVIEW, [S(STATUS_APPROVED), S(STATUS_REJECTED)]),
 
        ('approve_underreview', STATUS_UNDER_REVIEW, [S(STATUS_APPROVED), S(STATUS_UNDER_REVIEW)]),
 
        ('approve_notreviewed', STATUS_UNDER_REVIEW, [S(STATUS_APPROVED), S(STATUS_NOT_REVIEWED)]),
 
        ('underreview', STATUS_UNDER_REVIEW, [S(STATUS_UNDER_REVIEW), S(STATUS_UNDER_REVIEW)]),
 
        ('reject', STATUS_UNDER_REVIEW, [S(STATUS_REJECTED)]),
 
        ('reject_underreview', STATUS_UNDER_REVIEW, [S(STATUS_REJECTED), S(STATUS_UNDER_REVIEW)]),
 
        ('reject_notreviewed', STATUS_UNDER_REVIEW, [S(STATUS_REJECTED), S(STATUS_NOT_REVIEWED)]),
 
        ('notreviewed', STATUS_UNDER_REVIEW, [S(STATUS_NOT_REVIEWED)]),
 
    ])
 
    def test_result(self, name, expected_result, statuses):
 
        result = self.m._calculate_status(statuses)
 
        self.assertEqual(result, expected_result)
0 comments (0 inline, 0 general)