initial revision
authorSylvain <syt@logilab.fr>
Fri, 05 Dec 2008 17:32:51 +0100
changeset 0 b8ed93ffed08
child 1 ca9bc888574c
child 2 84cc5187a3dc
initial revision
MANIFEST.in
__init__.py
__pkginfo__.py
data/cubes.apycot.css
data/cubes.apycot.js
debian/changelog
debian/compat
debian/control
debian/copyright
debian/cubicweb-apycot.prerm
debian/rules
entities.py
i18n/en.po
i18n/fr.po
migration/postcreate.py
schema.py
setup.py
test/data/bootstrap_cubes
test/pytestconf.py
test/realdb_test_apycot.py
test/test_apycot.py
views/__init__.py
views/primary.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MANIFEST.in	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,10 @@
+include *.py
+include ChangeLog
+
+recursive-include views *.py
+recursive-include entities *.py
+recursive-include sobjects *.py
+recursive-include schema Include *.py *.sql.postgres 
+recursive-include data external_resources *.gif *.png *.css *.ico *.js
+recursive-include i18n *.pot *.po
+recursive-include migration *.sql *.py depends.map
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/__init__.py	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,4 @@
+"""cubicweb-apycot application package
+
+store apycot data and extract reports from them
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/__pkginfo__.py	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,55 @@
+# pylint: disable-msg=W0622
+"""cubicweb-apycot application packaging information"""
+
+distname = 'cubicweb-apycot'
+
+numversion = (0, 1, 0)
+version = '.'.join(str(num) for num in numversion)
+
+license = 'LCL'
+copyright = '''Copyright (c) 2008 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr'''
+
+author = 'Logilab'
+author_email = 'contact@logilab.fr'
+
+short_desc = 'store apycot data and extract reports from them'
+long_desc = '''store apycot data and extract reports from them'''
+
+from os import listdir as _listdir
+from os.path import join, isdir
+
+web = ''
+
+pyversions = ['2.4']
+
+#from cubicweb.devtools.pkginfo import get_distutils_datafiles
+CUBES_DIR = join('share', 'cubicweb', 'cubes')
+THIS_CUBE_DIR = join(CUBES_DIR, 'apycot')
+
+def listdir(dirpath):
+    return [join(dirpath, fname) for fname in _listdir(dirpath)
+            if fname[0] != '.' and not fname.endswith('.pyc')
+            and not fname.endswith('~')]
+
+from glob import glob
+try:
+    data_files = [
+        # common files
+        [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']],
+        ]
+    
+    # check for possible extended cube layout
+    for dirname in ('entities', 'views', 'sobjects', 'schema', 'data', 'i18n', 'migration'):
+        if isdir(dirname):
+            data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)])
+    # Note: here, you'll need to add subdirectories if you want
+    # them to be included in the debian package
+except OSError:
+    # we are in an installed directory
+    pass
+
+
+cube_eid = None # <=== FIXME if you need direct bug-subscription
+__use__ = ()
+__recommend__ = ('jpl',)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/cubes.apycot.css	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,235 @@
+/* sample css file for APyCoT HTML reports
+ *
+ * Copyright (c) 2003-2004 LOGILAB S.A. (Paris, FRANCE).
+ * http://www.logilab.fr/ -- mailto:contact@logilab.fr
+ */
+
+/* old report style css, for backward compat *********************************/
+
+/* h1 {  */
+/*     text-align : center; */
+/*     border-bottom: 2px solid lightgrey;  */
+/* } */
+
+/* h2 {  */
+/*     text-align : left; */
+/*     background-color : lightgray; */
+/* } */
+
+/* h3 {  */
+/*     text-align : center; */
+/*     border-top: 1px solid lightgrey;  */
+/* } */
+
+/* h2 a, h3 a {  */
+/*     text-decoration : none; */
+/* } */
+
+.summary {
+    margin-bottom: 2em;
+}
+.summary td {
+    padding-left: 2em;
+}
+.summary a {
+    text-decoration: underline;
+    color: black;
+/*     line-height: 0.5em;  */
+/*     margin-bottom: 1em;  */
+/*     margin-top: 0;  */
+/*     list-style-type: decimal; */
+}
+
+/* .summary ul {  */
+/*     list-style-type: lower-alpha; */
+/* } */
+
+/* .summary a{  */
+/*     text-decoration: none; */
+/* } */
+
+/* div.check {  */
+/*     margin-top : 2em; */
+/* } */
+
+/* div.test {  */
+/*     margin-top : 1em; */
+/* } */
+
+/* a {  */
+/*     color : black; */
+/* } */
+
+/* table.toplinks {  */
+/*     text-align : center; */
+/*     width : 100%; */
+/* } */
+/* table.result {  */
+/*     width : 100%;  */
+/*     margin-top : 5px; */
+/*     cell-spacing : 0; */
+/* } */
+
+/* table.result th {  */
+/*     text-align : left; */
+/* } */
+
+/* table.info tr th {  */
+/*     text-align : left; */
+/* } */
+
+/* table.info tr td {  */
+/*     font-style: italic;     */
+/*     padding-left : 1em; */
+/* } */
+
+/* table.raw tr th {  */
+/*     text-align : left; */
+/* } */
+
+/* tr.odd {  */
+/*     background-color : 0EFEFEF;          */
+/* } */
+
+/* tr.even {  */
+/* } */
+
+/* td, th {  */
+/*     vertical-align : top; */
+/* } */
+
+.status_success, .status_success a, .status_success em { 
+    text-align : justify;
+    text-decoration : none;
+    color : green;
+}
+
+.status_partial, .status_partial a, .status_partial em  { 
+    text-align : justify;
+    text-decoration : none;
+    color : orange;
+}
+
+.status_failure, .status_failure a, .status_failure em { 
+    text-align : justify;
+    text-decoration : none;
+    color : red;
+    font-weight: bold;    
+}
+
+.status_error, .status_error a, .status_error em  { 
+    text-align : justify;
+    text-decoration : none;
+    color : blue;
+    font-weight: bold;    
+}
+
+.status_killed, .status_killed a, .status_killed em  { 
+    text-align : justify;
+    text-decoration : none;
+    color : blue;
+    font-weight: bold;    
+}
+
+.status_skipped, .status_skipped a, .status_skipped em  { 
+    text-align : justify;
+    text-decoration : none;
+    color : rgb(195, 176, 145);
+    font-weight: bold;    
+}
+
+.status_missing, .status_missing a, .status_missing em  { 
+    text-align : justify;
+    text-decoration : none;
+    color : purple ;
+    font-weight: bold;    
+}
+
+.status_nodata, .status_nodata a, .status_nodata em  { 
+    text-align : justify;
+    text-decoration : none;
+    color : #0095B6 ;
+    font-weight: bold;    
+}
+
+/* .active, .active a, .active em {  */
+/*     text-align : justify; */
+/*     text-decoration : none; */
+/*     color : green; */
+/* } */
+
+/* .sleep, .sleep a, .sleep em {  */
+/*     text-align : justify; */
+/*     text-decoration : none; */
+/* } */
+
+/* .path, .line, .severity {  */
+/*     font-style: italic;     */
+/* } */
+
+/* .msg {  */
+/*     width : 100%; */
+/*     text-align : justify; */
+/* } */
+
+/* pre {  */
+/*       margin-top: 0.1em; */
+/*       margin-bottom: 0.2em; */
+/* } */
+
+/* td a {  */
+/* } */
+
+/* .msg, .line, .severity {  */
+/* } */
+
+/* .testname {  */
+/*     width : 100%; */
+/*     text-align : left; */
+/*     text-decoration: none; */
+/*     text-transform : uppercase; */
+/*     font-weight: bold;     */
+/* } */
+
+
+/* .testinfo {  */
+/*     float: right; */
+/*     text-decoration: none; */
+/*     font-style: italic;     */
+/*     white-space: nowrap; */
+/*     font-size: 70%; */
+/* } */
+
+/* .checkname {  */
+/*     text-align : justify; */
+/*     text-decoration : none; */
+/* } */
+
+/* .sep { */
+/*     border-top: 2px solid lightgrey;  */
+/* } */
+
+/* /\* new report style css ******************************************************\/ */
+
+/* div.navlinks {  */
+/*     width : 100%; */
+/*     text-align : center; */
+/* } */
+
+/* div.navlinks a {  */
+/*     color : blue; */
+/*     text-decoration : none; */
+/*     margin-left: 1em; */
+/*     margin-right: 1em; */
+/* } */
+
+/* table.field {  */
+/*     border: 1px solid grey;  */
+/*     margin-top: 1em;  */
+/*     margin-bottom: 1em;  */
+/*     font-size: 80%; */
+/* } */
+
+/* th {  */
+/*     background-color : #DDDDDD; */
+/* } */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/cubes.apycot.js	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,1 @@
+// This contains template-specific javascript
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debian/changelog	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,6 @@
+cubicweb-apycot (0.1.0-1) unstable; urgency=low
+
+  * initial release
+
+ -- 
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debian/compat	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,1 @@
+5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debian/control	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,19 @@
+Source: cubicweb-apycot
+Section: web
+Priority: optional
+Maintainer: Logilab Packaging Team <contact@logilab.fr>
+Uploaders: Sylvain Thenault <sylvain.thenault@logilab.fr> 
+Build-Depends: debhelper (>= 5.0.37.1), python (>=2.4), python-dev (>=2.4)
+Standards-Version: 3.8.0
+
+
+Package: cubicweb-apycot
+Architecture: all
+Depends: cubicweb-common (>= 2.99.1)
+Description: store apycot data and extract reports from them
+ CubicWeb is a semantic web application framework.
+ .
+ store apycot data and extract reports from them
+ .
+ This package will install all the components you need to run the
+ cubicweb-apycot application (cube :)..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debian/copyright	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,14 @@
+This package was debianized by Logilab <contact@logilab.fr>
+
+Upstream Author: 
+
+  Logilab <contact@logilab.fr>
+
+Copyright:
+
+Copyright (c) 2008 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+Logilab Closed source License. This code is *NOT* open-source. Usage of this
+code is subject to a licence agreement. If you want to use it, you should
+contact logilab's sales service at commercial@logilab.fr .
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debian/cubicweb-apycot.prerm	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,27 @@
+#!/bin/sh -e
+
+delete_pyo_pyc () {
+  find /usr/share/cubicweb/cubes/apycot -name "*.pyc" | xargs rm -f
+  find /usr/share/cubicweb/cubes/apycot -name "*.pyo" | xargs rm -f
+}
+
+
+case "$1" in
+    failed-upgrade|abort-install|abort-upgrade|disappear)
+    ;;
+    upgrade)
+    delete_pyo_pyc
+    ;;
+    remove)
+    delete_pyo_pyc
+    ;;
+    purge)
+    ;;
+
+    *)
+        echo "postrm called with unknown argument \`$1'" >&2
+        exit 1
+
+esac
+
+#DEBHELPER#
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debian/rules	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,51 @@
+#!/usr/bin/make -f
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+build: build-stamp
+build-stamp: 
+	dh_testdir
+	python setup.py -q build
+	touch build-stamp
+
+clean: 
+	dh_testdir
+	dh_testroot
+	rm -f build-stamp configure-stamp
+	rm -rf build
+	find . -name "*.pyc" | xargs rm -f
+	dh_clean
+
+install: build
+	dh_testdir
+	dh_testroot
+	dh_clean -k
+	dh_installdirs -i
+	python setup.py -q install --no-compile --prefix=debian/cubicweb-apycot/usr/
+
+
+# Build architecture-independent files here.
+binary-indep: build install
+	dh_testdir
+	dh_testroot
+	dh_install -i
+	dh_installchangelogs -i
+	dh_installexamples -i
+	dh_installdocs -i
+	dh_installman -i
+	dh_link -i
+	dh_compress -i -X.py -X.ini -X.xml -Xtest
+	dh_fixperms -i
+	dh_installdeb -i
+	dh_gencontrol -i 
+	dh_md5sums -i
+	dh_builddeb -i
+
+
+# Build architecture-dependent files here.
+binary-arch: 
+
+binary: binary-indep 
+.PHONY: build clean binary-arch binary-indep binary
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/entities.py	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,97 @@
+"""this contains the cube-specific entities' classes
+
+:organization: Logilab
+:copyright: 2008 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"
+
+from logilab.common.textutils import TIME_UNITS, BYTE_UNITS, apply_units, get_csv
+
+from cubicweb.entities import AnyEntity
+
+def text_to_dict(text):
+    res = {}
+    if not text:
+        return res
+    for line in text.splitlines():
+        line = line.strip()
+        if line:
+            key, value = line.split('=', 1)
+            res[key.strip()] = value.strip()
+    return res
+
+
+class ProjectApycotConfig(AnyEntity):
+    id = 'ProjectApycotConfig'
+    widgets = {'vcs_repository': 'StringWidget',
+               'vcs_path': 'StringWidget',
+               'checks': 'StringWidget',
+               }
+
+    @property
+    def environment(self):
+        return text_to_dict(self.check_environment)
+
+    @property
+    def testconfig(self):
+        config = text_to_dict(self.check_config)
+        for option in ('max-cpu-time', 'max-reprieve', 'max-time'):
+            if option in config:
+                config[option] = apply_units(config[option], TIME_UNITS)
+        if 'max-memory' in config:
+            config['max-memory'] = apply_units(config['max-memory'], BYTE_UNITS)
+        return config
+
+    @property
+    def preprocessors(self):
+        return text_to_dict(self.check_preprocessors)
+    
+    @property
+    def repository_def(self):
+        return {
+            'repository_type': self.vcs_repository_type,
+            'repository': self.vcs_repository,
+            'path': self.vcs_path,
+            'tag': self.vcs_tag,
+            # view_url / view_root
+            }
+
+    @property
+    def default_checks(self):
+        return get_csv(self.checks)
+    
+    def repository(self):
+        from apycotbot.repositories import get_repository
+        return get_repository(self.repository_def)
+        
+    def dependencies(self, _done=None):
+        if _done is None:
+            _done = set()
+        _done.add(self.eid)
+        result = []
+        for pac in self.needs_checkout:
+            if pac.eid in _done:
+                continue
+            result.append(pac)
+            result += pac.dependencies(_done)
+        return result
+        
+    def preprocessor_fact(self, pptype):
+        from apycotbot import get_registered
+        try:
+            return get_registered('preprocessor', self.preprocessors[pptype])
+        except KeyError:
+            return None
+        
+class ApycotExecution(AnyEntity):
+    id = 'ApycotExecution'
+    @property
+    def apycot_config(self):
+        return self.using_config[0]
+    
+    def dc_title(self):
+        return self.req._('Execution for config %(config)s on %(date)s') % {
+            'config': self.apycot_config.name,
+            'date': self.format_date(self.starttime, time=True)}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/i18n/en.po	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,7 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/i18n/fr.po	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,13 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: 2.0\n"
+"POT-Creation-Date: 2006-01-12 17:35+CET\n"
+"PO-Revision-Date: 2008-02-15 12:55+0100\n"
+"Last-Translator: Logilab\n"
+"Language-Team: French <devel@logilab.fr.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/migration/postcreate.py	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,2 @@
+# postcreate script. You could setup a workflow here for example
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/schema.py	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,88 @@
+"""apycot cube's specific schema
+
+:organization: Logilab
+:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+
+class ProjectApycotConfig(EntityType):
+    """apycot configuration to register a project branch to test"""
+    name = String(required=True, unique=True, maxsize=128,
+                   description='name for this configuration')
+    vcs_repository_type = String(required=True,
+                                 vocabulary=(u'hg', u'svn', u'cvs', u'fs'),
+                                 description='kind of version control system (vcs): mercurial, subversion, cvs, file system (eg no version control)')
+    vcs_repository = String(required=True,
+                            description='path or url to the vcs repository containing the project')
+    vcs_path = String(description='relative path to the project into the repository')
+    vcs_tag  = String(description='vcs tag or branch to use for test\'s checkout', maxsize=200)
+
+    checks = String(description=_('comma separated list of checks to execute by default for this project'))
+    check_preprocessors = String(description=_('preprocessors to use for this project for install, debian, build_doc... (one per line)'))
+    check_environment = String(description=_('environment variables to be set in the check process environment (one per line)'))
+    check_config = String(description=_('preprocessor/checker options (one per line)'))
+    
+    needs_checkout = SubjectRelation('ProjectApycotConfig',
+                                     description=_('project\'s dependencies that should be installed from their repository during checks requiring installtion'))
+    # XXX groups
+
+class ApycotExecution(EntityType):
+    starttime = Datetime(required=True)
+    endtime   = Datetime()
+    using_config = SubjectRelation('ProjectApycotConfig', cardinality='1*', composite='object')
+    
+class CheckResult(EntityType):
+    """group results of execution of a specific test on a project"""
+    # add: apycotbot
+    name      = String(required=True, maxsize=128, description='check name')
+    status    = String(required=True,
+                       vocabulary=(u'success', u'partial',  u'failure', u'error', u'nodata', 
+                                   u'missing', 'skipped', 'killed',
+                                   u'processing'))
+    starttime = Datetime()
+    endtime   = Datetime()
+    during_execution = SubjectRelation('ApycotExecution', cardinality='1*', composite='object')
+
+    
+class CheckResultLog(EntityType):
+    """log message which has occured during execution of a specific test on a project"""
+    # add: apycotbot
+    path     = String()
+    line     = Int()
+    severity = Int(vocabulary=range(4)) # INFO, WARNING, ERROR, FATAL
+    msg      = String()
+ 
+    for_check = SubjectRelation(('CheckResult', 'ApycotExecution'), cardinality='1*', composite='object')
+
+
+class CheckResultInfo(EntityType):
+    """arbitrary information about execution of a specific test on a project"""
+    # add: apycotbot
+    type = String(internationalizable=True)
+    label = String(required=True, internationalizable=True)
+    value = String(required=True, internationalizable=True)
+    
+    for_check = SubjectRelation(('CheckResult', 'ApycotExecution'), cardinality='1*', composite='object')
+
+
+class for_check(RelationType):
+    inlined = True
+
+class using_config(RelationType):
+    inlined = True
+
+class during_execution(RelationType):
+    inlined = True
+
+
+
+# jpl extension
+if 'Project' in defined_types:
+
+    class has_apycot_config(RelationDefinition):
+        subject = 'Project'
+        object = 'ProjectApycotConfig'
+        cardinality = '*?'
+        composite = 'subject'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup.py	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+# pylint: disable-msg=W0404,W0622,W0704,W0613,W0152
+# Copyright (c) 2003-2004 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program 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.
+#
+# This program 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
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+""" Generic Setup script, takes package info from __pkginfo__.py file """
+
+import os
+import sys
+import shutil
+from distutils.core import setup
+from distutils import command
+from distutils.command import install_lib
+from os.path import isdir, exists, join, walk
+
+# import required features
+from __pkginfo__ import distname, version, license, short_desc, long_desc, \
+     web, author, author_email
+try:
+    from __pkginfo__ import scripts
+except ImportError:
+    scripts = []
+try:
+    from __pkginfo__ import data_files
+except ImportError:
+    data_files = None
+    
+def ensure_scripts(linux_scripts):
+    """creates the proper script names required for each platform
+    (taken from 4Suite)
+    """
+    from distutils import util
+    if util.get_platform()[:3] == 'win':
+        scripts_ = [script + '.bat' for script in linux_scripts]
+    else:
+        scripts_ = linux_scripts
+    return scripts_
+
+def install(**kwargs):
+    """setup entry point"""
+    return setup(name=distname,
+                 version=version,
+                 license =license,
+                 description=short_desc,
+                 long_description=long_desc,
+                 author=author,
+                 author_email=author_email,
+                 url=web,
+                 scripts=ensure_scripts(scripts),
+                 data_files=data_files,
+                 **kwargs)
+            
+if __name__ == '__main__' :
+    install()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/bootstrap_cubes	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,1 @@
+apycot
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/pytestconf.py	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,37 @@
+import os
+import pwd
+
+from logilab.common.pytest import PyTester
+
+def getlogin():
+    """avoid usinng os.getlogin() because of strange tty / stdin problems
+    (man 3 getlogin)
+    Another solution would be to use $LOGNAME, $USER or $USERNAME
+    """
+    return pwd.getpwuid(os.getuid())[0]
+
+
+def update_parser(parser):
+    login = getlogin()
+    parser.add_option('-r', '--rebuild-database', dest='rebuild_db',
+                      default=False, action="store_true",
+                      help="remove tmpdb and rebuilds the test database")
+    parser.add_option('-u', '--dbuser', dest='dbuser', action='store',
+                      default=login, help="database user")
+    parser.add_option('-w', '--dbpassword', dest='dbpassword', action='store',
+                      default=login, help="database name")
+    parser.add_option('-n', '--dbname', dest='dbname', action='store',
+                      default=None, help="database name")
+    parser.add_option('--euser', dest='euser', action='store',
+                      default=login, help="esuer name")
+    parser.add_option('--epassword', dest='epassword', action='store',
+                      default=login, help="euser's password' name")
+    return parser
+
+
+class CustomPyTester(PyTester):
+    def __init__(self, cvg, options):
+        super(CustomPyTester, self).__init__(cvg, options)
+        if options.rebuild_db:
+            os.unlink('tmpdb')
+            os.unlink('tmpdb-template')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/realdb_test_apycot.py	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,31 @@
+from cubicweb.devtools import buildconfig, loadconfig
+from cubicweb.devtools.testlib import RealDBTest
+
+def setup_module(options):
+    if options.source:
+        configcls = loadconfig(options.source)
+    elif options.dbname is None:
+        raise Exception('either <sourcefile> or <dbname> options are required')
+    else:
+        configcls = buildconfig(options.dbuser, options.dbpassword,
+                                               options.dbname, options.euser,
+                                               options.epassword)
+    RealDatabaseTC.configcls = configcls
+
+class RealDatabaseTC(RealDBTest):
+    configcls = None # set by setup_module()
+
+    def test_all_primaries(self):
+        for rset in self.iter_individual_rsets(limit=50):
+            yield self.view, 'primary', rset, rset.req.reset_headers()
+    
+    ## startup views
+    def test_startup_views(self):
+        for vid in self.list_startup_views():
+            req = self.request()
+            yield self.view, vid, None, req
+
+
+if __name__ == '__main__':
+    from logilab.common.testlib import unittest_main
+    unittest_main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test_apycot.py	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,16 @@
+"""template automatic tests"""
+
+from logilab.common.testlib import TestCase, unittest_main
+
+class DefaultTC(TestCase):
+    def test_something(self):
+        self.skip('this cube has no test')
+
+## uncomment the import if you want to activate automatic test for your
+## template
+
+# from cubicweb.devtools.testlib import AutomaticWebTest
+
+
+if __name__ == '__main__':
+    unittest_main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/__init__.py	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,1 @@
+'''apycot reports'''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/primary.py	Fri Dec 05 17:32:51 2008 +0100
@@ -0,0 +1,176 @@
+from cubicweb.web.views.baseviews import PrimaryView
+
+class InfoLogMixin(object):
+    def render_entity_metadata(self, entity):
+        pass
+    
+    def display_info_section(self, entity):
+        req = self.req
+        inforset = req.execute(
+            'Any 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}, 'ae')
+        if inforset:
+            self.w(u'<h5>%s</h5>' % req._('execution information'))
+            self.view('table', inforset, w=self.w)
+            
+    def display_log_section(self, entity):
+        req = self.req
+        logrset = req.execute(
+            'Any P,L,S,M ORDERBY P,L,S WHERE X is CheckResultLog, '
+            'X path P, X line L, X severity S, X msg M, X for_check AE, '
+            'AE eid %(ae)s', {'ae': entity.eid}, 'ae')
+        if logrset:
+            self.w(u'<h5>%s</h5>' % req._('logs'))
+            self.view('table', logrset, w=self.w)
+        
+
+class ApycotExecutionPrimaryView(InfoLogMixin, PrimaryView):
+    accepts = ('ApycotExecution',)
+    skip_attrs =  PrimaryView.skip_attrs + ()
+    
+    def render_entity_title(self, entity):
+        self.w('<h1>%s</h1>' % entity.dc_title())
+            
+    def render_entity_relations(self, entity, siderelations):
+        self.req.add_css('cubes.apycot.css')
+        self.display_info_section(entity)
+        self.display_log_section(entity)
+        if entity.reverse_during_execution:
+            self.w(u'<h3>%s</h3>' % self.req._('checks'))
+            self.w(u'<table class="summary">')
+            for check in entity.reverse_during_execution:
+                self.w(u'<tr><td><a href="%s#%s">%s</a></td><td class="status_%s">%s</td></tr>'
+                       % (self.req.url(), check.name, check.name, check.status, check.status))
+            self.w(u'</table>')
+            for check in entity.reverse_during_execution:
+                check.view('primary', w=self.w)
+
+
+class CheckResultPrimaryView(InfoLogMixin, PrimaryView):
+    accepts = ('CheckResult',)
+    skip_attrs =  PrimaryView.skip_attrs + ('name', 'status')
+
+    def render_entity_title(self, entity):
+        self.w('<a name="%s"/>' % entity.name)
+        self.w('<h4>%s [<span class="status_%s">%s</span>]</h4>'
+               % (entity.name, entity.status, entity.status))
+    
+    def render_entity_relations(self, entity, siderelations):
+        self.display_info_section(entity)
+        self.display_log_section(entity)
+
+        
+# class DetailedReporter(SimpleReporter):
+#     """detailed reporter, including tests configuration, log messages with
+#     severity greater than a given treshold and raw values
+#     """
+#     __implements__ = IReporter
+#     __name__ = 'detailed_report'
+#     default_id = 'detailed'
+        
+#     def __init__(self, name, transport, formatter, all_reporters, verbosity=0):
+#         SimpleReporter.__init__(self, name, transport, formatter,
+#                                 all_reporters, verbosity)
+#         self.section = None
+#         self.test_section = None
+#         self._tresh = ERROR
+#         self._raws = None
+#         self._logs = None
+#         self._test_path = self._test_repo = self._test_tag = None
+
+
+#     def node_section_title(self, node):
+#         """return a title for a section associated to a node"""
+#         label = SimpleReporter.node_section_title(self, node)
+#         statut = node.get('status')
+#         if statut is None:
+#             return label
+#         else:
+#             return '%s (%s)' % (label, statut) 
+
+#     def reset(self):
+#         """reset reporting variables"""
+#         self._tresh = self.get_treshold()
+
+#     def cb_open_testsdata(self, node):
+#         """open a testsdata node (ie the root) hook"""
+#         self.reset()
+#         self.section = Section(title=self.document_title(node), klass='result')
+#         self.navigation(self.section, node)
+#         self.field_info(self.section, node)
+        
+#     def cb_close_testsdata(self, node):
+#         """close a testsdata node (ie the root) hook"""
+#         self.navigation(self.section, node)
+#         summary = self.summary(self.section)
+#         if summary:
+#             self.section.insert(0, summary)
+#         self.post_layout(self.section, self.document_id(node))
+
+#     def cb_open_test(self, node):
+#         """open test node hook"""
+#         try:
+#             self._test_repo = get_repository(dict(node.attrib))
+#         except ConfigError:
+#             self._test_repo = None
+#         title = self.node_section_title(node)
+#         self.test_section = Section(title=title,
+#                                     id=self.relative_url(node),
+#                                     klass='test')
+#         self.test_section.node = node
+#         self.section.append(self.test_section)
+#         self.field_info(self.test_section, node)
+
+#     def cb_open_check(self, node):
+#         """open check node hook"""
+#         self._raws = []
+#         self._logs = []
+        
+#     def cb_close_check(self, node):
+#         """close check node hook"""
+#         title = self.node_section_title(node)
+#         check_section = Section(title=title,  id=self.relative_url(node),
+#                                 klass='check')
+#         check_section.node = node
+#         self.test_section.append(check_section)
+#         self.field_info(check_section, node)        
+#         if self._raws:
+#             table = Table(cols=2, klass="field", rheaders=1)
+#             for node in self._raws:
+#                 table.append(Text(node.get('class')))
+#                 table.append(Text(self.text_value(node)))
+#             check_section.append(table)            
+#         if self._logs:
+#             table = Table(cols=4, klass="result", rheaders=1, cheaders=1)
+#             table.append(Text('path'))
+#             table.append(Text('line'))
+#             table.append(Text('severity'))
+#             table.append(Text('message'))
+#             repo = self._test_repo
+#             for node in self._logs:
+#                 path = node.get('path', '')
+#                 vurl = repo and repo.view_url_for(path)
+#                 if vurl:
+#                     path = Link(vurl, path)
+#                 else:
+#                     path = Text(path)
+#                 table.append(Span([path], klass="path"))
+#                 table.append(Span([Text(node.get('line', ''))], klass="line"))
+#                 table.append(Span([Text(node.get('severity'))],
+#                                                             klass="severity"))
+#                 table.append(Span([VerbatimText(self.text_value(node).strip())],
+#                                   klass="msg"))
+#             check_section.append(table)
+
+#     def cb_open_log(self, node):
+#         """open log node hook"""
+#         severity = node.get('severity')
+#         if eval(severity) < self._tresh:
+#             return
+#         self._logs.append( (node) )
+
+#     def cb_open_raw(self, node):
+#         """open raw node hook"""
+#         self._raws.append( (node) )
+