← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~lifeless/launchpad/useoops into lp:launchpad

 

Robert Collins has proposed merging lp:~lifeless/launchpad/useoops into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~lifeless/launchpad/useoops/+merge/71139

This branch migrates use to use the python-oops write method rather than one inside LP itself.

As a result of this, fresh in-memory OOPSes (vs read-from-disk ones) for LP also become python-oops oops dicts, and all the tests that were poking at in-memory OOPSes change to using the dict access syntax.

I've also tweaked the test_testcase test that was comparing the serialised forms: it did that because the LP internal parser wasn't setting the tzinfo correctly, python-oops does not have that bug, and so comparing the dicts works - but the getLastOops function is not migrated yet so I do a small hack there.

Other than that this should be totally noncontroversial :).
-- 
https://code.launchpad.net/~lifeless/launchpad/useoops/+merge/71139
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~lifeless/launchpad/useoops into lp:launchpad.
=== modified file 'lib/canonical/launchpad/webapp/errorlog.py'
--- lib/canonical/launchpad/webapp/errorlog.py	2011-08-10 06:50:52 +0000
+++ lib/canonical/launchpad/webapp/errorlog.py	2011-08-11 03:21:40 +0000
@@ -138,7 +138,8 @@
     implements(IErrorReport)
 
     def __init__(self, id, type, value, time, pageid, tb_text, username,
-                 url, duration, req_vars, db_statements, informational):
+                 url, duration, req_vars, db_statements, informational,
+                 branch_nick=None, revno=None):
         self.id = id
         self.type = type
         self.value = value
@@ -150,45 +151,13 @@
         self.duration = duration
         self.req_vars = req_vars
         self.db_statements = db_statements
-        self.branch_nick = versioninfo.branch_nick
-        self.revno = versioninfo.revno
+        self.branch_nick = branch_nick or versioninfo.branch_nick
+        self.revno = revno or versioninfo.revno
         self.informational = informational
 
     def __repr__(self):
         return '<ErrorReport %s %s: %s>' % (self.id, self.type, self.value)
 
-    def get_chunks(self):
-        """Returns a list of bytestrings making up the oops disk content."""
-        chunks = []
-        chunks.append('Oops-Id: %s\n' % _normalise_whitespace(self.id))
-        chunks.append(
-            'Exception-Type: %s\n' % _normalise_whitespace(self.type))
-        chunks.append(
-            'Exception-Value: %s\n' % _normalise_whitespace(self.value))
-        chunks.append('Date: %s\n' % self.time.isoformat())
-        chunks.append('Page-Id: %s\n' % _normalise_whitespace(self.pageid))
-        chunks.append('Branch: %s\n' % _safestr(self.branch_nick))
-        chunks.append('Revision: %s\n' % self.revno)
-        chunks.append('User: %s\n' % _normalise_whitespace(self.username))
-        chunks.append('URL: %s\n' % _normalise_whitespace(self.url))
-        chunks.append('Duration: %s\n' % self.duration)
-        chunks.append('Informational: %s\n' % self.informational)
-        chunks.append('\n')
-        safe_chars = ';/\\?:@&+$, ()*!'
-        for key, value in self.req_vars:
-            chunks.append('%s=%s\n' % (urllib.quote(key, safe_chars),
-                                  urllib.quote(value, safe_chars)))
-        chunks.append('\n')
-        for (start, end, database_id, statement) in self.db_statements:
-            chunks.append('%05d-%05d@%s %s\n' % (
-                start, end, database_id, _normalise_whitespace(statement)))
-        chunks.append('\n')
-        chunks.append(self.tb_text)
-        return chunks
-
-    def write(self, fp):
-        fp.writelines(self.get_chunks())
-
     @classmethod
     def read(cls, fp):
         # Deprecated: use the oops module directly now, when possible.
@@ -319,22 +288,21 @@
 
     def _raising(self, info, request=None, now=None, informational=False):
         """Private method used by raising() and handling()."""
-        entry = self._makeErrorReport(info, request, now, informational)
+        entry, filename = self._makeErrorReport(info, request, now, informational)
         if entry is None:
             return
-        filename = entry._filename
-        entry.write(open(filename, 'wb'))
+        oops.serializer_rfc822.write(entry, open(filename, 'wb'))
         # Set file permission to: rw-r--r--
         wanted_permission = (
             stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
         os.chmod(filename, wanted_permission)
         if request:
-            request.oopsid = entry.id
+            request.oopsid = entry['id']
             request.oops = entry
 
         if self.copy_to_zlog:
             self._do_copy_to_zlog(
-                entry.time, entry.type, entry.url, info, entry.id)
+                entry['time'], entry['type'], entry['url'], info, entry['id'])
         notify(ErrorReportEvent(entry))
         return entry
 
@@ -387,7 +355,7 @@
 
         strtype = str(getattr(info[0], '__name__', info[0]))
         if self._isIgnoredException(strtype, request, info[1]):
-            return
+            return None, None
 
         if not isinstance(info[2], basestring):
             tb_text = ''.join(format_exception(*info,
@@ -413,7 +381,7 @@
                 if webservice_error / 100 != 5:
                     request.oopsid = None
                     # Return so the OOPS is not generated.
-                    return
+                    return None, None
 
             missing = object()
             principal = getattr(request, 'principal', missing)
@@ -427,7 +395,7 @@
                 login = 'unauthenticated'
                 if strtype in (
                     self._ignored_exceptions_for_unauthenticated_users):
-                    return
+                    return None, None
 
             if principal is not None and principal is not missing:
                 username = _safestr(
@@ -473,12 +441,23 @@
 
         oopsid, filename = self.log_namer.newId(now)
 
-        result = ErrorReport(oopsid, strtype, strv, now, pageid, tb_text,
-                           username, strurl, duration,
-                           req_vars, statements,
-                           informational)
-        result._filename = filename
-        return result
+        result = {
+            'id': oopsid,
+            'type': strtype,
+            'value': strv,
+            'time': now,
+            'pageid': pageid,
+            'tb_text': tb_text,
+            'username': username,
+            'url': strurl,
+            'duration': duration,
+            'req_vars': req_vars,
+            'db_statements': statements,
+            'informational': informational,
+            'branch_nick': versioninfo.branch_nick,
+            'revno': versioninfo.revno,
+            }
+        return result, filename
 
     def handling(self, info, request=None, now=None):
         """Flag ErrorReport as informational only.

=== modified file 'lib/canonical/launchpad/webapp/tests/test_errorlog.py'
--- lib/canonical/launchpad/webapp/tests/test_errorlog.py	2011-08-10 06:50:52 +0000
+++ lib/canonical/launchpad/webapp/tests/test_errorlog.py	2011-08-11 03:21:40 +0000
@@ -102,43 +102,6 @@
             entry.db_statements[1],
             (5, 10, 'store_b', 'SELECT 2'))
 
-    def test_write(self):
-        """Test ErrorReport.write()"""
-        entry = ErrorReport('OOPS-A0001', 'NotFound', 'error message',
-                            datetime.datetime(2005, 04, 01, 00, 00, 00,
-                                              tzinfo=UTC),
-                            'IFoo:+foo-template',
-                            'traceback-text', 'Sample User',
-                            'http://localhost:9000/foo', 42,
-                            [('HTTP_USER_AGENT', 'Mozilla/5.0'),
-                             ('HTTP_REFERER', 'http://localhost:9000/'),
-                             ('name=foo', 'hello\nworld')],
-                            [(1, 5, 'store_a', 'SELECT 1'),
-                             (5, 10, 'store_b', 'SELECT\n2')], False)
-        fp = StringIO.StringIO()
-        entry.write(fp)
-        self.assertEqual(fp.getvalue(), dedent("""\
-            Oops-Id: OOPS-A0001
-            Exception-Type: NotFound
-            Exception-Value: error message
-            Date: 2005-04-01T00:00:00+00:00
-            Page-Id: IFoo:+foo-template
-            Branch: %s
-            Revision: %s
-            User: Sample User
-            URL: http://localhost:9000/foo
-            Duration: 42
-            Informational: False
-
-            HTTP_USER_AGENT=Mozilla/5.0
-            HTTP_REFERER=http://localhost:9000/
-            name%%3Dfoo=hello%%0Aworld
-
-            00001-00005@store_a SELECT 1
-            00005-00010@store_b SELECT 2
-
-            traceback-text""" % (versioninfo.branch_nick, versioninfo.revno)))
-
     def test_read(self):
         """Test ErrorReport.read()."""
         # Note: this exists to test the compatibility thunk only.
@@ -379,7 +342,7 @@
 
         # verify that the oopsid was set on the request
         self.assertEqual(request.oopsid, 'OOPS-91T1')
-        self.assertEqual(request.oops.id, 'OOPS-91T1')
+        self.assertEqual(request.oops['id'], 'OOPS-91T1')
 
     def test_raising_with_xmlrpc_request(self):
         # Test ErrorReportingUtility.raising() with an XML-RPC request.
@@ -823,10 +786,10 @@
                 raise ArbitraryException('foo')
             except ArbitraryException:
                 info = sys.exc_info()
-                oops = utility._makeErrorReport(info)
+                oops, _ = utility._makeErrorReport(info)
                 self.assertEqual(
                     [('<oops-message-0>', "{'a': 'b', 'c': 'd'}")],
-                    oops.req_vars)
+                    oops['req_vars'])
 
     def test__makeErrorReport_combines_request_and_error_vars(self):
         """The oops messages should be distinct from real request vars."""
@@ -837,10 +800,10 @@
                 raise ArbitraryException('foo')
             except ArbitraryException:
                 info = sys.exc_info()
-                oops = utility._makeErrorReport(info, request)
+                oops, _ = utility._makeErrorReport(info, request)
                 self.assertEqual(
                     [('<oops-message-0>', "{'a': 'b'}"), ('c', 'd')],
-                    oops.req_vars)
+                    oops['req_vars'])
 
 
 class TestSensitiveRequestVariables(testtools.TestCase):

=== modified file 'lib/launchpad_loggerhead/tests.py'
--- lib/launchpad_loggerhead/tests.py	2011-03-31 03:31:15 +0000
+++ lib/launchpad_loggerhead/tests.py	2011-08-11 03:21:40 +0000
@@ -18,7 +18,6 @@
 import zope.event
 
 from canonical.config import config
-from canonical.launchpad.webapp.errorlog import ErrorReport, ErrorReportEvent
 from canonical.launchpad.webapp.vhosts import allvhosts
 from canonical.testing.layers import DatabaseFunctionalLayer
 from launchpad_loggerhead.app import (
@@ -250,11 +249,11 @@
         # event
         self.assertEqual(1, len(self.oopses))
         oops = self.oopses[0]
-        self.assertEqual('RuntimeError', oops.type)
+        self.assertEqual('RuntimeError', oops['type'])
         # runtime_failing_app doesn't call start_response, but oops_middleware
         # does because it tries to send the OOPS information to the user.
         self.assertTrue(self.start_response_called)
-        self.assertEqual(_oops_html_template % {'oopsid': oops.id},
+        self.assertEqual(_oops_html_template % {'oopsid': oops['id']},
                          ''.join(self._response_chunks))
 
     def test_ignores_socket_exceptions(self):

=== modified file 'lib/lp/code/model/tests/test_diff.py'
--- lib/lp/code/model/tests/test_diff.py	2010-12-02 16:13:51 +0000
+++ lib/lp/code/model/tests/test_diff.py	2011-08-11 03:21:40 +0000
@@ -288,7 +288,7 @@
         diff_bytes = "not a real diff"
         diff = Diff.fromFile(StringIO(diff_bytes), len(diff_bytes))
         oops = self.oopses[0]
-        self.assertEqual('MalformedPatchHeader', oops.type)
+        self.assertEqual('MalformedPatchHeader', oops['type'])
         self.assertIs(None, diff.diffstat)
         self.assertIs(None, diff.added_lines_count)
         self.assertIs(None, diff.removed_lines_count)

=== modified file 'lib/lp/services/job/runner.py'
--- lib/lp/services/job/runner.py	2011-06-21 13:24:57 +0000
+++ lib/lp/services/job/runner.py	2011-08-11 03:21:40 +0000
@@ -147,7 +147,7 @@
 
     def notifyOops(self, oops):
         """Report this oops."""
-        ctrl = self.getOopsMailController(oops.id)
+        ctrl = self.getOopsMailController(oops['id'])
         if ctrl is None:
             return
         ctrl.send()
@@ -306,7 +306,7 @@
             transaction.commit()
             oops = self.runJobHandleError(job)
             if oops is not None:
-                self._logOopsId(oops.id)
+                self._logOopsId(oops['id'])
 
 
 class RunJobCommand(amp.Command):
@@ -384,7 +384,7 @@
         if oops is None:
             oops_id = ''
         else:
-            oops_id = oops.id
+            oops_id = oops['id']
         return {'success': len(runner.completed_jobs), 'oops_id': oops_id}
 
 
@@ -452,7 +452,7 @@
             else:
                 info = (failure.type, failure.value, failure.tb)
                 oops = self._doOops(job, info)
-                self._logOopsId(oops.id)
+                self._logOopsId(oops['id'])
         deferred.addCallbacks(update, job_raised)
         return deferred
 
@@ -461,7 +461,7 @@
             raise TimeoutError
         except TimeoutError:
             oops = self._doOops(job, sys.exc_info())
-            self._logOopsId(oops.id)
+            self._logOopsId(oops['id'])
 
     def getTaskSource(self):
         """Return a task source for all jobs in job_source."""

=== modified file 'lib/lp/services/job/tests/test_runner.py'
--- lib/lp/services/job/tests/test_runner.py	2011-07-05 09:01:53 +0000
+++ lib/lp/services/job/tests/test_runner.py	2011-08-11 03:21:40 +0000
@@ -193,9 +193,9 @@
         self.assertEqual(JobStatus.FAILED, job_1.job.status)
         self.assertEqual(JobStatus.COMPLETED, job_2.job.status)
         oops = self.oopses[-1]
-        self.assertIn('Fake exception.  Foobar, I say!', oops.tb_text)
-        self.assertEqual(1, len(oops.req_vars))
-        self.assertEqual("{'foo': 'bar'}", oops.req_vars[0][1])
+        self.assertIn('Fake exception.  Foobar, I say!', oops['tb_text'])
+        self.assertEqual(1, len(oops['req_vars']))
+        self.assertEqual("{'foo': 'bar'}", oops['req_vars'][0][1])
 
     def test_oops_messages_used_when_handling(self):
         """Oops messages should appear even when exceptions are handled."""
@@ -211,8 +211,8 @@
         runner = JobRunner([job_1, job_2])
         runner.runAll()
         oops = self.oopses[-1]
-        self.assertEqual(1, len(oops.req_vars))
-        self.assertEqual("{'foo': 'bar'}", oops.req_vars[0][1])
+        self.assertEqual(1, len(oops['req_vars']))
+        self.assertEqual("{'foo': 'bar'}", oops['req_vars'][0][1])
 
     def test_runAll_aborts_transaction_on_error(self):
         """runAll should abort the transaction on oops."""
@@ -250,7 +250,7 @@
         self.assertIn(
             'Launchpad encountered an internal error during the following'
             ' operation: appending a string to a list.  It was logged with id'
-            ' %s.  Sorry for the inconvenience.' % oops.id,
+            ' %s.  Sorry for the inconvenience.' % oops['id'],
             notification.get_payload(decode=True))
         self.assertNotIn('Fake exception.  Foobar, I say!',
                          notification.get_payload(decode=True))

=== modified file 'lib/lp/services/profile/profile.py'
--- lib/lp/services/profile/profile.py	2011-08-08 15:24:22 +0000
+++ lib/lp/services/profile/profile.py	2011-08-11 03:21:40 +0000
@@ -16,6 +16,7 @@
 
 from bzrlib import errors
 from bzrlib.lsprof import BzrProfiler
+import oops.serializer_rfc822
 from zope.pagetemplate.pagetemplatefile import PageTemplateFile
 from zope.app.publication.interfaces import IEndRequestEvent
 from zope.component import (
@@ -230,10 +231,10 @@
             # information.
             info = (ProfilingOops, None, None)
             error_utility = getUtility(IErrorReportingUtility)
-            oops = error_utility.handling(info, request)
-            oopsid = oops.id
+            oops_report = error_utility.handling(info, request)
+            oopsid = oops_report['id']
         else:
-            oops = request.oops
+            oops_report = request.oops
         if actions.intersection(('callgrind', 'pstats')):
             filename = '%s-%s-%s-%s' % (
                 timestamp, pageid, oopsid,
@@ -250,10 +251,10 @@
             prof_stats.save(dump_path)
             template_context['dump_path'] = os.path.abspath(dump_path)
         if is_html and 'show' in actions:
-            # Generate raw OOPS results.
-            f = StringIO.StringIO()
-            oops.write(f)
-            template_context['oops'] = f.getvalue()
+            # Generate rfc822 OOPS result (might be nice to have an html
+            # serializer..).
+            template_context['oops'] = ''.join(
+                    oops.serializer_rfc822.to_chunks(oops_report))
             # Generate profile summaries.
             for name in ('inlinetime', 'totaltime', 'callcount'):
                 prof_stats.sort(name)

=== modified file 'lib/lp/services/profile/tests.py'
--- lib/lp/services/profile/tests.py	2011-08-08 15:48:34 +0000
+++ lib/lp/services/profile/tests.py	2011-08-11 03:21:40 +0000
@@ -566,18 +566,16 @@
     def test_profiling_oops_is_informational(self):
         self.pushProfilingConfig(profiling_allowed='True')
         request = self.endRequest('/++profile++show/')
-        self.assertIsInstance(request.oops, ErrorReport)
-        self.assertTrue(request.oops.informational)
-        self.assertEquals(request.oops.type, 'ProfilingOops')
+        self.assertTrue(request.oops['informational'])
+        self.assertEquals(request.oops['type'], 'ProfilingOops')
         self.assertCleanProfilerState()
 
     def test_real_oops_trumps_profiling_oops(self):
         self.pushProfilingConfig(profiling_allowed='True')
         request = self.endRequest('/++profile++show/no-such-file',
                                   KeyError('foo'))
-        self.assertIsInstance(request.oops, ErrorReport)
-        self.assertFalse(request.oops.informational)
-        self.assertEquals(request.oops.type, 'KeyError')
+        self.assertFalse(request.oops['informational'])
+        self.assertEquals(request.oops['type'], 'KeyError')
         response = self.getAddedResponse(request)
         self.assertIn('Exception-Type: KeyError', response)
         self.assertCleanProfilerState()

=== modified file 'lib/lp/services/twistedsupport/tests/test_loggingsupport.py'
--- lib/lp/services/twistedsupport/tests/test_loggingsupport.py	2010-10-30 23:24:28 +0000
+++ lib/lp/services/twistedsupport/tests/test_loggingsupport.py	2011-08-11 03:21:40 +0000
@@ -79,8 +79,8 @@
         fail = makeFailure(RuntimeError)
         log.err(fail, error_time=error_time)
         flush_logged_errors(RuntimeError)
-        oops = globalErrorUtility.getOopsReport(error_time)
-        self.assertEqual(oops.type, 'RuntimeError')
+        oops = self.oopses[-1]
+        self.assertEqual(oops['type'], 'RuntimeError')
         self.assertLogMatches('^Logged OOPS id.*')
 
 

=== modified file 'lib/lp/testing/__init__.py'
--- lib/lp/testing/__init__.py	2011-07-29 15:38:00 +0000
+++ lib/lp/testing/__init__.py	2011-08-11 03:21:40 +0000
@@ -58,6 +58,7 @@
     timedelta,
     )
 from fnmatch import fnmatchcase
+from functools import partial
 from inspect import (
     getargspec,
     getmro,
@@ -82,6 +83,7 @@
     )
 from bzrlib.transport import get_transport
 import fixtures
+import oops.serializer_rfc822
 import pytz
 import simplejson
 from storm.expr import Variable
@@ -552,8 +554,9 @@
 
     def attachOopses(self):
         if len(self.oopses) > 0:
-            for (i, oops) in enumerate(self.oopses):
-                content = Content(UTF8_TEXT, oops.get_chunks)
+            for (i, report) in enumerate(self.oopses):
+                content = Content(UTF8_TEXT,
+                        partial(oops.serializer_rfc822.to_chunks, report))
                 self.addDetail("oops-%d" % i, content)
 
     def attachLibrarianLog(self, fixture):

=== modified file 'lib/lp/testing/tests/test_testcase.py'
--- lib/lp/testing/tests/test_testcase.py	2010-09-04 20:18:39 +0000
+++ lib/lp/testing/tests/test_testcase.py	2011-08-11 03:21:40 +0000
@@ -9,6 +9,7 @@
 import sys
 import unittest
 
+import oops.serializer_rfc822
 from storm.store import Store
 from zope.component import getUtility
 
@@ -86,22 +87,13 @@
         self.assertEqual(0, len(self.oopses))
         self.trigger_oops()
         self.attachOopses()
-        oops = errorlog.globalErrorUtility.getLastOopsReport()
-        # We have to serialise and read in again, as that is what
-        # getLastOopsReport does, and doing so changes whether the
-        # timezone is in the timestamp.
         content = StringIO()
         content.writelines(self.getDetails()['oops-0'].iter_bytes())
         content.seek(0)
         # Safety net: ensure that no autocasts have occured even on Python 2.6
         # which is slightly better.
         self.assertIsInstance(content.getvalue(), str)
-        from_details = errorlog.ErrorReport.read(content)
-        self.assertEqual(
-            oops.get_chunks(),
-            from_details.get_chunks())
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromName(__name__)
+        from_details = oops.serializer_rfc822.read(content)
+        oops_report = errorlog.globalErrorUtility.getLastOopsReport()
+        self.assertEqual(dict(oops_report.__dict__), from_details)
 

=== modified file 'versions.cfg'
--- versions.cfg	2011-08-10 06:50:52 +0000
+++ versions.cfg	2011-08-11 03:21:40 +0000
@@ -49,7 +49,7 @@
 mocker = 0.10.1
 mozrunner = 1.3.4
 oauth = 1.0
-oops = 0.0.1
+oops = 0.0.2
 paramiko = 1.7.4
 Paste = 1.7.2
 PasteDeploy = 1.3.3