changeset 196:b5f7d8a4035e

In preparation for supporting BDB XML transactions, move the report store backend selection away from using Trac extension points, and use a plain Python object instead of a component to represent a store backend.
author cmlenz
date Mon, 12 Sep 2005 21:59:14 +0000
parents bd6234ed6ac5
children d72c0587fae9
files bitten/master.py bitten/store.py bitten/tests/store.py bitten/trac_ext/charts.py bitten/trac_ext/summarizers.py bitten/trac_ext/web_ui.py bitten/upgrades.py
diffstat 7 files changed, 99 insertions(+), 128 deletions(-) [+]
line wrap: on
line diff
--- a/bitten/master.py
+++ b/bitten/master.py
@@ -20,7 +20,7 @@
 
 from trac.env import Environment
 from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, BuildLog
-from bitten.store import ReportStore
+from bitten.store import get_store
 from bitten.util import archive, beep, xmlio
 
 log = logging.getLogger('bitten.master')
@@ -109,7 +109,7 @@
     def _cleanup_orphaned_builds(self):
         # Reset all in-progress builds
         db = self.env.get_db_cnx()
-        store = ReportStore(self.env)
+        store = get_store(self.env)
         for build in Build.select(self.env, status=Build.IN_PROGRESS, db=db):
             build.status = Build.PENDING
             build.slave = None
@@ -120,7 +120,7 @@
             for build_log in BuildLog.select(self.env, build=build.id):
                 build_log.delete(db=db)
             build.update(db=db)
-            store.delete_reports(build=build)
+            store.delete(build=build)
         db.commit()
 
     def _cleanup_snapshots(self, when):
@@ -369,9 +369,9 @@
                                            message_elem.gettext()))
             build_log.insert(db=db)
 
-        store = ReportStore(self.env)
+        store = get_store(self.env)
         for report in elem.children('report'):
-            store.store_report(build, step, report)
+            store.store(build, step, report)
 
     def _build_completed(self, build, elem, timestamp_delta=None):
         log.info('Slave %s completed build %d ("%s" as of [%s]) with status %s',
@@ -392,8 +392,8 @@
         for step in BuildStep.select(self.env, build=build.id, db=db):
             step.delete(db=db)
 
-        store = ReportStore(self.env)
-        store.delete_reports(build=build)
+        store = get_store(self.env)
+        store.delete(build=build)
 
         build.slave = None
         build.started = 0
--- a/bitten/store.py
+++ b/bitten/store.py
@@ -13,48 +13,37 @@
 from trac.core import *
 from bitten.util import xmlio
 
-log = logging.getLogger('bitten.store')
-
-
-class IReportStoreBackend(Interface):
 
-    def store_report(build, step, xml):
-        """Store the given report."""
+class ReportStore(object):
 
-    def retrieve_reports(build, step=None, type=None):
-        """Retrieve reports."""
+    def delete(self, config=None, build=None, step=None, type=None):
+        raise NotImplementedError
+
+    def query(self, xquery, config=None, build=None, step=None,
+                     type=None):
+        raise NotImplementedError
+
+    def retrieve(self, build, step=None, type=None):
+        raise NotImplementedError
+
+    def store(self, build, step, xml):
+        raise NotImplementedError
 
 
-class ReportStore(Component):
-
-    backends = ExtensionPoint(IReportStoreBackend)
-
-    def delete_reports(self, config=None, build=None, step=None, type=None):
-        backend = self._get_configured_backend()
-        return backend.delete_reports(config, build, step, type)
-
-    def query_reports(self, xquery, config=None, build=None, step=None,
-                     type=None):
-        backend = self._get_configured_backend()
-        return backend.query_reports(xquery, config, build, step, type)
+class NullReportStore(ReportStore):
 
-    def retrieve_reports(self, build, step=None, type=None):
-        backend = self._get_configured_backend()
-        return backend.retrieve_reports(build, step, type)
+    def delete(self, config=None, build=None, step=None, type=None):
+        return
 
-    def store_report(self, build, step, xml):
-        assert xml.name == 'report' and 'type' in xml.attr
-        backend = self._get_configured_backend()
-        log.debug('Storing report of type "%s" in %s', xml.attr['type'],
-                  backend.__class__.__name__)
-        backend.store_report(build, step, xml)
+    def query(self, xquery, config=None, build=None, step=None,
+                     type=None):
+        return []
 
-    def _get_configured_backend(self):
-        configured = self.config.get('bitten', 'report_store', 'BDBXMLBackend')
-        for backend in self.backends:
-            if backend.__class__.__name__ == configured:
-                return backend
-        raise TracError, 'Report store backend not available'
+    def retrieve(self, build, step=None, type=None):
+        return []
+
+    def store(self, build, step, xml):
+        return
 
 
 try:
@@ -63,10 +52,9 @@
     dbxml = None
 
 
-class BDBXMLBackend(Component):
-    implements(IReportStoreBackend)
+class BDBXMLReportStore(ReportStore):
 
-    indexes = [
+    indices = [
         ('config', 'node-metadata-equality-string'),
         ('build', 'node-metadata-equality-decimal'),
         ('step',  'node-metadata-equality-string'),
@@ -111,42 +99,36 @@
             return self._value.asString()
 
 
-    def __init__(self):
-        self.path = os.path.join(self.env.path, 'db', 'bitten.dbxml')
+    def __init__(self, path):
+        self.path = path
+        self.mgr = dbxml.XmlManager()
+        if not os.path.exists(path):
+            self.container = self.mgr.createContainer(self.path)
+            ctxt = self.mgr.createUpdateContext()
+            for name, index in self.indices:
+                self.container.addIndex('', name, index, ctxt)
+        else:
+            self.container = self.mgr.openContainer(self.path)
 
-    def delete_reports(self, config=None, build=None, step=None, type=None):
-        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()
-        for elem in self.query_reports('return $reports', config=config,
-                                       build=build, step=step, type=type):
-            container.deleteDocument(elem._value.asDocument(), ctxt)
+    def delete(self, config=None, build=None, step=None, type=None):
+        ctxt = self.mgr.createUpdateContext()
+        for elem in self.query('return $reports', config=config, build=build,
+                               step=step, type=type):
+            self.container.deleteDocument(elem._value.asDocument(), ctxt)
 
-    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()
+    def store(self, build, step, xml):
+        assert xml.name == 'report' and 'type' in xml.attr
+        ctxt = self.mgr.createUpdateContext()
+        doc = self.mgr.createDocument()
         doc.setContent(str(xml))
         doc.setMetaData('', 'config', dbxml.XmlValue(build.config))
         doc.setMetaData('', 'build', dbxml.XmlValue(build.id))
         doc.setMetaData('', 'step', dbxml.XmlValue(step.name))
-        container.putDocument(doc, ctxt, dbxml.DBXML_GEN_NAME)
+        self.container.putDocument(doc, ctxt, dbxml.DBXML_GEN_NAME)
 
-    def query_reports(self, xquery, config=None, build=None, step=None,
+    def query(self, xquery, config=None, build=None, step=None,
                      type=None):
-        if dbxml is None:
-            log.warning('BDB XML not installed, cannot query reports')
-            return
-        mgr = dbxml.XmlManager()
-        container = self._open_container(mgr)
-        ctxt = mgr.createQueryContext()
+        ctxt = self.mgr.createQueryContext()
 
         constraints = []
         if config:
@@ -162,21 +144,16 @@
         if constraints:
             query += '[%s]' % ' and '.join(constraints)
         query += '\n' + xquery
-        self.log.debug('Executíng XQuery: %s', query)
-
-        results = mgr.query(query, ctxt)
-        for value in results:
-            yield BDBXMLBackend.XmlValueAdapter(value)
 
-    def retrieve_reports(self, build, step=None, type=None):
-        return self.query_reports('return $reports', build=build, step=step,
-                                  type=type)
+        results = self.mgr.query(query, ctxt)
+        for value in results:
+            yield BDBXMLReportStore.XmlValueAdapter(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)
+    def retrieve(self, build, step=None, type=None):
+        return self.query('return $reports', build=build, step=step, type=type)
+
+
+def get_store(env):
+    if dbxml is None:
+        return NullReportStore()
+    return BDBXMLReportStore(os.path.join(env.path, 'db', 'bitten.dbxml'))
--- a/bitten/tests/store.py
+++ b/bitten/tests/store.py
@@ -14,20 +14,19 @@
 import unittest
 
 from trac.test import EnvironmentStub, Mock
-from bitten.store import BDBXMLBackend
+from bitten.store import BDBXMLReportStore
 from bitten.util import xmlio
 
 
-class BDBXMLBackendTestCase(unittest.TestCase):
+class BDBXMLReportStoreTestCase(unittest.TestCase):
 
     def setUp(self):
-        self.env = EnvironmentStub()
-        self.env.path = tempfile.mkdtemp('bitten-test')
-        os.mkdir(os.path.join(self.env.path, 'db'))
-        self.store = BDBXMLBackend(self.env)
+        self.path = os.path.join(tempfile.gettempdir(), 'bitten_test.dbxml')
+        self.store = BDBXMLReportStore(self.path)
 
     def tearDown(self):
-        shutil.rmtree(self.env.path)
+        self.store = None
+        os.unlink(self.path)
 
     def test_store_report(self):
         """
@@ -36,10 +35,9 @@
         build = Mock(id=42, config='trunk')
         step = Mock(name='foo')
         xml = xmlio.Element('report', type='test')[xmlio.Element('dummy')]
-        self.store.store_report(build, step, xml)
+        self.store.store(build, step, xml)
 
-        self.assertEqual(1, len(list(self.store.retrieve_reports(build, step,
-                                                                 'test'))))
+        self.assertEqual(1, len(list(self.store.retrieve(build, step, 'test'))))
 
     def test_retrieve_reports_for_step(self):
         """
@@ -49,15 +47,15 @@
         build = Mock(id=42, config='trunk')
         step = Mock(name='foo')
         xml = xmlio.Element('report', type='test')[xmlio.Element('dummy')]
-        self.store.store_report(build, step, xml)
+        self.store.store(build, step, xml)
         xml = xmlio.Element('report', type='lint')[xmlio.Element('dummy')]
-        self.store.store_report(build, step, xml)
+        self.store.store(build, step, xml)
 
         other_step = Mock(name='bar')
         xml = xmlio.Element('report', type='test')[xmlio.Element('dummy')]
-        self.store.store_report(build, other_step, xml)
+        self.store.store(build, other_step, xml)
 
-        self.assertEqual(2, len(list(self.store.retrieve_reports(build, step))))
+        self.assertEqual(2, len(list(self.store.retrieve(build, step))))
 
     def test_retrieve_reports_for_build(self):
         """
@@ -68,23 +66,23 @@
         step_foo = Mock(name='foo')
         step_bar = Mock(name='bar')
         xml = xmlio.Element('report', type='test')[xmlio.Element('dummy')]
-        self.store.store_report(build, step_foo, xml)
+        self.store.store(build, step_foo, xml)
         xml = xmlio.Element('report', type='lint')[xmlio.Element('dummy')]
-        self.store.store_report(build, step_bar, xml)
+        self.store.store(build, step_bar, xml)
 
         other_build = Mock(id=66, config='trunk')
         step_baz = Mock(name='foo')
         xml = xmlio.Element('report', type='test')[xmlio.Element('dummy')]
-        self.store.store_report(other_build, step_baz, xml)
+        self.store.store(other_build, step_baz, xml)
 
-        self.assertEqual(2, len(list(self.store.retrieve_reports(build))))
+        self.assertEqual(2, len(list(self.store.retrieve(build))))
 
 
 def suite():
     suite = unittest.TestSuite()
     try:
         import dbxml
-        suite.addTest(unittest.makeSuite(BDBXMLBackendTestCase, 'test'))
+        suite.addTest(unittest.makeSuite(BDBXMLReportStoreTestCase, 'test'))
     except ImportError:
         print>>sys.stderr, 'Skipping unit tests for BDB XML backend'
     return suite
--- a/bitten/trac_ext/charts.py
+++ b/bitten/trac_ext/charts.py
@@ -13,7 +13,7 @@
 from trac.core import *
 from trac.web import IRequestHandler
 from bitten.model import BuildConfig, Build
-from bitten.store import ReportStore
+from bitten.store import get_store
 from bitten.trac_ext.api import IReportChartGenerator
 from bitten.util import xmlio
 
@@ -63,7 +63,7 @@
             rev_map[str(build.id)] = (build.rev,
                                       datetime.fromtimestamp(build.rev_time))
 
-        store = ReportStore(self.env)
+        store = get_store(self.env)
         xquery = """
 for $report in $reports
 return
@@ -78,7 +78,7 @@
         #        code
 
         tests = {} # Accumulated test numbers by revision
-        for test in store.query_reports(xquery, config=config, type='unittest'):
+        for test in store.query(xquery, config=config, type='unittest'):
             rev, rev_time = rev_map.get(test.attr['build'])
             if rev not in tests:
                 tests[rev] = [rev_time, 0, 0]
@@ -115,7 +115,7 @@
             rev_map[str(build.id)] = (build.rev,
                                       datetime.fromtimestamp(build.rev_time))
 
-        store = ReportStore(self.env)
+        store = get_store(self.env)
         xquery = """
 for $report in $reports
 return
@@ -134,7 +134,7 @@
         #        the Python code
 
         coverage = {} # Accumulated coverage info by revision
-        for test in store.query_reports(xquery, config=config, type='trace'):
+        for test in store.query(xquery, config=config, type='trace'):
             rev, rev_time = rev_map.get(test.attr['build'])
             if rev not in coverage:
                 coverage[rev] = [rev_time, 0, 0]
--- a/bitten/trac_ext/summarizers.py
+++ b/bitten/trac_ext/summarizers.py
@@ -12,7 +12,7 @@
 from trac.web.chrome import Chrome
 from trac.web.clearsilver import HDFWrapper
 from bitten.model import BuildConfig
-from bitten.store import ReportStore
+from bitten.store import get_store
 from bitten.trac_ext.api import IReportSummarizer
 
 
@@ -30,9 +30,9 @@
     def render_report_summary(self, req, build, step, report):
         hdf = HDFWrapper(loadpaths=Chrome(self.env).get_all_templates_dirs())
         config = BuildConfig.fetch(self.env, name=build.config)
-        store = ReportStore(self.env)
-        results = store.query_reports(self.query, config=config, build=build,
-                                      step=step, type=self.report_type)
+        store = get_store(self.env)
+        results = store.query(self.query, config=config, build=build, step=step,
+                              type=self.report_type)
         for idx, elem in enumerate(results):
             data = {}
             for name, value in elem.attr.items():
--- a/bitten/trac_ext/web_ui.py
+++ b/bitten/trac_ext/web_ui.py
@@ -19,7 +19,7 @@
                             add_link, add_stylesheet
 from trac.wiki import wiki_to_html
 from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, BuildLog
-from bitten.store import ReportStore
+from bitten.store import get_store
 from bitten.trac_ext.api import ILogFormatter, IReportSummarizer
 
 _status_label = {Build.IN_PROGRESS: 'in progress',
@@ -494,8 +494,8 @@
         for step in BuildStep.select(self.env, build=build.id, db=db):
             step.delete(db=db)
 
-        store = ReportStore(self.env)
-        store.delete_reports(build=build)
+        store = get_store(self.env)
+        store.delete(build=build)
 
         build.slave = None
         build.started = build.stopped = 0
@@ -526,9 +526,9 @@
             types = summarizer.get_supported_report_types()
             summarizers.update(dict([(type, summarizer) for type in types]))
 
-        store = ReportStore(self.env)
+        store = get_store(self.env)
         reports = []
-        for report in store.retrieve_reports(build, step):
+        for report in store.retrieve(build, step):
             report_type = report.attr['type']
             summarizer = summarizers.get(report_type)
             if summarizer:
@@ -622,9 +622,9 @@
         req.hdf['build'] = {'id': build.id,
                             'href': self.env.href.build(build.config, build.id)}
 
-        store = ReportStore(self.env)
+        store = get_store(self.env)
         reports = []
-        for report in store.retrieve_reports(build, step, report_type):
+        for report in store.retrieve(build, step, report_type):
             req.hdf['title'] = 'Build %d: %s' % (build.id, report_type)
             xml = report._node.toprettyxml('  ')
             if req.args.get('format') == 'xml':
--- a/bitten/upgrades.py
+++ b/bitten/upgrades.py
@@ -48,10 +48,6 @@
                    "NULL,label,description FROM old_config")
 
 def add_config_to_reports(env, db):
-    backend = env.config.get('bitten', 'report_store', 'BDBXMLBackend')
-    if backend != 'BDBXMLBackend':
-        return
-
     from bitten.model import Build
     try:
         import dbxml
Copyright (C) 2012-2017 Edgewall Software