← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~benji/launchpad/bug-678375 into lp:launchpad

 

Benji York has proposed merging lp:~benji/launchpad/bug-678375 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  #678375 preflight check for librarian access on appservers
  https://bugs.launchpad.net/bugs/678375


Bug 678375 describes a smoke test script for the librarian that would
test the current librarian-related configuration and exit with 0 if
successful or 1 if there is an error.

This branch contains an implementation of that script.

The script itself (utilities/smoke-test-librarian.py) is just a shell
around the module at lib/canonical/librarian/tests/test_smoketest.py.

The tests are located at lib/canonical/librarian/tests/test_smoketest.py
and can be run with the command "bin/test test_smoketest".

The only lint reported is the erroneous:

    ./utilities/smoke-test-librarian.py
        9: '_pythonpath' imported but unused

-- 
https://code.launchpad.net/~benji/launchpad/bug-678375/+merge/42884
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~benji/launchpad/bug-678375 into lp:launchpad.
=== added file 'lib/canonical/librarian/smoketest.py'
--- lib/canonical/librarian/smoketest.py	1970-01-01 00:00:00 +0000
+++ lib/canonical/librarian/smoketest.py	2010-12-06 22:59:10 +0000
@@ -0,0 +1,64 @@
+#! /usr/bin/python -S
+#
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Perform simple librarian operations to verify the current configuration.
+"""
+
+from cStringIO import StringIO
+import datetime
+import urllib
+
+from zope.component import getUtility
+import pytz
+import transaction
+
+from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
+
+
+FILE_SIZE = 1024
+FILE_DATA = 'x' * FILE_SIZE
+FILE_LIFETIME = datetime.timedelta(hours=1)
+
+
+def store_file(client):
+    file_id = client.addFile(
+        'smoke-test-file', FILE_SIZE, StringIO(FILE_DATA), 'text/plain',
+        expires=datetime.datetime.now(pytz.UTC)+FILE_LIFETIME)
+    # To be able to retrieve the file, we must commit the current transaction.
+    transaction.commit()
+    alias = getUtility(ILibraryFileAliasSet)[file_id]
+    return alias.http_url
+
+
+def read_file(url):
+    try:
+        data = urllib.urlopen(url).read()
+    except (MemoryError, KeyboardInterrupt, SystemExit):
+        # Re-raise catastrophic errors.
+        raise
+    except:
+        # An error is represented by returning None, which won't match when
+        # comapred against FILE_DATA.
+        return None
+
+    return data
+
+
+def main(restricted_client, regular_client):
+    print 'adding a private file to the librarian...'
+    private_url = store_file(restricted_client)
+    print 'retrieving private file from', private_url
+    if read_file(private_url) != FILE_DATA:
+        print 'ERROR: data fetched does not match data written'
+        return 1
+
+    print 'adding a public file to the librarian...'
+    public_url = store_file(regular_client)
+    print 'retrieving public file from', public_url
+    if read_file(public_url) != FILE_DATA:
+        print 'ERROR: data fetched does not match data written'
+        return 1
+
+    return 0

=== added file 'lib/canonical/librarian/tests/test_smoketest.py'
--- lib/canonical/librarian/tests/test_smoketest.py	1970-01-01 00:00:00 +0000
+++ lib/canonical/librarian/tests/test_smoketest.py	2010-12-06 22:59:10 +0000
@@ -0,0 +1,108 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test the script that does a smoke-test of the librarian."""
+
+__metaclass__ = type
+
+from cStringIO import StringIO
+from contextlib import contextmanager
+
+from canonical.librarian import smoketest
+from canonical.librarian.smoketest import (
+    FILE_DATA,
+    main,
+    store_file,
+    )
+from canonical.librarian.testing.fake import FakeLibrarian
+from canonical.testing.layers import ZopelessDatabaseLayer
+from lp.testing import TestCaseWithFactory
+
+
+class GoodUrllib:
+    """A urllib replacement for testing that returns good results."""
+
+    def urlopen(self, url):
+        return StringIO(FILE_DATA)
+
+
+class BadUrllib:
+    """A urllib replacement for testing that returns bad results."""
+
+    def urlopen(self, url):
+        return StringIO('bad data')
+
+
+class ErrorUrllib:
+    """A urllib replacement for testing that raises an exception."""
+
+    def urlopen(self, url):
+        raise IOError('network error')
+
+
+class ExplosiveUrllib:
+    """A urllib replacement that raises an "explosive" exception."""
+
+    def __init__(self, exception):
+        self.exception = exception
+
+    def urlopen(self, url):
+        raise self.exception
+
+
+@contextmanager
+def fake_urllib(fake):
+    original_urllib = smoketest.urllib
+    smoketest.urllib = fake
+    yield
+    smoketest.urllib = original_urllib
+
+
+class SmokeTestTestCase(TestCaseWithFactory):
+    """Class test for translation importer creation."""
+    layer = ZopelessDatabaseLayer
+
+    def setUp(self):
+        super(SmokeTestTestCase, self).setUp()
+        self.fake_librarian = self.useFixture(FakeLibrarian())
+
+    def test_store_file(self):
+        # Make sure that the function meant to store a file in the librarian
+        # and return the file's HTTP URL works.
+        self.assertEquals(
+            store_file(self.fake_librarian),
+            'http://localhost:58000/93/smoke-test-file')
+
+    def test_good_data(self):
+        # If storing and retrieving both the public and private files work,
+        # the main function will return 0 (which will be used as the processes
+        # exit code to signal success).
+        with fake_urllib(GoodUrllib()):
+            self.assertEquals(
+                main(self.fake_librarian, self.fake_librarian),
+                0)
+
+    def test_bad_data(self):
+        # If incorrect data is retrieved, the main function will return 1
+        # (which will be used as the processes exit code to signal an error).
+        with fake_urllib(BadUrllib()):
+            self.assertEquals(
+                main(self.fake_librarian, self.fake_librarian),
+                1)
+
+    def test_exception(self):
+        # If an exception is raised when retrieving the data, the main
+        # function will return 1 (which will be used as the processes exit
+        # code to signal an error).
+        with fake_urllib(ErrorUrllib()):
+            self.assertEquals(
+                main(self.fake_librarian, self.fake_librarian),
+                1)
+
+    def test_explosive_errors(self):
+        # If an "explosive" exception (an exception that should not be caught)
+        # is raised when retrieving the data it is re-raised.
+        for exception in MemoryError, SystemExit, KeyboardInterrupt:
+            with fake_urllib(ExplosiveUrllib(exception)):
+                self.assertRaises(exception,
+                    main, self.fake_librarian, self.fake_librarian)

=== added file 'utilities/smoke-test-librarian.py'
--- utilities/smoke-test-librarian.py	1970-01-01 00:00:00 +0000
+++ utilities/smoke-test-librarian.py	2010-12-06 22:59:10 +0000
@@ -0,0 +1,26 @@
+#! /usr/bin/python -S
+#
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Perform simple librarian operations to verify the current configuration.
+"""
+
+import _pythonpath # Not lint, actually needed.
+
+import sys
+
+from zope.component import getUtility
+from canonical.launchpad.scripts import execute_zcml_for_scripts
+from canonical.librarian.interfaces import (
+    IRestrictedLibrarianClient,
+    ILibrarianClient,
+    )
+from canonical.librarian.smoketest import main
+
+
+if __name__ == '__main__':
+    execute_zcml_for_scripts()
+    restricted_client = getUtility(IRestrictedLibrarianClient)
+    regular_client = getUtility(ILibrarianClient)
+    sys.exit(main(restricted_client, regular_client))