Merge lp:~adeuring/launchpad/bug-458160-fix-submissionparser-checkconsistency into lp:launchpad

Proposed by Abel Deuring
Status: Merged
Approved by: Guilherme Salgado
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~adeuring/launchpad/bug-458160-fix-submissionparser-checkconsistency
Merge into: lp:launchpad
Diff against target: 793 lines
5 files modified
lib/canonical/launchpad/scripts/hwdbsubmissions.py (+16/-11)
lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py (+95/-84)
lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py (+186/-34)
lib/lp/testing/__init__.py (+67/-0)
lib/lp/testing/tests/test_inlinetests.py (+20/-0)
To merge this branch: bzr merge lp:~adeuring/launchpad/bug-458160-fix-submissionparser-checkconsistency
Reviewer Review Type Date Requested Status
Guilherme Salgado (community) Approve
Review via email: mp+13827@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) wrote :

A two-line change or "real" code together with 100 lines of a new test helper (including tests of the helper) and ~450 lines of changed/improved/fixed tests...

The method SubmissionParser.checkConsistency() in l/c/l/scripts/hwdbsubmissions.py did not specify enough parameters when it called SubmissionParser.checkConsistentUdevDeviceData(). This bug was introduced in a previous branch when I modified the latter method to do more checks which required one more parameter (dmi_data).

The bug in checkConsistency() was not detected because of the way this method was tested: All tests of it created a regular instance of SubmissionParser and then monkey-patched the methods called by checkConsistency(), thus avoiding to set up lots of quite convoluted looking test data required for the non-monkey-patched versions of these methods. The problem is that the surrogate of checkConsistentUdevDeviceData() did not get the new parameter...

I assume that such an error can occur in other tests too, so my idea to avoid a similar problem in the future was this: Don't monkey-patch class SubmissionParser, but use instead a mock class, derived from the real class, and ensure that the methods of mock class have the same signatures as the methods of the base class.

Such a test can be made by calling the new function validate_mock_class() in lp.testing. The test is quite simple: It compares the signature of methods defined in the mock class with the signature of the first method with same name found in the base classes. Even the names of method parameters must not be changed. I think this not a serious problem: having identical parameters names isn't that bad, and allowing differernt names would require a somewhat more complicated comparison, which isn't worth the effort, I think.

the "doc string tests" in lp.testing.__init__ were not tested at all, so I added a test suite for them.

Revision history for this message
Abel Deuring (adeuring) wrote :
Download full text (19.3 KiB)

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 `Submissio...

Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (3.4 KiB)

Hi Abel,

Your tests are certainly more readable *and* robust now, but I was left
wondering if there isn't a mock library [1] that already does what we
want. Did you check that?

Other than that, I have only a couple suggestions below, but I think
this is a nice improvement as it is, so I'm approving it.

 status approved

[1] http://www.voidspace.org.uk/python/mock/
    http://labix.org/mocker
    etc

On Fri, 2009-10-23 at 08:54 +0000, Abel Deuring wrote:
[...]
> === 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.

This is not really a decorator, right? It'd be nice if it was a class
decorator, though. Did you consider that?

> +
> + >>> 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__):

No need for the parenthesis here.

> + mock_args = getargspec(obj)
> + real_args = getargspec(base_class.__dict__[name])
> + if mock_args != real_args:
> + raise AssertionError(
> + ...

Read more...

review: Approve
Revision history for this message
Abel Deuring (adeuring) wrote :
Download full text (3.5 KiB)

Hi Guilherme,

thanks for your reveiw!

On 23.10.2009 15:12, Guilherme Salgado wrote:
> Review: Approve
> Hi Abel,
>
> Your tests are certainly more readable *and* robust now, but I was left
> wondering if there isn't a mock library [1] that already does what we
> want. Did you check that?

No, I was not aware of this library. After a quick check I think that it
is a very useful tool, but it would not solve the problem I was having.
(Though I may of course have missed something.)

>
> Other than that, I have only a couple suggestions below, but I think
> this is a nice improvement as it is, so I'm approving it.
>
> status approved
>
> [1] http://www.voidspace.org.uk/python/mock/
> http://labix.org/mocker
> etc
>
> On Fri, 2009-10-23 at 08:54 +0000, Abel Deuring wrote:
> [...]
>> === 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.
>
> This is not really a decorator, right? It'd be nice if it was a class
> decorator, though. Did you consider that?

Argh, I forgot to change the doc string... I _wanted_ to use it as a
decorator, but then discovered that we cannot use class decorators in
Python 2.4 and 2.5.

>
>> +
>> + >>> 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 isclas...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/scripts/hwdbsubmissions.py'
--- lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-21 16:49:03 +0000
+++ lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-23 18:56:14 +0000
@@ -33,6 +33,7 @@
3333
34from canonical.lazr.xml import RelaxNGValidator34from canonical.lazr.xml import RelaxNGValidator
3535
36from canonical.cachedproperty import cachedproperty
36from canonical.config import config37from canonical.config import config
37from canonical.librarian.interfaces import LibrarianServerError38from canonical.librarian.interfaces import LibrarianServerError
38from canonical.launchpad.interfaces.hwdb import (39from canonical.launchpad.interfaces.hwdb import (
@@ -1364,7 +1365,8 @@
1364 if ('udev' in parsed_data['hardware']1365 if ('udev' in parsed_data['hardware']
1365 and not self.checkConsistentUdevDeviceData(1366 and not self.checkConsistentUdevDeviceData(
1366 parsed_data['hardware']['udev'],1367 parsed_data['hardware']['udev'],
1367 parsed_data['hardware']['sysfs-attributes'])):1368 parsed_data['hardware']['sysfs-attributes'],
1369 parsed_data['hardware']['dmi'],)):
1368 return False1370 return False
1369 duplicate_ids = self.findDuplicateIDs(parsed_data)1371 duplicate_ids = self.findDuplicateIDs(parsed_data)
1370 if duplicate_ids:1372 if duplicate_ids:
@@ -1470,15 +1472,20 @@
1470 del self.devices['/devices']1472 del self.devices['/devices']
1471 return True1473 return True
14721474
1473 def getKernelPackageName(self):1475 @cachedproperty
1474 """Return the kernel package name of the submission,"""1476 def kernel_package_name(self):
1475 root_hal_device = self.devices[ROOT_UDI]1477 """The kernel package name for the submission."""
1476 kernel_version = root_hal_device.getProperty('system.kernel.version')1478 if ROOT_UDI in self.devices:
1479 root_hal_device = self.devices[ROOT_UDI]
1480 kernel_version = root_hal_device.getProperty(
1481 'system.kernel.version')
1482 else:
1483 kernel_version = self.parsed_data['summary'].get('kernel-release')
1477 if kernel_version is None:1484 if kernel_version is None:
1478 self._logWarning(1485 self._logWarning(
1479 'Submission does not provide property system.kernel.version '1486 'Submission does not provide property system.kernel.version '
1480 'for /org/freedesktop/Hal/devices/computer.',1487 'for /org/freedesktop/Hal/devices/computer or a summary '
1481 WARNING_NO_HAL_KERNEL_VERSION)1488 'sub-node <kernel-release>.')
1482 return None1489 return None
1483 kernel_package_name = 'linux-image-' + kernel_version1490 kernel_package_name = 'linux-image-' + kernel_version
1484 packages = self.parsed_data['software']['packages']1491 packages = self.parsed_data['software']['packages']
@@ -1490,8 +1497,7 @@
1490 'Inconsistent kernel version data: According to HAL the '1497 'Inconsistent kernel version data: According to HAL the '
1491 'kernel is %s, but the submission does not know about a '1498 'kernel is %s, but the submission does not know about a '
1492 'kernel package %s'1499 'kernel package %s'
1493 % (kernel_version, kernel_package_name),1500 % (kernel_version, kernel_package_name))
1494 WARNING_NO_HAL_KERNEL_VERSION)
1495 return None1501 return None
1496 return kernel_package_name1502 return kernel_package_name
14971503
@@ -2185,10 +2191,9 @@
2185 # drivers, so there is currently no need to search for2191 # drivers, so there is currently no need to search for
2186 # for user space printer drivers, for example.2192 # for user space printer drivers, for example.
2187 if self.driver_name is not None:2193 if self.driver_name is not None:
2188 kernel_package_name = self.parser.getKernelPackageName()
2189 db_driver_set = getUtility(IHWDriverSet)2194 db_driver_set = getUtility(IHWDriverSet)
2190 return db_driver_set.getOrCreate(2195 return db_driver_set.getOrCreate(
2191 kernel_package_name, self.driver_name)2196 self.parser.kernel_package_name, self.driver_name)
2192 else:2197 else:
2193 return None2198 return None
21942199
21952200
=== 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-23 18:56:14 +0000
@@ -23,6 +23,7 @@
23 ROOT_UDI)23 ROOT_UDI)
24from canonical.testing import BaseLayer24from canonical.testing import BaseLayer
2525
26from lp.testing import validate_mock_class
2627
27class SubmissionParserTestParseSoftware(SubmissionParser):28class SubmissionParserTestParseSoftware(SubmissionParser):
28 """A Variant used to test SubmissionParser._parseSoftware.29 """A Variant used to test SubmissionParser._parseSoftware.
@@ -2102,7 +2103,7 @@
21022103
2103 All shortcut methods return True.2104 All shortcut methods return True.
2104 """2105 """
2105 def checkUdevDictsHavePathKey(self, udev_data):2106 def checkUdevDictsHavePathKey(self, udev_nodes):
2106 """See `SubmissionParser`."""2107 """See `SubmissionParser`."""
2107 return True2108 return True
21082109
@@ -2114,7 +2115,7 @@
2114 """See `SubmissionParser`."""2115 """See `SubmissionParser`."""
2115 return True2116 return True
21162117
2117 def checkUdevScsiProperties(self, udev_data, syfs_data):2118 def checkUdevScsiProperties(self, udev_data, sysfs_data):
2118 """See `SubmissionParser`."""2119 """See `SubmissionParser`."""
2119 return True2120 return True
21202121
@@ -2122,6 +2123,8 @@
2122 """See `SubmissionParser`."""2123 """See `SubmissionParser`."""
2123 return True2124 return True
21242125
2126 validate_mock_class(UdevTestSubmissionParser)
2127
2125 def testCheckConsistentUdevDeviceData(self):2128 def testCheckConsistentUdevDeviceData(self):
2126 """Test of SubmissionParser.checkConsistentUdevDeviceData(),"""2129 """Test of SubmissionParser.checkConsistentUdevDeviceData(),"""
2127 parser = self.UdevTestSubmissionParser()2130 parser = self.UdevTestSubmissionParser()
@@ -2137,10 +2140,12 @@
2137 self.UdevTestSubmissionParser):2140 self.UdevTestSubmissionParser):
2138 """A SubmissionPaser where checkUdevDictsHavePathKey() fails."""2141 """A SubmissionPaser where checkUdevDictsHavePathKey() fails."""
21392142
2140 def checkUdevDictsHavePathKey(self, udev_data):2143 def checkUdevDictsHavePathKey(self, udev_nodes):
2141 """See `SubmissionParser`."""2144 """See `SubmissionParser`."""
2142 return False2145 return False
21432146
2147 validate_mock_class(SubmissionParserUdevPathCheckFails)
2148
2144 parser = SubmissionParserUdevPathCheckFails()2149 parser = SubmissionParserUdevPathCheckFails()
2145 self.assertFalse(parser.checkConsistentUdevDeviceData(2150 self.assertFalse(parser.checkConsistentUdevDeviceData(
2146 None, None, None))2151 None, None, None))
@@ -2158,6 +2163,8 @@
2158 """See `SubmissionParser`."""2163 """See `SubmissionParser`."""
2159 return False2164 return False
21602165
2166 validate_mock_class(SubmissionParserUdevPciCheckFails)
2167
2161 parser = SubmissionParserUdevPciCheckFails()2168 parser = SubmissionParserUdevPciCheckFails()
2162 self.assertFalse(parser.checkConsistentUdevDeviceData(2169 self.assertFalse(parser.checkConsistentUdevDeviceData(
2163 None, None, None))2170 None, None, None))
@@ -2175,6 +2182,8 @@
2175 """See `SubmissionParser`."""2182 """See `SubmissionParser`."""
2176 return False2183 return False
21772184
2185 validate_mock_class(SubmissionParserUdevUsbCheckFails)
2186
2178 parser = SubmissionParserUdevUsbCheckFails()2187 parser = SubmissionParserUdevUsbCheckFails()
2179 self.assertFalse(parser.checkConsistentUdevDeviceData(2188 self.assertFalse(parser.checkConsistentUdevDeviceData(
2180 None, None, None))2189 None, None, None))
@@ -2192,6 +2201,8 @@
2192 """See `SubmissionParser`."""2201 """See `SubmissionParser`."""
2193 return False2202 return False
21942203
2204 validate_mock_class(SubmissionParserUdevUsbCheckFails)
2205
2195 parser = SubmissionParserUdevUsbCheckFails()2206 parser = SubmissionParserUdevUsbCheckFails()
2196 self.assertFalse(parser.checkConsistentUdevDeviceData(2207 self.assertFalse(parser.checkConsistentUdevDeviceData(
2197 None, None, None))2208 None, None, None))
@@ -2209,55 +2220,38 @@
2209 """See `SubmissionParser`."""2220 """See `SubmissionParser`."""
2210 return False2221 return False
22112222
2223 validate_mock_class(SubmissionParserUdevUsbCheckFails)
2224
2212 parser = SubmissionParserUdevUsbCheckFails()2225 parser = SubmissionParserUdevUsbCheckFails()
2213 self.assertFalse(parser.checkConsistentUdevDeviceData(2226 self.assertFalse(parser.checkConsistentUdevDeviceData(
2214 None, None, None))2227 None, None, None))
22152228
2216 def _setupConsistencyCheckParser(self):2229 class MockSubmissionParser(SubmissionParser):
2217 """Prepare and return a SubmissionParser instance.2230 """A SubmissionParser variant for testing checkCOnsistentData()
22182231
2219 All "method substitutes" return a valid result.2232 All "method substitutes" return a valid result.
2220 """2233 """
2221 test = self2234
2222 def findDuplicateIDs(self, parsed_data):2235 def findDuplicateIDs(self, parsed_data):
2223 test.assertTrue(isinstance(self, SubmissionParser))
2224 return set()2236 return set()
22252237
2226 def findInvalidIDReferences(self, parsed_data):2238 def findInvalidIDReferences(self, parsed_data):
2227 test.assertTrue(isinstance(self, SubmissionParser))
2228 return set()2239 return set()
22292240
2230 def getUDIDeviceMap(self, devices):2241 def getUDIDeviceMap(self, devices):
2231 test.assertTrue(isinstance(self, SubmissionParser))
2232 return {}2242 return {}
22332243
2234 def getUDIChildren(self, udi_device_map):2244 def getUDIChildren(self, udi_device_map):
2235 test.assertTrue(isinstance(self, SubmissionParser))
2236 return {}2245 return {}
22372246
2238 def checkHALDevicesParentChildConsistency(self, devices):2247 def checkHALDevicesParentChildConsistency(self, udi_children):
2239 test.assertTrue(isinstance(self, SubmissionParser))
2240 return []2248 return []
22412249
2242 def checkConsistentUdevDeviceData(self, udev_data, sysfs_data):2250 def checkConsistentUdevDeviceData(
2251 self, udev_data, sysfs_data, dmi_data):
2243 return True2252 return True
22442253
2245 parser = SubmissionParser(self.log)2254 validate_mock_class(MockSubmissionParser)
2246 parser.findDuplicateIDs = (
2247 lambda parsed_data: findDuplicateIDs(parser, parsed_data))
2248 parser.findInvalidIDReferences = (
2249 lambda parsed_data: findInvalidIDReferences(parser, parsed_data))
2250 parser.getUDIDeviceMap = (
2251 lambda devices: getUDIDeviceMap(parser, devices))
2252 parser.getUDIChildren = (
2253 lambda udi_device_map: getUDIChildren(parser, udi_device_map))
2254 parser.checkHALDevicesParentChildConsistency = (
2255 lambda udi_children: checkHALDevicesParentChildConsistency(
2256 parser, udi_children))
2257 parser.checkConsistentUdevDeviceData = (
2258 lambda udev_data, sysfs_data: checkConsistentUdevDeviceData(
2259 parser, udev_data, sysfs_data))
2260 return parser
22612255
2262 def assertErrorMessage(self, submission_key, log_message):2256 def assertErrorMessage(self, submission_key, log_message):
2263 """Search for message in the log entries for submission_key.2257 """Search for message in the log entries for submission_key.
@@ -2309,7 +2303,7 @@
23092303
2310 def testConsistencyCheck(self):2304 def testConsistencyCheck(self):
2311 """Test of SubmissionParser.checkConsistency."""2305 """Test of SubmissionParser.checkConsistency."""
2312 parser = self._setupConsistencyCheckParser()2306 parser = self.MockSubmissionParser()
2313 result = parser.checkConsistency({'hardware':2307 result = parser.checkConsistency({'hardware':
2314 {'hal': {'devices': []}}})2308 {'hal': {'devices': []}}})
2315 self.assertEqual(result, True,2309 self.assertEqual(result, True,
@@ -2318,45 +2312,53 @@
23182312
2319 def testConsistencyCheckValidUdevData(self):2313 def testConsistencyCheckValidUdevData(self):
2320 """Test of SubmissionParser.checkConsistency."""2314 """Test of SubmissionParser.checkConsistency."""
2321 parser = self._setupConsistencyCheckParser()2315 parser = self.MockSubmissionParser()
2322 self.assertTrue(parser.checkConsistency(2316 self.assertTrue(parser.checkConsistency(
2323 {2317 {
2324 'hardware': {2318 'hardware': {
2325 'udev': [],2319 'udev': None,
2326 'sysfs-attributes': []2320 'sysfs-attributes': None,
2321 'dmi': None,
2327 }2322 }
2328 }2323 }
2329 ))2324 ))
23302325
2331 def testConsistencyCheck_invalid_udev_data(self):2326 def testConsistencyCheck_invalid_udev_data(self):
2332 """Test of SubmissionParser.checkConsistency."""2327 """Test of SubmissionParser.checkConsistency."""
2333 def checkConsistentUdevDeviceData(self, udev_data, sysfs_data):2328 class MockSubmissionParserBadUdevDeviceData(
2334 return False2329 self.MockSubmissionParser):
23352330 """A parser where checkConsistentUdevDeviceData() fails."""
2336 parser = self._setupConsistencyCheckParser()2331
2337 parser.checkConsistentUdevDeviceData = (2332 def checkConsistentUdevDeviceData(self, udev_data, sysfs_data,
2338 lambda udev_data, sysfs_data: checkConsistentUdevDeviceData(2333 dmi_data):
2339 parser, udev_data, sysfs_data))2334 return False
2335
2336 validate_mock_class(MockSubmissionParserBadUdevDeviceData)
2337
2338 parser = MockSubmissionParserBadUdevDeviceData()
2340 self.assertFalse(parser.checkConsistency(2339 self.assertFalse(parser.checkConsistency(
2341 {2340 {
2342 'hardware': {2341 'hardware': {
2343 'udev': [{}],2342 'udev': None,
2344 'sysfs-attributes': []2343 'sysfs-attributes': None,
2344 'dmi': None,
2345 }2345 }
2346 }2346 }
2347 ))2347 ))
23482348
2349 def testConsistencyCheckWithDuplicateIDs(self):2349 def testConsistencyCheckWithDuplicateIDs(self):
2350 """SubmissionParser.checkConsistency detects duplicate IDs."""2350 """SubmissionParser.checkConsistency detects duplicate IDs."""
2351 test = self2351 class MockSubmissionParserDuplicateIds(
2352 def findDuplicateIDs(self, parsed_data):2352 self.MockSubmissionParser):
2353 test.assertTrue(isinstance(self, SubmissionParser))2353 """A parser where findDuplicateIDs() fails."""
2354 return set([1])2354
23552355 def findDuplicateIDs(self, parsed_data):
2356 parser = self._setupConsistencyCheckParser()2356 return set([1])
2357
2358 validate_mock_class(MockSubmissionParserDuplicateIds)
2359
2360 parser = MockSubmissionParserDuplicateIds(self.log)
2357 parser.submission_key = 'Consistency check detects duplicate IDs'2361 parser.submission_key = 'Consistency check detects duplicate IDs'
2358 parser.findDuplicateIDs = (
2359 lambda parsed_data: findDuplicateIDs(parser, parsed_data))
2360 result = parser.checkConsistency({'hardware':2362 result = parser.checkConsistency({'hardware':
2361 {'hal': {'devices': []}}})2363 {'hal': {'devices': []}}})
2362 self.assertEqual(result, False,2364 self.assertEqual(result, False,
@@ -2366,15 +2368,16 @@
23662368
2367 def testConsistencyCheckWithInvalidIDReferences(self):2369 def testConsistencyCheckWithInvalidIDReferences(self):
2368 """SubmissionParser.checkConsistency detects invalid ID references."""2370 """SubmissionParser.checkConsistency detects invalid ID references."""
2369 test = self2371 class MockSubmissionParserInvalidIDReferences(
2370 def findInvalidIDReferences(self, parsed_data):2372 self.MockSubmissionParser):
2371 test.assertTrue(isinstance(self, SubmissionParser))2373 """A parser where findInvalidIDReferences() fails."""
2372 return set([1])2374 def findInvalidIDReferences(self, parsed_data):
23732375 return set([1])
2374 parser = self._setupConsistencyCheckParser()2376
2377 validate_mock_class(MockSubmissionParserInvalidIDReferences)
2378
2379 parser = MockSubmissionParserInvalidIDReferences(self.log)
2375 parser.submission_key = 'Consistency check detects invalid ID refs'2380 parser.submission_key = 'Consistency check detects invalid ID refs'
2376 parser.findInvalidIDReferences = (
2377 lambda parsed_data: findInvalidIDReferences(parser, parsed_data))
2378 result = parser.checkConsistency({'hardware':2381 result = parser.checkConsistency({'hardware':
2379 {'hal': {'devices': []}}})2382 {'hal': {'devices': []}}})
2380 self.assertEqual(result, False,2383 self.assertEqual(result, False,
@@ -2384,16 +2387,18 @@
23842387
2385 def testConsistencyCheckWithDuplicateUDI(self):2388 def testConsistencyCheckWithDuplicateUDI(self):
2386 """SubmissionParser.checkConsistency detects duplicate UDIs."""2389 """SubmissionParser.checkConsistency detects duplicate UDIs."""
2387 test = self2390 class MockSubmissionParserUDIDeviceMapFails(
2388 def getUDIDeviceMap(self, parsed_data):2391 self.MockSubmissionParser):
2389 test.assertTrue(isinstance(self, SubmissionParser))2392 """A parser where getUDIDeviceMap() fails."""
2390 raise ValueError(2393
2391 'Duplicate UDI: /org/freedesktop/Hal/devices/computer')2394 def getUDIDeviceMap(self, devices):
23922395 raise ValueError(
2393 parser = self._setupConsistencyCheckParser()2396 'Duplicate UDI: /org/freedesktop/Hal/devices/computer')
2397
2398 validate_mock_class(MockSubmissionParserUDIDeviceMapFails)
2399
2400 parser = MockSubmissionParserUDIDeviceMapFails(self.log)
2394 parser.submission_key = 'Consistency check detects invalid ID refs'2401 parser.submission_key = 'Consistency check detects invalid ID refs'
2395 parser.getUDIDeviceMap = (
2396 lambda devices: getUDIDeviceMap(parser, devices))
2397 result = parser.checkConsistency({'hardware':2402 result = parser.checkConsistency({'hardware':
2398 {'hal': {'devices': []}}})2403 {'hal': {'devices': []}}})
2399 self.assertEqual(result, False,2404 self.assertEqual(result, False,
@@ -2404,15 +2409,17 @@
24042409
2405 def testConsistencyCheckChildUDIWithoutParent(self):2410 def testConsistencyCheckChildUDIWithoutParent(self):
2406 """SubmissionParser.checkConsistency detects "orphaned" devices."""2411 """SubmissionParser.checkConsistency detects "orphaned" devices."""
2407 test = self2412 class MockSubmissionParserUDIChildrenFails(
2408 def getUDIChildren(self, udi_device_map):2413 self.MockSubmissionParser):
2409 test.assertTrue(isinstance(self, SubmissionParser))2414 """A parser where getUDIChildren() fails."""
2410 raise ValueError('Unknown parent UDI /foo in <device id="3">')2415
24112416 def getUDIChildren(self, udi_device_map):
2412 parser = self._setupConsistencyCheckParser()2417 raise ValueError('Unknown parent UDI /foo in <device id="3">')
2418
2419 validate_mock_class(MockSubmissionParserUDIChildrenFails)
2420
2421 parser = MockSubmissionParserUDIChildrenFails(self.log)
2413 parser.submission_key = 'Consistency check detects invalid ID refs'2422 parser.submission_key = 'Consistency check detects invalid ID refs'
2414 parser.getUDIChildren = (
2415 lambda udi_device_map: getUDIChildren(parser, udi_device_map))
2416 result = parser.checkConsistency({'hardware':2423 result = parser.checkConsistency({'hardware':
2417 {'hal': {'devices': []}}})2424 {'hal': {'devices': []}}})
2418 self.assertEqual(result, False,2425 self.assertEqual(result, False,
@@ -2423,17 +2430,21 @@
24232430
2424 def testConsistencyCheckCircularParentChildRelation(self):2431 def testConsistencyCheckCircularParentChildRelation(self):
2425 """SubmissionParser.checkConsistency detects "orphaned" devices."""2432 """SubmissionParser.checkConsistency detects "orphaned" devices."""
2426 test = self2433 class MockSubmissionParserHALDevicesParentChildConsistency(
2427 def checkHALDevicesParentChildConsistency(self, devices):2434 self.MockSubmissionParser):
2428 test.assertTrue(isinstance(self, SubmissionParser))2435 """A parser where checkHALDevicesParentChildConsistency() fails.
2429 return ['/foo', '/bar']2436 """
24302437
2431 parser = self._setupConsistencyCheckParser()2438 def checkHALDevicesParentChildConsistency(self, udi_children):
2439 return ['/foo', '/bar']
2440
2441 validate_mock_class(
2442 MockSubmissionParserHALDevicesParentChildConsistency)
2443
2444 parser = MockSubmissionParserHALDevicesParentChildConsistency(
2445 self.log)
2432 parser.submission_key = ('Consistency check detects circular '2446 parser.submission_key = ('Consistency check detects circular '
2433 'parent-child relationships')2447 'parent-child relationships')
2434 parser.checkHALDevicesParentChildConsistency = (
2435 lambda devices: checkHALDevicesParentChildConsistency(
2436 parser, devices))
2437 result = parser.checkConsistency({'hardware':2448 result = parser.checkConsistency({'hardware':
2438 {'hal': {'devices': []}}})2449 {'hal': {'devices': []}}})
2439 self.assertEqual(result, False,2450 self.assertEqual(result, False,
24402451
=== modified file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py'
--- lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py 2009-10-22 09:07:26 +0000
+++ lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py 2009-10-23 18:56:14 +0000
@@ -360,8 +360,8 @@
360 self.assertErrorMessage(360 self.assertErrorMessage(
361 parser.submission_key, "No udev root device defined")361 parser.submission_key, "No udev root device defined")
362362
363 def testKernelPackageName(self):363 def test_kernel_package_name_hal_data(self):
364 """Test of SubmissionParser.getKernelPackageName.364 """Test of SubmissionParser.kernel_package_name.
365365
366 Regular case.366 Regular case.
367 """367 """
@@ -388,17 +388,19 @@
388 },388 },
389 }389 }
390 parser.buildDeviceList(parser.parsed_data)390 parser.buildDeviceList(parser.parsed_data)
391 kernel_package = parser.getKernelPackageName()391 kernel_package = parser.kernel_package_name
392 self.assertEqual(kernel_package, self.KERNEL_PACKAGE,392 self.assertEqual(
393 'Unexpected result of SubmissionParser.getKernelPackageName. '393 self.KERNEL_PACKAGE, kernel_package,
394 'Unexpected value of SubmissionParser.kernel_package_name. '
394 'Expected linux-image-2.6.24-19-generic, got %r' % kernel_package)395 'Expected linux-image-2.6.24-19-generic, got %r' % kernel_package)
395396
396 self.assertEqual(len(self.handler.records), 0,397 self.assertEqual(
398 0, len(self.handler.records),
397 'One or more warning messages were logged by '399 'One or more warning messages were logged by '
398 'getKernelPackageName, where zero was expected.')400 'SubmissionParser.kernel_package_name, where zero was expected.')
399401
400 def testKernelPackageNameInconsistent(self):402 def test_kernel_package_hal_data_name_inconsistent(self):
401 """Test of SubmissionParser.getKernelPackageName.403 """Test of SubmissionParser.kernel_package_name.
402404
403 Test a name inconsistency.405 Test a name inconsistency.
404 """406 """
@@ -426,24 +428,24 @@
426 }428 }
427 parser.submission_key = 'Test of inconsistent kernel package name'429 parser.submission_key = 'Test of inconsistent kernel package name'
428 parser.buildDeviceList(parser.parsed_data)430 parser.buildDeviceList(parser.parsed_data)
429 kernel_package = parser.getKernelPackageName()431 kernel_package = parser.kernel_package_name
430 self.assertEqual(kernel_package, None,432 self.assertIs(None, kernel_package)
431 'Unexpected result of SubmissionParser.getKernelPackageName. '433 self.assertWarningMessage(
432 'Expected None, got %r' % kernel_package)434 parser.submission_key,
433 self.assertWarningMessage(parser.submission_key,
434 'Inconsistent kernel version data: According to HAL the '435 'Inconsistent kernel version data: According to HAL the '
435 'kernel is 2.6.24-19-generic, but the submission does not '436 'kernel is 2.6.24-19-generic, but the submission does not '
436 'know about a kernel package linux-image-2.6.24-19-generic')437 'know about a kernel package linux-image-2.6.24-19-generic')
437 # The warning appears only once per submission, even if the438 # The warning appears only once per submission, even if the
438 # SubmissionParser.getKernelPackageName is called more than once.439 # property kernel_package_name is accessed more than once.
439 num_warnings = len(self.handler.records)440 num_warnings = len(self.handler.records)
440 parser.getKernelPackageName()441 test = parser.kernel_package_name
441 self.assertEqual(num_warnings, len(self.handler.records),442 self.assertEqual(
443 num_warnings, len(self.handler.records),
442 'Warning for missing HAL property system.kernel.version '444 'Warning for missing HAL property system.kernel.version '
443 'repeated.')445 'repeated.')
444446
445 def testKernelPackageNameNoHALData(self):447 def test_kernel_package_name_hal_data_no_kernel_version_in_hal_data(self):
446 """Test of SubmissionParser.getKernelPackageName.448 """Test of SubmissionParser.kernel_package_name.
447449
448 Test without HAL property system.kernel.version.450 Test without HAL property system.kernel.version.
449 """451 """
@@ -469,26 +471,28 @@
469 }471 }
470 parser.submission_key = 'Test: missing property system.kernel.version'472 parser.submission_key = 'Test: missing property system.kernel.version'
471 parser.buildDeviceList(parser.parsed_data)473 parser.buildDeviceList(parser.parsed_data)
472 kernel_package = parser.getKernelPackageName()474 self.assertIs(None, parser.kernel_package_name)
473 self.assertEqual(kernel_package, None,475 self.assertWarningMessage(
474 'Unexpected result of SubmissionParser.getKernelPackageName. '476 parser.submission_key,
475 'Expected None, got %r' % kernel_package)
476 self.assertWarningMessage(parser.submission_key,
477 'Submission does not provide property system.kernel.version '477 'Submission does not provide property system.kernel.version '
478 'for /org/freedesktop/Hal/devices/computer.')478 'for /org/freedesktop/Hal/devices/computer or a summary '
479 'sub-node <kernel-release>.')
479 # The warning appears only once per submission, even if the480 # The warning appears only once per submission, even if the
480 # SubmissionParser.getKernelPackageName is called more than once.481 # property kernel_package_name is accessed more than once.
481 num_warnings = len(self.handler.records)482 num_warnings = len(self.handler.records)
482 parser.getKernelPackageName()483 test = parser.kernel_package_name
483 self.assertEqual(num_warnings, len(self.handler.records),484 self.assertEqual(
485 num_warnings, len(self.handler.records),
484 'Warning for missing HAL property system.kernel.version '486 'Warning for missing HAL property system.kernel.version '
485 'repeated.')487 'repeated.')
486488
487 def testKernelPackageNameNoPackageData(self):489 def test_kernel_package_name_hal_data_no_package_data(self):
488 """Test of SubmissionParser.getKernelPackageName.490 """Test of SubmissionParser.kernel_package_name.
489491
490 Test without any package data. getKernelPackageName returns492 Test without any package data. In this case,
491 the property system.kernel.version without any further checking.493 SubmissionParser.kernel_package_name is the value of the property
494 system.kernel.version if the root HAL device. No further checks
495 are done.
492 """496 """
493 parser = SubmissionParser(self.log)497 parser = SubmissionParser(self.log)
494 devices = [498 devices = [
@@ -512,8 +516,156 @@
512 }516 }
513 parser.submission_key = 'Test: missing property system.kernel.version'517 parser.submission_key = 'Test: missing property system.kernel.version'
514 parser.buildDeviceList(parser.parsed_data)518 parser.buildDeviceList(parser.parsed_data)
515 kernel_package = parser.getKernelPackageName()519 kernel_package = parser.kernel_package_name
516 self.assertEqual(kernel_package, self.KERNEL_PACKAGE,520 self.assertEqual(
521 self.KERNEL_PACKAGE, kernel_package,
522 'Unexpected result of SubmissionParser.getKernelPackageName, '
523 'test without any package data. Expected None, got %r'
524 % kernel_package)
525
526 def test_kernel_package_name_udev_data(self):
527 """Test of SubmissionParser.kernel_package_name for udev data.
528
529 Variant for udev data, regular case.
530 """
531 parser = SubmissionParser(self.log)
532 parser.parsed_data = {
533 'hardware': {
534 'udev': [
535 {'P': '/devices/LNXSYSTM:00'}
536 ],
537 'sysfs-attributes': {},
538 'dmi': {},
539 },
540 'software': {
541 'packages': {
542 self.KERNEL_PACKAGE: {},
543 },
544 },
545 'summary': {
546 'kernel-release': self.KERNEL_VERSION,
547 },
548 }
549 parser.buildDeviceList(parser.parsed_data)
550 kernel_package = parser.kernel_package_name
551 self.assertEqual(
552 self.KERNEL_PACKAGE, kernel_package,
553 'Unexpected value of SubmissionParser.kernel_package_name. '
554 'Expected linux-image-2.6.24-19-generic, got %r' % kernel_package)
555
556 self.assertEqual(
557 0, len(self.handler.records),
558 'One or more warning messages were logged by '
559 'SubmissionParser.kernel_package_name, where zero was expected.')
560
561 def test_kernel_package_udev_data_name_inconsistent(self):
562 """Test of SubmissionParser.kernel_package_name.
563
564 Variant for udev data, name inconsistency.
565 """
566 parser = SubmissionParser(self.log)
567 parser.parsed_data = {
568 'hardware': {
569 'udev': [
570 {'P': '/devices/LNXSYSTM:00'}
571 ],
572 'sysfs-attributes': {},
573 'dmi': {},
574 },
575 'software': {
576 'packages': {
577 'linux-image-from-obscure-external-source': {},
578 },
579 },
580 'summary': {
581 'kernel-release': self.KERNEL_VERSION,
582 },
583 }
584 parser.submission_key = 'Test of inconsistent kernel package name'
585 parser.buildDeviceList(parser.parsed_data)
586 kernel_package = parser.kernel_package_name
587 self.assertIs(None, kernel_package)
588 self.assertWarningMessage(
589 parser.submission_key,
590 'Inconsistent kernel version data: According to HAL the '
591 'kernel is 2.6.24-19-generic, but the submission does not '
592 'know about a kernel package linux-image-2.6.24-19-generic')
593 # The warning appears only once per submission, even if the
594 # property kernel_package_name is accessed more than once.
595 num_warnings = len(self.handler.records)
596 test = parser.kernel_package_name
597 self.assertEqual(
598 num_warnings, len(self.handler.records),
599 'Warning for missing HAL property system.kernel.version '
600 'repeated.')
601
602 def test_kernel_package_name_udev_data_no_kernel_version_in_summary(self):
603 """Test of SubmissionParser.kernel_package_name.
604
605 Test without the summary sub-node <kernel-release>.
606 """
607 parser = SubmissionParser(self.log)
608 parser.parsed_data = {
609 'hardware': {
610 'udev': [
611 {'P': '/devices/LNXSYSTM:00'}
612 ],
613 'sysfs-attributes': {},
614 'dmi': {},
615 },
616 'software': {
617 'packages': {
618 self.KERNEL_PACKAGE: {},
619 },
620 },
621 'summary': {},
622 }
623 parser.submission_key = 'Test: missing property system.kernel.version'
624 parser.buildDeviceList(parser.parsed_data)
625 self.assertIs(None, parser.kernel_package_name)
626 self.assertWarningMessage(
627 parser.submission_key,
628 'Submission does not provide property system.kernel.version '
629 'for /org/freedesktop/Hal/devices/computer or a summary '
630 'sub-node <kernel-release>.')
631 # The warning appears only once per submission, even if the
632 # property kernel_package_name is accessed more than once.
633 num_warnings = len(self.handler.records)
634 test = parser.kernel_package_name
635 self.assertEqual(
636 num_warnings, len(self.handler.records),
637 'Warning for missing HAL property system.kernel.version '
638 'repeated.')
639
640 def test_kernel_package_name_udev_data_no_package_data(self):
641 """Test of SubmissionParser.kernel_package_name.
642
643 Variant for udev data, test without any package data. In this case,
644 SubmissionParser.kernel_package_name is the value of the property
645 system.kernel.version if the root HAL device. No further checks
646 are done.
647 """
648 parser = SubmissionParser(self.log)
649 parser.parsed_data = {
650 'hardware': {
651 'udev': [
652 {'P': '/devices/LNXSYSTM:00'},
653 ],
654 'sysfs-attributes': {},
655 'dmi': {},
656 },
657 'software': {
658 'packages': {},
659 },
660 'summary': {
661 'kernel-release': self.KERNEL_VERSION,
662 },
663 }
664 parser.submission_key = 'Test: missing property system.kernel.version'
665 parser.buildDeviceList(parser.parsed_data)
666 kernel_package = parser.kernel_package_name
667 self.assertEqual(
668 self.KERNEL_PACKAGE, kernel_package,
517 'Unexpected result of SubmissionParser.getKernelPackageName, '669 'Unexpected result of SubmissionParser.getKernelPackageName, '
518 'test without any package data. Expected None, got %r'670 'test without any package data. Expected None, got %r'
519 % kernel_package)671 % kernel_package)
520672
=== 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 18:56:14 +0000
@@ -8,6 +8,7 @@
8from datetime import datetime, timedelta8from datetime import datetime, timedelta
9from pprint import pformat9from pprint import pformat
10import copy10import copy
11from inspect import getargspec, getmembers, getmro, isclass, ismethod
11import os12import os
12import shutil13import shutil
13import subprocess14import subprocess
@@ -744,3 +745,69 @@
744 tree.unlock()745 tree.unlock()
745746
746 return contents747 return contents
748
749def validate_mock_class(mock_class):
750 """Validate method signatures in mock classes derived from real classes.
751
752 We often use mock classes in tests which are derived from real
753 classes.
754
755 This function ensures that methods redefined in the mock
756 class have the same signature as the corresponding methods of
757 the base class.
758
759 >>> class A:
760 ...
761 ... def method_one(self, a):
762 ... pass
763
764 >>>
765 >>> class B(A):
766 ... def method_one(self, a):
767 ... pass
768 >>> validate_mock_class(B)
769
770 If a class derived from A defines method_one with a different
771 signature, we get an AssertionError.
772
773 >>> class C(A):
774 ... def method_one(self, a, b):
775 ... pass
776 >>> validate_mock_class(C)
777 Traceback (most recent call last):
778 ...
779 AssertionError: Different method signature for method_one:...
780
781 Even a parameter name must not be modified.
782
783 >>> class D(A):
784 ... def method_one(self, b):
785 ... pass
786 >>> validate_mock_class(D)
787 Traceback (most recent call last):
788 ...
789 AssertionError: Different method signature for method_one:...
790
791 If validate_mock_class() for anything but a class, we get an
792 AssertionError.
793
794 >>> validate_mock_class('a string')
795 Traceback (most recent call last):
796 ...
797 AssertionError: validate_mock_class() must be called for a class
798 """
799 assert isclass(mock_class), (
800 "validate_mock_class() must be called for a class")
801 base_classes = getmro(mock_class)
802 for name, obj in getmembers(mock_class):
803 if ismethod(obj):
804 for base_class in base_classes[1:]:
805 if name in base_class.__dict__:
806 mock_args = getargspec(obj)
807 real_args = getargspec(base_class.__dict__[name])
808 if mock_args != real_args:
809 raise AssertionError(
810 'Different method signature for %s: %r %r' % (
811 name, mock_args, real_args))
812 else:
813 break
747814
=== 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-23 18:56:14 +0000
@@ -0,0 +1,20 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Run the doc string tests."""
5
6import doctest
7
8from zope.testing.doctest import NORMALIZE_WHITESPACE, ELLIPSIS
9
10from canonical.launchpad.testing.systemdocs import LayeredDocFileSuite
11from canonical.testing import BaseLayer
12from lp import testing
13
14def test_suite():
15 suite = LayeredDocFileSuite(
16 layer=BaseLayer)
17 suite.addTest(doctest.DocTestSuite(
18 testing, optionflags=NORMALIZE_WHITESPACE|ELLIPSIS))
19 return suite
20