Merge lp:~gz/bzr/lazy_hook_test_cleanup_785054 into lp:bzr

Proposed by Martin Packman
Status: Work in progress
Proposed branch: lp:~gz/bzr/lazy_hook_test_cleanup_785054
Merge into: lp:bzr
Diff against target: 161 lines (+144/-0)
1 file modified
bzrlib/tests/test_selftest.py (+144/-0)
To merge this branch: bzr merge lp:~gz/bzr/lazy_hook_test_cleanup_785054
Reviewer Review Type Date Requested Status
bzr-core Pending
Review via email: mp+61586@code.launchpad.net

Description of the change

This branch isn't ready to land (it doesn't fix the bug), but I need some help working out what's going on with the hooks isolation logic.

The branch is motivated by this mistake in the existing code, which looks like a simple fix:

=== modified file 'bzrlib/tests/__init__.py'
--- old/bzrlib/tests/__init__.py 2011-05-17 17:03:58 +0000
+++ new/bzrlib/tests/__init__.py 2011-05-19 14:53:38 +0000
@@ -1699,7 +1699,7 @@
     def _restoreHooks(self):
         for klass, (name, hooks) in self._preserved_hooks.items():
             setattr(klass, name, hooks)
- hooks._lazy_hooks = self._preserved_lazy_hooks
+ bzrlib.hooks._lazy_hooks = self._preserved_lazy_hooks

     def knownFailure(self, reason):
         """This test has failed for some known reason."""

As the _preserved_lazy_hooks dict is never restored, I'd expect the added test_lazy_hooks_unregistered_lock to fail, but it passes with or without the change. Also, the two test_*_artificial cases fail to do what I expect them to whatever.

Not restoring the dictionary doesn't seem to break much in practice at the moment, but is another testcase lifetime complication.

To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

Oh hmm, I thought I commented here but we mostly discussed on IRC, don't hesitate to ping if you need more help.

Revision history for this message
Martin Packman (gz) wrote :

I've discussed this on IRC with John and Vincent, and will reduce the tests to a sane subset and try removing hook cleanup code and see if things stay working.

Unmerged revisions

5896. By Martin Packman

Split hook isolation tests into several cases in new class

5895. By Martin Packman

Add test for broken selftest lazy lock cleanup

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/tests/test_selftest.py'
2--- bzrlib/tests/test_selftest.py 2011-05-18 19:49:22 +0000
3+++ bzrlib/tests/test_selftest.py 2011-05-19 15:13:33 +0000
4@@ -43,6 +43,7 @@
5 branchbuilder,
6 bzrdir,
7 errors,
8+ hooks,
9 lockdir,
10 memorytree,
11 osutils,
12@@ -1005,6 +1006,149 @@
13 self.assertEquals(2, result.count)
14
15
16+class _TestingHooks(hooks.Hooks):
17+
18+ def __init__(self):
19+ hooks.Hooks.__init__(self, "bzrlib.tests.test_selftest", "_test_hooks")
20+ self.add_hook("go", "Called with a identifier string", (2, 4))
21+
22+
23+_test_hooks = _TestingHooks()
24+
25+
26+class TestHookIsolation(tests.TestCase):
27+ """Make sure use of hooks in tests stays within the testcase"""
28+
29+ def test_methods_called(self):
30+ """Ensure the basic hook isolation methods are called"""
31+ calls = []
32+ class InstrumentedTestCase(tests.TestCase):
33+ def _clear_hooks(self):
34+ super(InstrumentedTestCase, self)._clear_hooks()
35+ calls.append("_clear_hooks")
36+ def _restoreHooks(self):
37+ super(InstrumentedTestCase, self)._restoreHooks()
38+ calls.append("_restoreHooks")
39+ def test_pass(self):
40+ pass
41+ result = tests.ExtendedTestResult(StringIO(), None, None, None)
42+ InstrumentedTestCase("test_pass").run(result)
43+ self.assertEqual(calls, ["_clear_hooks", "_restoreHooks"])
44+
45+ def test_dict__lazy_hooks_restored(self):
46+ """The hooks._lazy_hooks dict should be different during the test run
47+
48+ Would prefer not to test this isolation implementation detail, but haven't
49+ found another way to write a failing test for an existing issue.
50+ """
51+ from bzrlib import hooks as _mod_hooks
52+ hook_dicts = {"before": _mod_hooks._lazy_hooks}
53+ class Test(tests.TestCase):
54+ def test_record__lazy_hooks(self):
55+ hook_dicts["during"] = _mod_hooks._lazy_hooks
56+ result = tests.ExtendedTestResult(StringIO(), None, None, None)
57+ Test("test_record__lazy_hooks").run(result)
58+ hook_dicts["after"] = _mod_hooks._lazy_hooks
59+ self.assertNotEqual(id(hook_dicts["before"]), id(hook_dicts["during"]))
60+ self.assertNotEqual(id(hook_dicts["after"]), id(hook_dicts["during"]))
61+ self.assertEqual(id(hook_dicts["before"]), id(hook_dicts["after"]))
62+
63+ def test_normal_hooks_unregistered_artificial(self):
64+ """A normal testing hook should be limited to the testcase lifetime
65+
66+ This fails because the hook never seems to get installed
67+ """
68+ def _trigger_hook(identifier):
69+ for hook in _test_hooks["go"]:
70+ hook(identifier)
71+ callback_log = StringIO()
72+ def _hook_callback(string):
73+ callback_log.write(string + "\n")
74+ class TestThatRegistersLazyHook(tests.TestCase):
75+ def test_lock_hook(inner_self):
76+ _test_hooks.install_named_hook(
77+ "go", _hook_callback, "test_lock_hook")
78+ _trigger_hook("inner")
79+ result = tests.ExtendedTestResult(StringIO(), None, None, None)
80+ TestThatRegistersLazyHook('test_lock_hook').run(result)
81+ _trigger_hook("outer")
82+ self.assertContainsString(callback_log.getvalue(), "inner")
83+ self.assertNotContainsString(callback_log.getvalue(), "outer")
84+
85+ def test_lazy_hooks_unregistered_artificial(self):
86+ """A lazy testing hook should be limited to the testcase lifetime
87+
88+ This fails because the hook escapes the inner test case
89+ """
90+ def _trigger_hook(identifier):
91+ for hook in _test_hooks["go"]:
92+ hook(identifier)
93+ callback_log = StringIO()
94+ def _hook_callback(string):
95+ callback_log.write(string + "\n")
96+ class TestThatRegistersLazyHook(tests.TestCase):
97+ def test_lock_hook(inner_self):
98+ hooks.install_lazy_named_hook("bzrlib.tests.test_selftest",
99+ "_test_hooks", "go", _hook_callback, "test_lock_hook")
100+ _trigger_hook("inner")
101+ result = tests.ExtendedTestResult(StringIO(), None, None, None)
102+ TestThatRegistersLazyHook('test_lock_hook').run(result)
103+ _trigger_hook("outer")
104+ self.assertContainsString(callback_log.getvalue(), "inner")
105+ self.assertNotContainsString(callback_log.getvalue(), "outer")
106+
107+ @staticmethod
108+ def _trigger_lock_hooks(lock_url):
109+ """Pretend to acquire and release a generic lock"""
110+ from bzrlib.lock import Lock, LockResult
111+ lock_result = LockResult(lock_url)
112+ for hook in Lock.hooks["lock_acquired"]:
113+ hook(lock_result)
114+ for hook in Lock.hooks["lock_released"]:
115+ hook(lock_result)
116+
117+ def test_normal_hooks_unregistered_lock(self):
118+ """A normal lock hook should be limited to the testcase lifetime"""
119+ from bzrlib.lock import Lock
120+ _trigger_hook = self._trigger_lock_hooks
121+ callback_log = StringIO()
122+ def _hook_callback(result):
123+ callback_log.write("lock %s\n" % (result.lock_url,))
124+ class TestThatRegistersLazyHook(tests.TestCase):
125+ def test_lock_hook(inner_self):
126+ Lock.hooks.install_named_hook(
127+ "lock_released", _hook_callback, "test_lock_hook")
128+ _trigger_hook("invalid:hook-inner")
129+ result = tests.ExtendedTestResult(StringIO(), None, None, None)
130+ TestThatRegistersLazyHook('test_lock_hook').run(result)
131+ self.assertContainsString(callback_log.getvalue(), "lock")
132+ callback_log.truncate(0)
133+ _trigger_hook("invalid:hook-outer")
134+ self.assertEqual("", callback_log.getvalue())
135+
136+ def test_lazy_hooks_unregistered_lock(self):
137+ """A lazy lock hook should be limited to the testcase lifetime
138+
139+ Somehow this test doesn't fail even if _restoreHooks doesn't set the
140+ hooks._lazy_hooks dictionary back to its previous instance.
141+ """
142+ _trigger_hook = self._trigger_lock_hooks
143+ callback_log = StringIO()
144+ def _hook_callback(result):
145+ callback_log.write("lock %s\n" % (result.lock_url,))
146+ class TestThatRegistersLazyHook(tests.TestCase):
147+ def test_lock_hook(inner_self):
148+ hooks.install_lazy_named_hook("bzrlib.lock", "Lock.hooks",
149+ "lock_released", _hook_callback, "test_lock_hook")
150+ _trigger_hook("invalid:hook-inner")
151+ result = tests.ExtendedTestResult(StringIO(), None, None, None)
152+ TestThatRegistersLazyHook('test_lock_hook').run(result)
153+ self.assertContainsString(callback_log.getvalue(), "lock")
154+ callback_log.truncate(0)
155+ _trigger_hook("invalid:hook-outer")
156+ self.assertEqual("", callback_log.getvalue())
157+
158+
159 class TestUnicodeFilenameFeature(tests.TestCase):
160
161 def test_probe_passes(self):