sigh, forgot half of the cover letter: tests: ./bin/test --test=test_hwdb_submission_parser ./bin/test -m lp.testing = Launchpad lint = Checking for conflicts. and issues in doctests and templates. Running jslint, xmllint, pyflakes, and pylint. Using normal rules. Linting changed files: lib/canonical/launchpad/scripts/hwdbsubmissions.py lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py lib/lp/testing/__init__.py lib/lp/testing/tests/test_inlinetests.py == Pyflakes notices == lib/canonical/launchpad/scripts/hwdbsubmissions.py 22: redefinition of unused 'etree' from line 20 lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 10: redefinition of unused 'etree' from line 8 lib/lp/testing/__init__.py 20: 'InvalidURLJoin' imported but unused 43: 'is_logged_in' imported but unused 45: 'test_tales' imported but unused == Pylint notices == lib/canonical/launchpad/scripts/hwdbsubmissions.py 20: [F0401] Unable to import 'xml.etree.cElementTree' (No module named etree) lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 8: [F0401] Unable to import 'xml.etree.cElementTree' (No module named etree) The imported but unused "thingies" in lib/lp/testing/__init__.py are elsewhere imported from lp.testing. hwdbsubmisisons,py tries to import cElementTree from two different locations. One of them is needed for Python 2.4, the other one for 2.5. This branch is based on lp:~adeuring/launchpad/bug-458029-kernel-package-name-for-udev-submissions, which is reveiwed but has not yet landed. (ec2testing for that branch got stuck somehow; I killed the ec2 instance yesterday evening after 7 or 8 hours.) The diff against the base branch: === modified file 'lib/canonical/launchpad/scripts/hwdbsubmissions.py' --- lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-22 11:08:18 +0000 +++ lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-22 19:40:00 +0000 @@ -1365,7 +1365,8 @@ if ('udev' in parsed_data['hardware'] and not self.checkConsistentUdevDeviceData( parsed_data['hardware']['udev'], - parsed_data['hardware']['sysfs-attributes'])): + parsed_data['hardware']['sysfs-attributes'], + parsed_data['hardware']['dmi'],)): return False duplicate_ids = self.findDuplicateIDs(parsed_data) if duplicate_ids: === modified file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py' --- lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 2009-10-21 16:49:03 +0000 +++ lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 2009-10-22 19:59:45 +0000 @@ -23,6 +23,7 @@ ROOT_UDI) from canonical.testing import BaseLayer +from lp.testing import validate_mock_class class SubmissionParserTestParseSoftware(SubmissionParser): """A Variant used to test SubmissionParser._parseSoftware. @@ -2102,7 +2103,7 @@ All shortcut methods return True. """ - def checkUdevDictsHavePathKey(self, udev_data): + def checkUdevDictsHavePathKey(self, udev_nodes): """See `SubmissionParser`.""" return True @@ -2114,7 +2115,7 @@ """See `SubmissionParser`.""" return True - def checkUdevScsiProperties(self, udev_data, syfs_data): + def checkUdevScsiProperties(self, udev_data, sysfs_data): """See `SubmissionParser`.""" return True @@ -2122,6 +2123,8 @@ """See `SubmissionParser`.""" return True + validate_mock_class(UdevTestSubmissionParser) + def testCheckConsistentUdevDeviceData(self): """Test of SubmissionParser.checkConsistentUdevDeviceData(),""" parser = self.UdevTestSubmissionParser() @@ -2137,10 +2140,12 @@ self.UdevTestSubmissionParser): """A SubmissionPaser where checkUdevDictsHavePathKey() fails.""" - def checkUdevDictsHavePathKey(self, udev_data): + def checkUdevDictsHavePathKey(self, udev_nodes): """See `SubmissionParser`.""" return False + validate_mock_class(SubmissionParserUdevPathCheckFails) + parser = SubmissionParserUdevPathCheckFails() self.assertFalse(parser.checkConsistentUdevDeviceData( None, None, None)) @@ -2158,6 +2163,8 @@ """See `SubmissionParser`.""" return False + validate_mock_class(SubmissionParserUdevPciCheckFails) + parser = SubmissionParserUdevPciCheckFails() self.assertFalse(parser.checkConsistentUdevDeviceData( None, None, None)) @@ -2175,6 +2182,8 @@ """See `SubmissionParser`.""" return False + validate_mock_class(SubmissionParserUdevUsbCheckFails) + parser = SubmissionParserUdevUsbCheckFails() self.assertFalse(parser.checkConsistentUdevDeviceData( None, None, None)) @@ -2192,6 +2201,8 @@ """See `SubmissionParser`.""" return False + validate_mock_class(SubmissionParserUdevUsbCheckFails) + parser = SubmissionParserUdevUsbCheckFails() self.assertFalse(parser.checkConsistentUdevDeviceData( None, None, None)) @@ -2209,55 +2220,38 @@ """See `SubmissionParser`.""" return False + validate_mock_class(SubmissionParserUdevUsbCheckFails) + parser = SubmissionParserUdevUsbCheckFails() self.assertFalse(parser.checkConsistentUdevDeviceData( None, None, None)) - def _setupConsistencyCheckParser(self): - """Prepare and return a SubmissionParser instance. + class MockSubmissionParser(SubmissionParser): + """A SubmissionParser variant for testing checkCOnsistentData() All "method substitutes" return a valid result. """ - test = self + def findDuplicateIDs(self, parsed_data): - test.assertTrue(isinstance(self, SubmissionParser)) return set() def findInvalidIDReferences(self, parsed_data): - test.assertTrue(isinstance(self, SubmissionParser)) return set() def getUDIDeviceMap(self, devices): - test.assertTrue(isinstance(self, SubmissionParser)) return {} def getUDIChildren(self, udi_device_map): - test.assertTrue(isinstance(self, SubmissionParser)) return {} - def checkHALDevicesParentChildConsistency(self, devices): - test.assertTrue(isinstance(self, SubmissionParser)) + def checkHALDevicesParentChildConsistency(self, udi_children): return [] - def checkConsistentUdevDeviceData(self, udev_data, sysfs_data): + def checkConsistentUdevDeviceData( + self, udev_data, sysfs_data, dmi_data): return True - parser = SubmissionParser(self.log) - parser.findDuplicateIDs = ( - lambda parsed_data: findDuplicateIDs(parser, parsed_data)) - parser.findInvalidIDReferences = ( - lambda parsed_data: findInvalidIDReferences(parser, parsed_data)) - parser.getUDIDeviceMap = ( - lambda devices: getUDIDeviceMap(parser, devices)) - parser.getUDIChildren = ( - lambda udi_device_map: getUDIChildren(parser, udi_device_map)) - parser.checkHALDevicesParentChildConsistency = ( - lambda udi_children: checkHALDevicesParentChildConsistency( - parser, udi_children)) - parser.checkConsistentUdevDeviceData = ( - lambda udev_data, sysfs_data: checkConsistentUdevDeviceData( - parser, udev_data, sysfs_data)) - return parser + validate_mock_class(MockSubmissionParser) def assertErrorMessage(self, submission_key, log_message): """Search for message in the log entries for submission_key. @@ -2309,7 +2303,7 @@ def testConsistencyCheck(self): """Test of SubmissionParser.checkConsistency.""" - parser = self._setupConsistencyCheckParser() + parser = self.MockSubmissionParser() result = parser.checkConsistency({'hardware': {'hal': {'devices': []}}}) self.assertEqual(result, True, @@ -2318,45 +2312,53 @@ def testConsistencyCheckValidUdevData(self): """Test of SubmissionParser.checkConsistency.""" - parser = self._setupConsistencyCheckParser() + parser = self.MockSubmissionParser() self.assertTrue(parser.checkConsistency( { 'hardware': { - 'udev': [], - 'sysfs-attributes': [] + 'udev': None, + 'sysfs-attributes': None, + 'dmi': None, } } )) def testConsistencyCheck_invalid_udev_data(self): """Test of SubmissionParser.checkConsistency.""" - def checkConsistentUdevDeviceData(self, udev_data, sysfs_data): - return False - - parser = self._setupConsistencyCheckParser() - parser.checkConsistentUdevDeviceData = ( - lambda udev_data, sysfs_data: checkConsistentUdevDeviceData( - parser, udev_data, sysfs_data)) + class MockSubmissionParserBadUdevDeviceData( + self.MockSubmissionParser): + """A parser where checkConsistentUdevDeviceData() fails.""" + + def checkConsistentUdevDeviceData(self, udev_data, sysfs_data, + dmi_data): + return False + + validate_mock_class(MockSubmissionParserBadUdevDeviceData) + + parser = MockSubmissionParserBadUdevDeviceData() self.assertFalse(parser.checkConsistency( { 'hardware': { - 'udev': [{}], - 'sysfs-attributes': [] + 'udev': None, + 'sysfs-attributes': None, + 'dmi': None, } } )) def testConsistencyCheckWithDuplicateIDs(self): """SubmissionParser.checkConsistency detects duplicate IDs.""" - test = self - def findDuplicateIDs(self, parsed_data): - test.assertTrue(isinstance(self, SubmissionParser)) - return set([1]) - - parser = self._setupConsistencyCheckParser() + class MockSubmissionParserDuplicateIds( + self.MockSubmissionParser): + """A parser where findDuplicateIDs() fails.""" + + def findDuplicateIDs(self, parsed_data): + return set([1]) + + validate_mock_class(MockSubmissionParserDuplicateIds) + + parser = MockSubmissionParserDuplicateIds(self.log) parser.submission_key = 'Consistency check detects duplicate IDs' - parser.findDuplicateIDs = ( - lambda parsed_data: findDuplicateIDs(parser, parsed_data)) result = parser.checkConsistency({'hardware': {'hal': {'devices': []}}}) self.assertEqual(result, False, @@ -2366,15 +2368,16 @@ def testConsistencyCheckWithInvalidIDReferences(self): """SubmissionParser.checkConsistency detects invalid ID references.""" - test = self - def findInvalidIDReferences(self, parsed_data): - test.assertTrue(isinstance(self, SubmissionParser)) - return set([1]) - - parser = self._setupConsistencyCheckParser() + class MockSubmissionParserInvalidIDReferences( + self.MockSubmissionParser): + """A parser where findInvalidIDReferences() fails.""" + def findInvalidIDReferences(self, parsed_data): + return set([1]) + + validate_mock_class(MockSubmissionParserInvalidIDReferences) + + parser = MockSubmissionParserInvalidIDReferences(self.log) parser.submission_key = 'Consistency check detects invalid ID refs' - parser.findInvalidIDReferences = ( - lambda parsed_data: findInvalidIDReferences(parser, parsed_data)) result = parser.checkConsistency({'hardware': {'hal': {'devices': []}}}) self.assertEqual(result, False, @@ -2384,16 +2387,18 @@ def testConsistencyCheckWithDuplicateUDI(self): """SubmissionParser.checkConsistency detects duplicate UDIs.""" - test = self - def getUDIDeviceMap(self, parsed_data): - test.assertTrue(isinstance(self, SubmissionParser)) - raise ValueError( - 'Duplicate UDI: /org/freedesktop/Hal/devices/computer') - - parser = self._setupConsistencyCheckParser() + class MockSubmissionParserUDIDeviceMapFails( + self.MockSubmissionParser): + """A parser where getUDIDeviceMap() fails.""" + + def getUDIDeviceMap(self, devices): + raise ValueError( + 'Duplicate UDI: /org/freedesktop/Hal/devices/computer') + + validate_mock_class(MockSubmissionParserUDIDeviceMapFails) + + parser = MockSubmissionParserUDIDeviceMapFails(self.log) parser.submission_key = 'Consistency check detects invalid ID refs' - parser.getUDIDeviceMap = ( - lambda devices: getUDIDeviceMap(parser, devices)) result = parser.checkConsistency({'hardware': {'hal': {'devices': []}}}) self.assertEqual(result, False, @@ -2404,15 +2409,17 @@ def testConsistencyCheckChildUDIWithoutParent(self): """SubmissionParser.checkConsistency detects "orphaned" devices.""" - test = self - def getUDIChildren(self, udi_device_map): - test.assertTrue(isinstance(self, SubmissionParser)) - raise ValueError('Unknown parent UDI /foo in ') - - parser = self._setupConsistencyCheckParser() + class MockSubmissionParserUDIChildrenFails( + self.MockSubmissionParser): + """A parser where getUDIChildren() fails.""" + + def getUDIChildren(self, udi_device_map): + raise ValueError('Unknown parent UDI /foo in ') + + validate_mock_class(MockSubmissionParserUDIChildrenFails) + + parser = MockSubmissionParserUDIChildrenFails(self.log) parser.submission_key = 'Consistency check detects invalid ID refs' - parser.getUDIChildren = ( - lambda udi_device_map: getUDIChildren(parser, udi_device_map)) result = parser.checkConsistency({'hardware': {'hal': {'devices': []}}}) self.assertEqual(result, False, @@ -2423,17 +2430,21 @@ def testConsistencyCheckCircularParentChildRelation(self): """SubmissionParser.checkConsistency detects "orphaned" devices.""" - test = self - def checkHALDevicesParentChildConsistency(self, devices): - test.assertTrue(isinstance(self, SubmissionParser)) - return ['/foo', '/bar'] - - parser = self._setupConsistencyCheckParser() + class MockSubmissionParserHALDevicesParentChildConsistency( + self.MockSubmissionParser): + """A parser where checkHALDevicesParentChildConsistency() fails. + """ + + def checkHALDevicesParentChildConsistency(self, udi_children): + return ['/foo', '/bar'] + + validate_mock_class( + MockSubmissionParserHALDevicesParentChildConsistency) + + parser = MockSubmissionParserHALDevicesParentChildConsistency( + self.log) parser.submission_key = ('Consistency check detects circular ' 'parent-child relationships') - parser.checkHALDevicesParentChildConsistency = ( - lambda devices: checkHALDevicesParentChildConsistency( - parser, devices)) result = parser.checkConsistency({'hardware': {'hal': {'devices': []}}}) self.assertEqual(result, False, === modified file 'lib/lp/testing/__init__.py' --- lib/lp/testing/__init__.py 2009-10-20 01:55:17 +0000 +++ lib/lp/testing/__init__.py 2009-10-23 08:01:11 +0000 @@ -8,6 +8,7 @@ from datetime import datetime, timedelta from pprint import pformat import copy +from inspect import getargspec, getmembers, getmro, isclass, ismethod import os import shutil import subprocess @@ -744,3 +745,69 @@ tree.unlock() return contents + +def validate_mock_class(mock_class): + """Validate method signatures in mock classes derived from real classes. + + We often use mock classes in tests which are derived from real + classes. + + This decorator ensures that methods redefined in the mock + class have the same signature as the corresponding methods of + the base class. + + >>> class A: + ... + ... def method_one(self, a): + ... pass + + >>> + >>> class B(A): + ... def method_one(self, a): + ... pass + >>> validate_mock_class(B) + + If a class derived from A defines method_one with a different + signature, we get an AssertionError. + + >>> class C(A): + ... def method_one(self, a, b): + ... pass + >>> validate_mock_class(C) + Traceback (most recent call last): + ... + AssertionError: Different method signature for method_one:... + + Even a parameter name must not be modified. + + >>> class D(A): + ... def method_one(self, b): + ... pass + >>> validate_mock_class(D) + Traceback (most recent call last): + ... + AssertionError: Different method signature for method_one:... + + If validate_mock_class() for anything but a class, we get an + AssertionError. + + >>> validate_mock_class('a string') + Traceback (most recent call last): + ... + AssertionError: validate_mock_class() must be called for a class + """ + assert isclass(mock_class), ( + "validate_mock_class() must be called for a class") + base_classes = getmro(mock_class) + for name, obj in getmembers(mock_class): + if ismethod(obj): + for base_class in base_classes[1:]: + if (name in base_class.__dict__): + mock_args = getargspec(obj) + real_args = getargspec(base_class.__dict__[name]) + if mock_args != real_args: + raise AssertionError( + 'Different method signature for %s: %r %r' % ( + name, mock_args, real_args)) + else: + break === added file 'lib/lp/testing/tests/test_inlinetests.py' --- lib/lp/testing/tests/test_inlinetests.py 1970-01-01 00:00:00 +0000 +++ lib/lp/testing/tests/test_inlinetests.py 2009-10-22 17:50:48 +0000 @@ -0,0 +1,20 @@ +# Copyright 2009 Canonical Ltd. This software is licensed under the +# GNU Affero General Public License version 3 (see the file LICENSE). + +"""Run the doc string tests.""" + +import doctest + +from zope.testing.doctest import NORMALIZE_WHITESPACE, ELLIPSIS + +from canonical.launchpad.testing.systemdocs import LayeredDocFileSuite +from canonical.testing import BaseLayer +from lp import testing + +def test_suite(): + suite = LayeredDocFileSuite( + layer=BaseLayer) + suite.addTest(doctest.DocTestSuite( + testing, optionflags=NORMALIZE_WHITESPACE|ELLIPSIS)) + return suite +