changeset 626:73ed8c171063

Simplify email notification code by removing BuildInfo class
author mgood
date Sun, 09 Aug 2009 08:47:19 +0000
parents 98675686ec4d
children 0a9f2af1f711
files bitten/notify.py bitten/templates/bitten_notify_email.txt bitten/tests/notify.py
diffstat 3 files changed, 93 insertions(+), 157 deletions(-) [+]
line wrap: on
line diff
--- a/bitten/notify.py
+++ b/bitten/notify.py
@@ -30,14 +30,14 @@
         self.log.debug('Initializing BittenNotify plugin')
 
     def notify(self, build=None):
-        self.log.info('BittenNotify invoked for build %r' % build)
-        self.log.debug('build status: %s' % build.status)
+        self.log.info('BittenNotify invoked for build %r', build)
+        self.log.debug('build status: %s', build.status)
         if not self._should_notify(build):
             return
-        self.log.info('Sending notification for build %r' % build)
+        self.log.info('Sending notification for build %r', build)
         try:
-            email = BittenNotifyEmail(self.env)
-            email.notify(BuildInfo(self.env, build))
+            email = BuildNotifyEmail(self.env)
+            email.notify(build)
         except Exception, e:
             self.log.exception("Failure sending notification for build "
                                "%s: %s", build.id, e)
@@ -78,99 +78,77 @@
         return []
 
 
-class BuildInfo(dict):
-    """Wraps a Build instance and exposes properties conveniently"""
-
-    readable_states = {Build.SUCCESS:'Successful', Build.FAILURE:'Failed'}
-
-    def __init__(self, env, build):
-        dict.__init__(self)
-        self.build = build
-        self.env = env
-        self['project_name'] = self.env.project_name
-        self['id'] = self.build.id
-        self['status'] = self.readable_states[self.build.status]
-        self['link'] = self.env.abs_href.build(self.build.config,
-                self.build.id)
-        self['config'] = self.build.config
-        self['slave'] = self.build.slave
-        self['changeset'] = self.build.rev
-        self['changesetlink'] = self.env.abs_href.changeset(self.build.rev)
-        self['author'] = self.get_author(build)
-        self['errors'] = self.get_errors(build)
-        self['faillog'] = self.get_faillog(build)
-
-    def get_author(self, build):
-        if build and build.rev:
-            changeset = self.env.get_repository().get_changeset(build.rev)
-            return changeset.author
-
-    def get_failed_steps(self, build):
-        build_steps = BuildStep.select(self.env,
-                build=build.id,
-                status=BuildStep.FAILURE)
-        return build_steps
-
-    def get_errors(self, build):
-        errors = ''
-        for step in self.get_failed_steps(build):
-            errors += ', '.join(['%s: %s' % (step.name, error) \
-                    for error in step.errors])
-        return errors
-
-    def get_faillog(self, build):
-        faillog = ''
-        for step in self.get_failed_steps(build):
-            build_logs = BuildLog.select(self.env,
-                    build=build.id,
-                    step=step.name)
-            for log in build_logs:
-                faillog += '\n'.join(['%5s: %s' % (level, msg) \
-                        for level, msg in log.messages])
-        return faillog
-
-    def __getattr__(self, attr):
-        return dict.__getitem__(self,attr)
-
-    def __repr__(self):
-        repr = ''
-        for k, v in self.items():
-            repr += '%s: %s\n' % (k, v)
-        return repr
-
-    def __str__(self):
-        return self.repr()
-
-
-class BittenNotifyEmail(NotifyEmail):
+class BuildNotifyEmail(NotifyEmail):
     """Notification of failed builds."""
 
+    readable_states = {
+        Build.SUCCESS: 'Successful',
+        Build.FAILURE: 'Failed',
+    }
     template_name = 'bitten_notify_email.txt'
     from_email = 'bitten@localhost'
 
     def __init__(self, env):
         NotifyEmail.__init__(self, env)
 
-    def notify(self, build_info):
-        self.build_info = build_info
-        self.data = self.build_info
-        subject = '[%s Build] %s [%s] %s' % (self.build_info.status,
-                self.env.project_name,
-                self.build_info.changeset,
-                self.build_info.config)
-        stream = self.template.generate(**self.data)
-        body = stream.render('text')
-        self.env.log.debug('notification: %s' % body )
-        NotifyEmail.notify(self, self.build_info.id, subject)
+    def notify(self, build):
+        self.build = build
+        self.data.update(self.template_data())
+        subject = '[%s Build] %s [%s] %s' % (self.readable_states[build.status],
+                                             self.env.project_name,
+                                             self.build.rev,
+                                             self.build.config)
+        NotifyEmail.notify(self, self.build.id, subject)
 
     def get_recipients(self, resid):
-        to = [self.build_info.author]
+        to = [self.get_author()]
         cc = []
         return (to, cc)
 
-    def send(self, torcpts, ccrcpts, mime_headers={}):
+    def send(self, torcpts, ccrcpts):
         mime_headers = {
-            'X-Trac-Build-ID': str(self.build_info.id),
-            'X-Trac-Build-URL': self.build_info.link,
+            'X-Trac-Build-ID': str(self.build.id),
+            'X-Trac-Build-URL': self.build_link(),
         }
         NotifyEmail.send(self, torcpts, ccrcpts, mime_headers)
+
+    def build_link(self):
+        return self.env.abs_href.build(self.build.config, self.build.id)
+
+    def template_data(self):
+        failed_steps = BuildStep.select(self.env, build=self.build.id,
+                                        status=BuildStep.FAILURE)
+        change = self.get_changeset()
+        return {
+            'build': {
+                'id': self.build.id,
+                'status': self.readable_states[self.build.status],
+                'link': self.build_link(),
+                'config': self.build.config,
+                'slave': self.build.slave,
+                'failed_steps': [{
+                    'name': step.name,
+                    'description': step.description,
+                    'errors': step.errors,
+                    'log_messages': self.get_all_log_messages_for_step(step),
+                } for step in failed_steps],
+            },
+            'change': {
+                'rev': change.rev,
+                'link': self.env.abs_href.changeset(change.rev),
+                'author': change.author,
+            },
+        }
+
+    def get_all_log_messages_for_step(self, step):
+        messages = []
+        for log in BuildLog.select(self.env, build=self.build.id,
+                                   step=step.name):
+            messages.extend(log.messages)
+        return messages
+
+    def get_changeset(self):
+        return self.env.get_repository().get_changeset(self.build.rev)
+
+    def get_author(self):
+        return self.get_changeset().author
--- a/bitten/templates/bitten_notify_email.txt
+++ b/bitten/templates/bitten_notify_email.txt
@@ -1,15 +1,26 @@
-${status} build of ${project_name} [${changeset}]
+$build.status build of $project.name [$change.rev]
 ---------------------------------------------------------------------
 
-  Changeset:             ${changeset}  -  <${changesetlink}>
-  Committed by:          ${author}
+  Changeset:             $change.rev  -  <$change.link>
+  Committed by:          $change.author
 
-  Build Configuration:   ${config}
-  Build Slave:           ${slave}
-  Build Number:          ${id}  -  <${link}>
+  Build Configuration:   $build.config
+  Build Slave:           $build.slave
+  Build Number:          $build.id  -  <${build.link}>
+#if build.failed_steps
 
-  Failed Steps:          ${errors}
-  Failure Log:
+  Failures:
+#for step in build.failed_steps
+    Step:                $step.name
+    Errors:              ${', '.join(step.errors)}
+    Log:
+      #for lvl, msg in step.log_messages
+      [${lvl.upper().ljust(8)}] $msg
+      #end
+#end
+#end
 
-${faillog}
-
+--
+Build URL: <$build.link>
+$project.name <${project.url or abs_href()}>
+$project.descr
--- a/bitten/tests/notify.py
+++ b/bitten/tests/notify.py
@@ -12,13 +12,13 @@
 from trac.test import EnvironmentStub, Mock
 from trac.web.session import DetachedSession
 from bitten.model import schema, Build, BuildStep, BuildLog
-from bitten.notify import BittenNotify, BittenNotifyEmail, BuildInfo
+from bitten.notify import BittenNotify, BuildNotifyEmail
 
 
 class BittenNotifyBaseTest(unittest.TestCase):
     def setUp(self):
         self.env = EnvironmentStub(enable=['trac.*', 'bitten.notify.*'])
-        repos = Mock(get_changeset=lambda rev: Mock(author='author'))
+        repos = Mock(get_changeset=lambda rev: Mock(author='author', rev=rev))
         self.env.get_repository = lambda authname=None: repos
 
         db = self.env.get_db_cnx()
@@ -62,52 +62,7 @@
         self.env.config.set(option.section, option.name, value)
 
 
-class BuildInfoTest(BittenNotifyBaseTest):
-    """unit tests for BuildInfo class"""
-
-    def setUp(self):
-        BittenNotifyBaseTest.setUp(self)
-        #fixture
-        self.failed_build = Build(self.env,
-                config = 'config',
-                slave = 'slave',
-                rev = 10,
-                status = Build.FAILURE)
-        self.failed_build.id = 1
-        self.successful_build = Build(self.env, status = Build.SUCCESS)
-        self.successful_build.id = 2
-        step = BuildStep(self.env,
-                build = 1,
-                name = 'test',
-                status = BuildStep.FAILURE)
-        step.errors = ['msg']
-        step.insert()
-        log = BuildLog(self.env, build = 1, step = 'test')
-        log.messages = [('info','msg')]
-        log.insert()
-
-    def test_exposed_properties(self):
-        build_info = BuildInfo(self.env, self.failed_build)
-        self.assertEquals(self.failed_build.id, build_info.id)
-        self.assertEquals('Failed', build_info.status)
-        self.assertEquals('http://example.org/trac.cgi/build/config/1',
-                build_info.link)
-        self.assertEquals('config', build_info.config)
-        self.assertEquals('slave', build_info.slave)
-        self.assertEquals('10', build_info.changeset)
-        self.assertEquals('http://example.org/trac.cgi/changeset/10',
-                build_info.changesetlink)
-        self.assertEquals('author', build_info.author)
-        self.assertEquals('test: msg', build_info.errors)
-        self.assertEquals(' info: msg', build_info.faillog)
-
-    def test_exposed_properties_on_successful_build(self):
-        build_info = BuildInfo(self.env, self.successful_build)
-        self.assertEquals(self.successful_build.id, build_info.id)
-        self.assertEquals('Successful', build_info.status)
-
-
-class BittenNotifyEmailTest(BittenNotifyBaseTest):
+class BuildNotifyEmailTest(BittenNotifyBaseTest):
     """unit tests for BittenNotifyEmail class"""
     def setUp(self):
         BittenNotifyBaseTest.setUp(self)
@@ -117,32 +72,24 @@
             self.notifications_sent_to = to
         def noop(*args, **kw):
             pass
-        self.email = Mock(BittenNotifyEmail, self.env,
+        self.email = Mock(BuildNotifyEmail, self.env,
                           begin_send=noop,
                           finish_send=noop,
                           send=send)
-        self.build_info = BuildInfo(self.env,
-                Build(self.env, status=Build.SUCCESS, rev=123))
+        self.build = Build(self.env, status=Build.SUCCESS, rev=123)
 
     def test_notification_is_sent_to_author(self):
-        self.email.notify(self.build_info)
+        self.email.notify(self.build)
         self.assertTrue('author' in self.notifications_sent_to,
                 'Recipient list should contain the author')
 
-    def add_known_user(self, username, name, email):
-        session = DetachedSession(self.env, username)
-        if name is not None:
-            session['name'] = name
-        if email is not None:
-            session['email'] = email
-        session.save()
+    # TODO functional tests of generated mails
 
 
 def suite():
     suite = unittest.TestSuite()
-    suite.addTest(unittest.makeSuite(BittenNotifyTest,'test'))
-    suite.addTest(unittest.makeSuite(BuildInfoTest,'test'))
-    suite.addTest(unittest.makeSuite(BittenNotifyEmailTest,'test'))
+    suite.addTest(unittest.makeSuite(BittenNotifyTest, 'test'))
+    suite.addTest(unittest.makeSuite(BuildNotifyEmailTest, 'test'))
     return suite
 
 if __name__ == '__main__':
Copyright (C) 2012-2017 Edgewall Software