changeset 116:86439c2aa6d6

Store report data in BDB XML database. Closes #31.
author cmlenz
date Sun, 07 Aug 2005 18:12:43 +0000
parents 16d69eb6e047
children 2f0f2f006526
files bitten/build/pythontools.py bitten/master.py bitten/store.py bitten/tests/__init__.py bitten/tests/store.py bitten/util/xmlio.py
diffstat 6 files changed, 226 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/bitten/build/pythontools.py
+++ b/bitten/build/pythontools.py
@@ -56,7 +56,7 @@
                          r'(?P<msg>.*)$')
     msg_types = dict(W='warning', E='error', C='convention', R='refactor')
 
-    lint_elem = xmlio.Element('lint')
+    problems = xmlio.Element('problems')
     try:
         fd = open(ctxt.resolve(file), 'r')
         try:
@@ -69,11 +69,11 @@
                         filename = filename[len(ctxt.basedir) + 1:]
                     lineno = int(match.group('line'))
                     tag = match.group('tag')
-                    xmlio.SubElement(lint_elem, 'problem', type=type, tag=tag,
+                    xmlio.SubElement(problems, 'problem', type=type, tag=tag,
                                      file=filename, line=lineno)[
                         match.group('msg') or ''
                     ]
-            ctxt.report(lint_elem)
+            ctxt.report(problems)
         finally:
             fd.close()
     except IOError, e:
--- a/bitten/master.py
+++ b/bitten/master.py
@@ -31,6 +31,7 @@
 
 from trac.env import Environment
 from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, BuildLog
+from bitten.store import ReportStore
 from bitten.util import archive, beep, xmlio
 
 log = logging.getLogger('bitten.master')
@@ -354,6 +355,10 @@
                                            message_elem.gettext()))
             build_log.insert(db=db)
 
+        store = ReportStore(self.env)
+        for report in elem.children('report'):
+            store.store_report(build, step, report)
+
     def _build_completed(self, db, build, elem):
         log.info('Slave %s completed build %d ("%s" as of [%s])', self.name,
                  build.id, build.config, build.rev)
new file mode 100644
--- /dev/null
+++ b/bitten/store.py
@@ -0,0 +1,138 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de>
+#
+# Bitten 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.
+#
+# Trac 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# Author: Christopher Lenz <cmlenz@gmx.de>
+
+import logging
+import os
+
+from trac.core import *
+
+log = logging.getLogger('bitten.store')
+
+
+class IReportStore(Interface):
+
+    def store_report(build, step, xml):
+        """Store the given report."""
+
+    def retrieve_reports(build, step, type=None):
+        """Retrieve reports."""
+
+
+class ReportStore(Component):
+
+    backends = ExtensionPoint(IReportStore)
+
+    def _get_backend(self):
+        configured = self.config.get('bitten', 'report_store', 'BDBXMLStore')
+        for backend in self.backends:
+            if backend.__class__.__name__ == configured:
+                return backend
+        raise TracError, 'No report store backend available'
+    backend = property(fget=lambda self: self._get_backend())
+
+    def store_report(self, build, step, xml):
+        assert xml.name == 'report' and 'type' in xml.attr
+        self.backend.store_report(build, step, xml)
+
+    def retrieve_reports(self, build, step, type=None):
+        return self.backend.retrieve_reports(build, step, type)
+
+
+try:
+    import dbxml
+except ImportError:
+    dbxml = None
+
+
+class BDBXMLStore(Component):
+    implements(IReportStore)
+
+    indexes = [
+        ('build', 'node-metadata-equality-decimal'),
+        ('step',  'node-metadata-equality-string'),
+        ('type',  'node-attribute-equality-string'),
+        ('file',  'node-attribute-equality-string'),
+        ('line',  'node-attribute-equality-decimal')
+    ]
+
+
+    class XmlValueWrapper(object):
+
+        _metadata = None
+
+        def __init__(self, value):
+            self.value = value
+
+        def __str__(self):
+            return self.value.asString()
+
+        def _get_metadata(self):
+            if self._metadata is None:
+                self._metadata = {}
+                for metadata in self.value.asDocument().getMetaDataIterator():
+                    if not metadata.get_uri():
+                        self._metadata[metadata.get_name()] = \
+                            metadata.get_value().asString()
+            return self._metadata
+        metadata = property(fget=lambda self: self._get_metadata())
+
+
+    def __init__(self):
+        self.path = os.path.join(self.env.path, 'db', 'bitten.dbxml')
+
+    def store_report(self, build, step, xml):
+        if dbxml is None:
+            log.warning('BDB XML not installed, cannot store report')
+            return
+        mgr = dbxml.XmlManager()
+        container = self._open_container(mgr, create=True)
+        ctxt = mgr.createUpdateContext()
+        doc = mgr.createDocument()
+        doc.setContent(str(xml))
+        doc.setMetaData('', 'build', dbxml.XmlValue(build.id))
+        doc.setMetaData('', 'step', dbxml.XmlValue(step.name))
+        container.putDocument(doc, ctxt, dbxml.DBXML_GEN_NAME)
+
+    def retrieve_reports(self, build, step, type=None):
+        if dbxml is None:
+            log.warning('BDB XML not installed, cannot retrieve reports')
+            return
+        path = os.path.join(self.env.path, 'db', 'bitten.dbxml')
+        mgr = dbxml.XmlManager()
+        container = self._open_container(mgr)
+        ctxt = mgr.createQueryContext()
+        query = "collection('%s')/report[dbxml:metadata('build')=%d and " \
+                                        "dbxml:metadata('step')='%s'" \
+                % (path, build.id, step.name)
+        if type is not None:
+            query += " and @type='%s'" % type
+        query += "]"
+        results = mgr.query(query, ctxt)
+        for value in results:
+            yield BDBXMLStore.XmlValueWrapper(value)
+
+    def _open_container(self, mgr, create=False):
+        if create and not os.path.exists(self.path):
+            container = mgr.createContainer(self.path)
+            ctxt = mgr.createUpdateContext()
+            for name, index in self.indexes:
+                container.addIndex('', name, index, ctxt)
+            return container
+        return mgr.openContainer(self.path)
--- a/bitten/tests/__init__.py
+++ b/bitten/tests/__init__.py
@@ -20,7 +20,7 @@
 
 import unittest
 
-from bitten.tests import model, recipe
+from bitten.tests import model, recipe, store
 from bitten.util import tests as util
 from bitten.trac_ext import tests as trac_ext
 
@@ -28,6 +28,7 @@
     suite = unittest.TestSuite()
     suite.addTest(model.suite())
     suite.addTest(recipe.suite())
+    suite.addTest(store.suite())
     suite.addTest(trac_ext.suite())
     suite.addTest(util.suite())
     return suite
new file mode 100644
--- /dev/null
+++ b/bitten/tests/store.py
@@ -0,0 +1,66 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de>
+#
+# Bitten 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.
+#
+# Trac 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# Author: Christopher Lenz <cmlenz@gmx.de>
+
+import os
+import shutil
+import tempfile
+import unittest
+
+from trac.test import EnvironmentStub, Mock
+from bitten.store import BDBXMLStore
+from bitten.util import xmlio
+
+
+class BDBXMLStoreTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.env = EnvironmentStub()
+        self.env.path = tempfile.mkdtemp('bitten-test')
+        os.mkdir(os.path.join(self.env.path, 'db'))
+
+    def tearDown(self):
+        shutil.rmtree(self.env.path)
+
+    def test_store_report(self):
+        store = BDBXMLStore(self.env)
+        build = Mock(id=42)
+        step = Mock(name='foo')
+        xml = xmlio.Element('report', type='test')[xmlio.Element('dummy')]
+        store.store_report(build, step, xml)
+
+        xml = xmlio.Element('report', type='lint')[xmlio.Element('dummy')]
+        store.store_report(build, step, xml)
+
+        reports = list(store.retrieve_reports(build, step))
+        self.assertEqual(2, len(reports))
+        self.assertEqual('42', reports[0].metadata['build'])
+        self.assertEqual('foo', reports[0].metadata['step'])
+        self.assertEqual('42', reports[1].metadata['build'])
+        self.assertEqual('foo', reports[1].metadata['step'])
+
+        reports = list(store.retrieve_reports(build, step, 'test'))
+        self.assertEqual(1, len(reports))
+
+        reports = list(store.retrieve_reports(build, step, 'lint'))
+        self.assertEqual(1, len(reports))
+
+
+def suite():
+    return unittest.makeSuite(BDBXMLStoreTestCase, 'test')
--- a/bitten/util/xmlio.py
+++ b/bitten/util/xmlio.py
@@ -207,6 +207,18 @@
     def gettext(self):
         return ''.join([c.nodeValue for c in self._node.childNodes])
 
+    def write(self, out, newlines=False):
+        """Serializes the element and writes the XML to the given output
+        stream.
+        """
+        self._node.writexml(out, newl=newlines and '\n' or '')
+
+    def __str__(self):
+        """Return a string representation of the XML element."""
+        buf = StringIO()
+        self.write(buf)
+        return buf.getvalue()
+
 
 if __name__ == '__main__':
     import doctest
Copyright (C) 2012-2017 Edgewall Software