Merge lp:~jameinel/bzr/2.4-overridAttr-non-existant into lp:bzr/2.4

Proposed by John A Meinel
Status: Merged
Approved by: John A Meinel
Approved revision: no longer in the source branch.
Merged at revision: 6073
Proposed branch: lp:~jameinel/bzr/2.4-overridAttr-non-existant
Merge into: lp:bzr/2.4
Diff against target: 92 lines (+48/-6)
2 files modified
bzrlib/tests/__init__.py (+8/-2)
bzrlib/tests/test_selftest.py (+40/-4)
To merge this branch: bzr merge lp:~jameinel/bzr/2.4-overridAttr-non-existant
Reviewer Review Type Date Requested Status
Martin Packman (community) Approve
Review via email: mp+132871@code.launchpad.net

Commit message

Teach TestCase.overrideAttr how to handle attributes that don't exist yet.

Description of the change

This is a precursor branch for another fix I want to land.

Essentially, TestCase.overrideAttr didn't handle the case where you want to set an attribute and that attribute doesn't exist. (I'm using it in a follow up branch to force an OS level function to a known state.)

I'm just submitting it now because it should be clearer factored out into 2 patches.

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Looks fine, but I'm not sure having special logic for the case where new is uninited and value is uninited makes sense. That doesn't seem like something a test author would do deliberately, except perhaps if they were later in the test setting the attribute to something and wanted the cleanup at the end, in which case the value would leak through in this case.

review: Approve
Revision history for this message
Vincent Ladeuil (vila) wrote :

IIRC we didn't implement the unexisting attr case because we didn't need it.

Later on, testtools provided 'patch' which takes this case into account.

I think it would be better to use the testtools version for your specific case.

Also, unless you intend to cut a release for 2.4, I think we should avoid landing patches on stable releases for which we don't intend to make a release, the SRU process is resource intensive and we still haven't a 2.6.0 release yet...

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 11/6/2012 12:06 PM, Martin Packman wrote:
> Review: Approve
>
> Looks fine, but I'm not sure having special logic for the case
> where new is uninited and value is uninited makes sense. That
> doesn't seem like something a test author would do deliberately,
> except perhaps if they were later in the test setting the attribute
> to something and wanted the cleanup at the end, in which case the
> value would leak through in this case.
>

It happens explicitly on Windows when I'm testing that the test case
handles when ENOTSUP doesn't exist. Either I support that case, or I
have to add logic around:
 if getattr(foo, "bar", None) is not None:
    self.overrideAttr(foo, "bar")

In other words, I still need to only remove the attribute if it
exists, and I'd rather have that logic in one place.

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (Cygwin)
Comment: Using GnuPG with Mozilla - http://www.enigmail.net/

iEYEARECAAYFAlCYzZ4ACgkQJdeBCYSNAAOZ4gCgx2Z8Lk4Ud0wRXD9TlRb2cx8w
tVQAn28G5xt6eGbwuuzy+CeOrM55Fwtx
=em4F
-----END PGP SIGNATURE-----

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 11/6/2012 12:39 PM, Vincent Ladeuil wrote:
> IIRC we didn't implement the unexisting attr case because we didn't
> need it.
>
> Later on, testtools provided 'patch' which takes this case into
> account.
>
> I think it would be better to use the testtools version for your
> specific case.
>
> Also, unless you intend to cut a release for 2.4, I think we should
> avoid landing patches on stable releases for which we don't intend
> to make a release, the SRU process is resource intensive and we
> still haven't a 2.6.0 release yet...
>

So I intended to do the SSH fdatasync patch in the release that added
fdatasync (2.4). I'd rather have that patch in a 2.4 series that
people can download if they want even if we don't SRU it, rather than
only land it in something newer.

I don't plan on SRUing this, but I still feel it is worth landing it
in a branch. If we ever do a 2.4 SRU, then we have this patch
available, rather than having to think about it after the fact, and
possibly backport a fix.

I tried grepping through the codebase, and could find no references to
testtools.monkey.patch. Even in bzr.dev tip.

Also, reading the code in monkey.py it *doesn't* handle the delattr
case. At least in 0.9.14.final.0 that I have here.

John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (Cygwin)
Comment: Using GnuPG with Mozilla - http://www.enigmail.net/

iEYEARECAAYFAlCYz8sACgkQJdeBCYSNAANxmACg2DZAYhtSTKtWOMUScq5t5PMe
658AoNSuBXU/cFzZ4hB8O2CRYHjRKacm
=GGKp
-----END PGP SIGNATURE-----

Revision history for this message
Vincent Ladeuil (vila) wrote :

> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> On 11/6/2012 12:39 PM, Vincent Ladeuil wrote:
> > IIRC we didn't implement the unexisting attr case because we didn't
> > need it.
> >
> > Later on, testtools provided 'patch' which takes this case into
> > account.
> >
> > I think it would be better to use the testtools version for your
> > specific case.
> >
> > Also, unless you intend to cut a release for 2.4, I think we should
> > avoid landing patches on stable releases for which we don't intend
> > to make a release, the SRU process is resource intensive and we
> > still haven't a 2.6.0 release yet...
> >
>
> So I intended to do the SSH fdatasync patch in the release that added
> fdatasync (2.4). I'd rather have that patch in a 2.4 series that
> people can download if they want even if we don't SRU it, rather than
> only land it in something newer.

Daggy fixing as you did is enough (and a Good Thing anyway) for people to easily get the patch if they need.

>
> I don't plan on SRUing this,

Then don't make a potentially critical and required SRU harder by adding code that is not mandatory.

> I tried grepping through the codebase, and could find no references to
> testtools.monkey.patch. Even in bzr.dev tip.

Yes, because the existing overrideAttr cover all the needed cases.

>
> Also, reading the code in monkey.py it *doesn't* handle the delattr
> case. At least in 0.9.14.final.0 that I have here.

It is there in 0.9.12 and... still there on tip, check again (hint: restore()).

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

>> Also, reading the code in monkey.py it *doesn't* handle the
>> delattr case. At least in 0.9.14.final.0 that I have here.
>
> It is there in 0.9.12 and... still there on tip, check again (hint:
> restore()).
>

It will delete an attribute that did not previously exist. There is
*no way* to specify that you want to delete an attribute that already
exists. (Hint *patch*)

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (Cygwin)
Comment: Using GnuPG with Mozilla - http://www.enigmail.net/

iEYEARECAAYFAlCZV1oACgkQJdeBCYSNAANGQACfQP4RdjnkBz6nYnochK/lvd9J
x1UAnA7E/yJinS5cxkveUYCOqBqSwn/g
=nb5c
-----END PGP SIGNATURE-----

Revision history for this message
John A Meinel (jameinel) wrote :

sent to pqm by email

Revision history for this message
Vincent Ladeuil (vila) wrote :

> It will delete an attribute that did not previously exist. There is *no way* to specify that you want to delete an attribute that already exists. (Hint *patch*)

So you're taking the same syntax as 'patch' to mean something completely different ?

And you don't mention it in the docstring, news entry, cover letter ?

The fact that I mis-read your patch and that you fail to explain the difference with patch clearly demonstrate that other people will fall in the same trap.

Not to mention the other trap described by mgz.

A far safer and clearer idiom would be:

overrideAttr(obj, name) # Guarantees isolation
delattr(obj, name) # Testing what happens when name is deleted

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py 2011-11-30 22:47:17 +0000
+++ bzrlib/tests/__init__.py 2012-11-05 11:40:33 +0000
@@ -1751,9 +1751,15 @@
17511751
1752 :returns: The actual attr value.1752 :returns: The actual attr value.
1753 """1753 """
1754 value = getattr(obj, attr_name)
1755 # The actual value is captured by the call below1754 # The actual value is captured by the call below
1756 self.addCleanup(setattr, obj, attr_name, value)1755 value = getattr(obj, attr_name, _unitialized_attr)
1756 if value is _unitialized_attr:
1757 # When the test completes, the attribute should not exist, but if
1758 # we aren't setting a value, we don't need to do anything.
1759 if new is not _unitialized_attr:
1760 self.addCleanup(delattr, obj, attr_name)
1761 else:
1762 self.addCleanup(setattr, obj, attr_name, value)
1757 if new is not _unitialized_attr:1763 if new is not _unitialized_attr:
1758 setattr(obj, attr_name, new)1764 setattr(obj, attr_name, new)
1759 return value1765 return value
17601766
=== modified file 'bzrlib/tests/test_selftest.py'
--- bzrlib/tests/test_selftest.py 2011-10-26 16:42:12 +0000
+++ bzrlib/tests/test_selftest.py 2012-11-05 11:40:33 +0000
@@ -1651,6 +1651,12 @@
1651 self.assertRaises(AssertionError,1651 self.assertRaises(AssertionError,
1652 self.assertListRaises, _TestException, success_generator)1652 self.assertListRaises, _TestException, success_generator)
16531653
1654 def _run_successful_test(self, test):
1655 result = testtools.TestResult()
1656 test.run(result)
1657 self.assertTrue(result.wasSuccessful())
1658 return result
1659
1654 def test_overrideAttr_without_value(self):1660 def test_overrideAttr_without_value(self):
1655 self.test_attr = 'original' # Define a test attribute1661 self.test_attr = 'original' # Define a test attribute
1656 obj = self # Make 'obj' visible to the embedded test1662 obj = self # Make 'obj' visible to the embedded test
@@ -1666,8 +1672,7 @@
1666 obj.test_attr = 'modified'1672 obj.test_attr = 'modified'
1667 self.assertEqual('modified', obj.test_attr)1673 self.assertEqual('modified', obj.test_attr)
16681674
1669 test = Test('test_value')1675 self._run_successful_test(Test('test_value'))
1670 test.run(unittest.TestResult())
1671 self.assertEqual('original', obj.test_attr)1676 self.assertEqual('original', obj.test_attr)
16721677
1673 def test_overrideAttr_with_value(self):1678 def test_overrideAttr_with_value(self):
@@ -1683,10 +1688,41 @@
1683 self.assertEqual('original', self.orig)1688 self.assertEqual('original', self.orig)
1684 self.assertEqual('modified', obj.test_attr)1689 self.assertEqual('modified', obj.test_attr)
16851690
1686 test = Test('test_value')1691 self._run_successful_test(Test('test_value'))
1687 test.run(unittest.TestResult())
1688 self.assertEqual('original', obj.test_attr)1692 self.assertEqual('original', obj.test_attr)
16891693
1694 def test_overrideAttr_with_no_existing_value_and_value(self):
1695 # Do not define the test_attribute
1696 obj = self # Make 'obj' visible to the embedded test
1697 class Test(tests.TestCase):
1698
1699 def setUp(self):
1700 tests.TestCase.setUp(self)
1701 self.orig = self.overrideAttr(obj, 'test_attr', new='modified')
1702
1703 def test_value(self):
1704 self.assertEqual(tests._unitialized_attr, self.orig)
1705 self.assertEqual('modified', obj.test_attr)
1706
1707 self._run_successful_test(Test('test_value'))
1708 self.assertRaises(AttributeError, getattr, obj, 'test_attr')
1709
1710 def test_overrideAttr_with_no_existing_value_and_no_value(self):
1711 # Do not define the test_attribute
1712 obj = self # Make 'obj' visible to the embedded test
1713 class Test(tests.TestCase):
1714
1715 def setUp(self):
1716 tests.TestCase.setUp(self)
1717 self.orig = self.overrideAttr(obj, 'test_attr')
1718
1719 def test_value(self):
1720 self.assertEqual(tests._unitialized_attr, self.orig)
1721 self.assertRaises(AttributeError, getattr, obj, 'test_attr')
1722
1723 self._run_successful_test(Test('test_value'))
1724 self.assertRaises(AttributeError, getattr, obj, 'test_attr')
1725
1690 def test_recordCalls(self):1726 def test_recordCalls(self):
1691 from bzrlib.tests import test_selftest1727 from bzrlib.tests import test_selftest
1692 calls = self.recordCalls(1728 calls = self.recordCalls(

Subscribers

People subscribed via source and target branches