views/testexecution.py
author David Douard <david.douard@logilab.fr>
Fri, 14 Nov 2014 12:51:47 +0100
changeset 1740 f3137466457a
parent 1560 4205ad135a12
child 1785 0a9ea9e3c412
permissions -rw-r--r--
Big Refactoring (BR) of the test execution model Get rid of narval preprocessors in favor of Recipes - Each preprocessing recipe must be run from the "main" recipe (thus we add a ATest.exec_recipe() method that can be called from Recipe execution). - We add a (Repository, checkout_recipe, Recipe) relation, which is the Recipe that tells how to retrieve the code for a given Repository - Also add a (ProjectEnvironment, setup_recipe, Recip) dedicated to perform the installation for a project (compile and install) - Add implementations for these recipes for Python project - Rewrite the quick recipe using this new model

"""this module contains the primary views for apycot entities

:organization: Logilab
:copyright: 2008-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: General Public License version 2 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
_ = unicode

from datetime import datetime

from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter

from logilab.mtconverter import xml_escape

from cubicweb import Unauthorized, tags
from cubicweb.predicates import is_instance, none_rset, score_entity
from cubicweb.view import EntityView
from cubicweb.web import box
from cubicweb.web.views import tabs, tableview, baseviews, uicfg
from cubicweb.web.views import ibreadcrumbs, idownloadable, navigation

from cubes.narval.views import no_robot_index

from cubes.narval.views.plan import PlanOptionsCell


_pvs = uicfg.primaryview_section
_pvdc = uicfg.primaryview_display_ctrl

class InfoLogMixin(object):

    def display_info_section(self, entity):
        rset = self._cw.execute(
            'Any X,T,L,V ORDERBY T,L WHERE X is CheckResultInfo, '
            'X type T, X label L, X value V, X for_check AE, AE eid %(ae)s',
            {'ae': entity.eid})
        title = self._cw._('execution information')
        self.wview('table', rset, 'null', title=title, displaycols=range(1, 4),
                   divid='info%s'%entity.eid)

class ChecksDescriptorMixin(object):
    def describe_execution(self, exc, changes_only=False, xml_compat=False):
        self._cw.add_css('cubes.apycot.css')
        _ = self._cw._
        if xml_compat:
            w = lambda x: self.w(xml_escape(x))
        else:
            w = self.w
        if not changes_only:
            if exc.endtime is not None:
                nb_checkers = len(exc.checkers)
                duration = exc.duration or self._cw._('unknown duration')
                if nb_checkers > 1:
                    w(u'<p>')
                    w(_(u'%(nb)s checkers run in %(dur)s')
                           % {'nb': nb_checkers, 'dur': duration})
                    w(u'</p>')
                else:
                    w(u'<p>')
                    w(_(u'%(nb)s checker run in %(dur)s')
                           % {u'nb': nb_checkers, 'dur': duration})
                    w(u'</p>')
            else:
                w(u'<p>')
                if exc.starttime:
                    duration = datetime.now() - exc.starttime
                else:
                    duration = self._cw._('unknown duration')
                w(_(u'running for %(dur)s') % {'dur': duration})
                w(u'</p>')
        changes = exc.status_changes()
        if changes_only:
            if changes:
                checkers = (new for old, new in changes.values())
            else:
                checkers = ()
                w(_(u'nothing changed'))
        else:
            checkers = exc.checkers
        if checkers:
            w(u'<dl>')
            for checker in checkers:
                w(u'<dt>%s</dt>' % xml_escape(checker.name))
                status = checker.view(u'incontext')
                if changes_only or (changes is not None and checker.name in changes):
                    old_status = changes[checker.name][0].view(u'incontext')
                    w(u'<dd><strong>%s</strong> (previously <strike><em>%s</em></strike>)</dd>'
                      % (status, old_status))
                else:
                    w(u'<dd><strong>%s</strong></dd>' % status)
            w(u'</dl>')


# TestExecution ################################################################

class TESummaryTable(EntityView):
    __regid__ = 'apycot.te.summarytable'
    __select__ = is_instance('TestExecution') | none_rset()
    title = _('Apycot executions')
    category = 'startupview'

    html_headers = no_robot_index
    default_rql = ('Any T,PE,TC,T,TB,TF, TS ORDERBY is_null(TST) DESC, TST DESC WHERE '
                   'T status TS, T using_config TC, T using_environment PE, '
                   'TR? wf_info_for T, TR creation_date TST, TR tr_count 0, '
                   'T branch TB, T execution_archive TF?')

    def call(self):
        w = self.w
        req = self._cw
        if self.cw_rset is None:
            self.cw_rset = req.execute(self.default_rql)
        self.wview('apycot.te.table', self.cw_rset, 'null')


class TETable(tableview.EntityTableView):
    __regid__ = 'apycot.te.table'
    __select__ = is_instance('TestExecution')

    columns = ['testexecution', 'projectenvironment', 'using_config', 'checks', 'branch', 'starttime', 'endtime', 'execution_archive']
    column_renderers = {
            'testexecution': tableview.MainEntityColRenderer(
                vid='apycot.te.statuscell'),
            'using_config': tableview.RelatedEntityColRenderer(
                header=_('TestConfig'), getrelated=lambda x: x.configuration),
            'projectenvironment': tableview.RelatedEntityColRenderer(
                header=_('ProjectEnvironment'), getrelated=lambda x: x.environment),
            'starttime': tableview.EntityTableColRenderer(lambda w, p: w(p._cw.format_date(p.starttime, time=True))),
            'endtime': tableview.EntityTableColRenderer(lambda w, p: w(p._cw.format_date(p.endtime, time=True))),
            'checks': tableview.MainEntityColRenderer(
                header=_('checks'), addcount=False, vid='apycot.te.summarycell'),
            'execution_archive': tableview.RelationColRenderer(
                subvid='icon')
            }
    layout_args = {
            'display_filter': 'top'
            }

    def call(self, columns=None):
        self._cw.add_css('cubes.apycot.css')
        super(TETable, self).call(columns)


class TENoPETable(TETable):
    __regid__ = 'apycot.te.nopetable'
    columns = ['testexecution', 'using_config', 'checks', 'branch', 'starttime', 'endtime', 'execution_archive']


class TENoTCTable(TETable):
    __regid__ = 'apycot.te.notctable'
    columns = ['testexecution', 'projectenvironment', 'checks', 'branch', 'starttime', 'endtime', 'execution_archive']


_pvdc.tag_attribute(('TestExecution', 'priority',), {'vid': 'tasksqueue.priority'}) # XXX rtag inheritance bug
_pvs.tag_subject_of(('TestExecution', 'execution_log', '*'), 'relations')
_pvdc.tag_subject_of(('TestExecution', 'execution_log','*'), {'vid': 'narval.formated_log',
                                                         'loglevel': 'Error'})
_pvs.tag_subject_of(('TestExecution', 'log_file', '*'), 'relations')
_pvdc.tag_subject_of(('TestExecution', 'log_file','*'), {'vid': 'narval.formated_log'})
_pvs.tag_subject_of(('TestExecution', 'using_revision', '*'), 'hidden')
_pvs.tag_subject_of(('TestExecution', 'using_config', '*'), 'hidden')
_pvs.tag_subject_of(('TestExecution', 'execution_archive', '*'), 'hidden')
_pvs.tag_subject_of(('TestExecution', 'execution_of', '*'), 'hidden')
_pvs.tag_object_of(('*', 'during_execution', 'TestExecution'), 'hidden')
_pvs.tag_attribute(('TestExecution', 'script'), 'hidden')

class TEPrimaryView(tabs.TabbedPrimaryView):
    __select__ = is_instance('TestExecution')

    default_tab = 'apycot.te.tab_setup'

    html_headers = no_robot_index

    @property
    def tabs(self):
        tabs = ['apycot.te.tab_setup']
        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
        for check in sorted(entity.reverse_during_execution,
                key=lambda checkresult: checkresult.creation_date):
            label = u'%s [<b class="status_%s">%s</b>]' % (
                xml_escape(check.name), check.status, self._cw._(check.status))
            tabs.append((check.anchored_name,
                         {'vid': 'apycot.te.checkresult', 'label': label,
                          'rset': check.as_rset()}))
        return tabs

    def render_entity_title(self, entity):
        self._cw.add_css('cubes.apycot.css')
        title = self._cw._('Execution of %(pe)s/%(config)s#%(branch)s') % {
            'config': entity.configuration.view('outofcontext'),
            'pe':     entity.environment.view('outofcontext'),
            'branch': entity.branch and xml_escape(entity.branch)}
        self.w('<h1>%s</h1>' % title)


class TEConfigTab(InfoLogMixin, tabs.PrimaryTab):
    __regid__ = _('apycot.te.tab_setup')
    __select__ = is_instance('TestExecution')

    html_headers = no_robot_index

    def display_version_configuration(self, entity):
        title = self._cw._('version configuration')
        try:
            rset = self._cw.execute(
                'Any R, REV, B ORDERBY RN '
                'WHERE TE using_revision REV, TE eid %(te)s, '
                'REV from_repository R, REV branch B, R source_url RN',
                {'te': entity.eid})
        except Unauthorized:
            return # user can't read repositories for instance
        self.wview('table', rset, 'null', title=title, divid='vc%s'%entity.eid)

    def display_recipe(self,entity):
        self._cw.add_css('pygments.css')
        title = self._cw._('recipe information')
        self.w('<h2>%s</h2>' % title)
        self.w(highlight(entity.script, PythonLexer(), HtmlFormatter()))

    def render_entity_relations(self, entity):
        self.display_recipe(entity)
        self.display_version_configuration(entity)
        self.display_info_section(entity)
        super(TEConfigTab, self).render_entity_relations(entity)


class TECheckResultsTab(InfoLogMixin, tabs.PrimaryTab):
    __regid__ = 'apycot.te.checkresult'
    __select__ = is_instance('CheckResult')

    html_headers = no_robot_index

    def render_entity_relations(self, entity):
        self.display_info_section(entity)
        super(TECheckResultsTab, self).render_entity_relations(entity)


class TEBreadCrumbTextView(ibreadcrumbs.BreadCrumbTextView):
    __select__ = is_instance('TestExecution')
    def cell_call(self, row, col):
        entity = self.cw_rset.get_entity(row, col)
        self.w(self._cw.format_date(entity.starttime, time=True))


class TEStatusCell(tabs.PrimaryTab):
    __select__ = is_instance('TestExecution')
    __regid__ = 'apycot.te.statuscell'
    def cell_call(self, row, col):
        entity = self.cw_rset.get_entity(row, col)
        self.w(tags.a(self._cw._(entity.status), href=entity.absolute_url(),
                      klass="global status_%s" % entity.status,
                      title=self._cw._('see detailed execution report')))

class TESummaryCell(tabs.PrimaryTab):
    __select__ = is_instance('TestExecution')
    __regid__ = 'apycot.te.summarycell'
    def cell_call(self, row, col):
        entity = self.cw_rset.get_entity(row, col)
        checks = []
        for check in sorted(entity.reverse_during_execution,
                key=lambda checkresult: checkresult.creation_date):
            content = u'%s (%s)' % (self._cw._(check.name), check.status)
            url = check.absolute_url()
            title = self._cw._('see execution report for %s') % check.name
            checks.append(tags.a(content, href=url, title=title,
                                 klass=('status_%s' % check.status)))
        if checks:
            self.w(u', '.join(checks))


class TEOptionsCell(PlanOptionsCell):
    __select__ = PlanOptionsCell.__select__ & is_instance('TestExecution')

    def cell_call(self, row, col, **kwargs):
        entity = self.cw_rset.get_entity(row, col)
        self.w(self._cw._(u'branch="%s"<br/>') % xml_escape(entity.branch))
        if entity.options:
            self.w(u'; ')
            self.w(xml_escape(u'; '.join(entity.options.splitlines())))


class TEDescriptionView(ChecksDescriptorMixin, EntityView):
    __regid__ = 'apycot.te.description'
    __select__ = is_instance('TestExecution')

    changes_only = False

    def cell_call(self, row, col):
        entity = self.cw_rset.get_entity(row, col)
        self.w(u'<h2>%s</h2>' % xml_escape(entity.dc_title()))
        self.describe_execution(entity, changes_only=self.changes_only)


class TEChangeView(TEDescriptionView):
    __regid__ = 'apycot.te.changes'
    changes_only = True

class TEInContextView(baseviews.InContextView):
    __select__ = is_instance('TestExecution')

    def cell_call(self, row, col):
        entity = self.cw_rset.get_entity(row, col)
        status = '<span class="status_%s">%s</span>' % (
            entity.status, entity.printable_value('status'))
        if entity.starttime:
            status = self._cw._('%(date)s: %(status)s') % {
                'status': status,
                'date': self._cw.format_date(entity.starttime, time=True)}
        else:
            status = '[%s]' % status
        self.w('<a href="%s" title="%s">%s</a>' % (
               entity.absolute_url(),
               self._cw._('view details'),
               status))

class TEOutOfContextView(baseviews.OutOfContextView):
    __select__ = is_instance('TestExecution')

    def cell_call(self, row, col):
        self._cw.add_css('cubes.apycot.css')
        entity = self.cw_rset.get_entity(row, col)
        status = '<span class="status_%s">%s</span>' % (
            entity.status, entity.printable_value('status'))
        if entity.starttime:
            status = self._cw._('%(pe)s/%(config)s on %(date)s: %(status)s') % {
                'config': xml_escape(entity.configuration.dc_title()),
                'pe': xml_escape(entity.environment.dc_title()),
                'status': status,
                'date': self._cw.format_date(entity.starttime, time=True)}
        else:
            status = self._cw._('%(pe)s/%(config)s &lt;%(status)s&gt;') % {
                'config': xml_escape(entity.configuration.dc_title()),
                'pe': xml_escape(entity.environment.dc_title()),
                'status': status}
        self.w('<a href="%s" title="%s">%s</a>' % (
               entity.absolute_url(), self._cw._('view details'), status))


class TEDownloadBox(box.EntityBoxTemplate):
    __regid__ = 'apycot.te.download_box'
    __select__ = (box.EntityBoxTemplate.__select__ & is_instance('TestExecution') &
                  score_entity(lambda x: x.execution_archive))

    def cell_call(self, row, col, **kwargs):
        archive = self.cw_rset.get_entity(row, col).execution_archive[0]
        idownloadable.download_box(self.w, archive,
                                   self._cw._('download execution environment'))


class TEIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
    __select__ = is_instance('TestExecution')

    def parent_entity(self):
        """hierarchy, used for breadcrumbs"""
        try:
            return self.entity.environment
        except IndexError: # XXX bw compat
            return self.entity.configuration

    def breadcrumbs(self, view=None, recurs=None):
        projectenv = self.parent_entity()
        breadcrumbs = projectenv.cw_adapt_to('IBreadCrumbs').breadcrumbs(view,
                                                                         recurs or set())
        if projectenv.__regid__ == 'ProjectEnvironment': # XXX bw compat
            breadcrumbs.append(self.entity.configuration)
        breadcrumbs.append(self.entity)
        return breadcrumbs


class TEIPrevNextAdapter(navigation.IPrevNextAdapter):
    __select__ = is_instance('TestExecution')

    def previous_entity(self):
        return self.entity.previous_execution()

    def next_entity(self):
        entity = self.entity
        rset = self._cw.execute(
            'Any X,TC ORDERBY X ASC LIMIT 1 '
            'WHERE X is TestExecution, X branch %(branch)s, X eid > %(x)s, '
            'X using_config TC, TC eid %(tc)s, '
            'X using_environment PE, PE eid %(pe)s',
            {'branch': entity.branch, 'x': entity.eid,
             'tc': entity.configuration.eid, 'pe': entity.environment.eid})
        if rset:
            return rset.get_entity(0, 0)


# CheckResult ##################################################################

_pvs.tag_attribute(('CheckResult', 'name'), 'hidden')
_pvs.tag_attribute(('CheckResult', 'status'), 'hidden')
_pvs.tag_subject_of(('CheckResult', 'log_file','*'), 'relations')
_pvdc.tag_subject_of(('CheckResult', 'log_file','*'), {'vid': 'narval.formated_log'})
_pvs.tag_subject_of(('CheckResult', 'during_execution', '*'), 'hidden')
_pvs.tag_object_of(('*', 'for_check', '*'), 'hidden')


class CRPrimaryView(TECheckResultsTab):
    __regid__ = 'primary'
    __select__ = is_instance('CheckResult')

    html_headers = no_robot_index

    def render_entity_title(self, entity):
        self._cw.add_css('cubes.apycot.css')
        self.w('<h4 id="%s" >%s [<span class="status_%s">%s</span>]</h4>'
               % (entity.anchored_name,
                  xml_escape(entity.name), entity.status, entity.status))



class CRStatusView(baseviews.InContextView):
    __select__ = is_instance('CheckResult')

    def cell_call(self, row, col):
        entity = self.cw_rset.get_entity(row, col)
        self.w('<a href="%s" class="status_%s" title="%s">%s</a>' % (
               entity.absolute_url(),
               entity.status,
               self._cw._('view details'),
               self._cw._(entity.status)))


class CRIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
    __select__ = is_instance('CheckResult')

    def parent_entity(self):
        return self.entity.execution


class CRIPrevNextAdapter(navigation.IPrevNextAdapter):
    __select__ = is_instance('CheckResult')

    def previous_entity(self):
        previous_exec = self.entity.execution.cw_adapt_to('IPrevNext').previous_entity()
        if previous_exec:
            return previous_exec.check_result_by_name(self.entity.name)

    def next_entity(self):
        next_exec = self.entity.execution.cw_adapt_to('IPrevNext').next_entity()
        if next_exec:
            return next_exec.check_result_by_name(self.entity.name)


class CRIIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
    __select__ = is_instance('CheckResultInfo')

    def parent_entity(self):
        return self.entity.check_result