Merge lp:~lifeless/bzr/hpss_ratches into lp:~bzr/bzr/trunk-old

Proposed by Robert Collins
Status: Rejected
Rejected by: Robert Collins
Proposed branch: lp:~lifeless/bzr/hpss_ratches
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 276 lines (has conflicts)
Text conflict in bzrlib/tests/test_selftest.py
To merge this branch: bzr merge lp:~lifeless/bzr/hpss_ratches
Reviewer Review Type Date Requested Status
Martin Pool Disapprove
Review via email: mp+9905@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

My small yak shave for the day - do the TODO Andrew and I have had for
hpss ratchets. We had a warning we wanted adjacent to the ratchet
figure, and this patch preserves that but moves all the ratchet figures
to one place. Its not the only way to tackle it, but this seems fairly
small and clear.

-Rob

--

Revision history for this message
Martin Pool (mbp) wrote :

> My small yak shave for the day - do the TODO Andrew and I have had for
> hpss ratchets. We had a warning we wanted adjacent to the ratchet
> figure, and this patch preserves that but moves all the ratchet figures
> to one place. Its not the only way to tackle it, but this seems fairly
> small and clear.

It's nice to remove the duplication but this seems like a really convoluted way to do it.

The Ratchet doesn't add any value beyond just holding a callback.

Passing something that's almost but not quite the name of the test into a dictionary is just an invitation for them to get out of sync.

It seems like you want to separate out the definition of the length by moving it to a place people will not easily change it, but that's just a bit like security by obscurity.

I'd rather just see the assertion changed to something like self.check_network_effort(...) and then put a comment on that method about not changing it.

review: Disapprove
Revision history for this message
Robert Collins (lifeless) wrote :

On Mon, 2009-08-10 at 03:12 +0000, Martin Pool wrote:
>
>
> It's nice to remove the duplication but this seems like a really
> convoluted way to do it.
>
> The Ratchet doesn't add any value beyond just holding a callback.

Thats true. I could have used partial function application too, but I
felt that would be more obscure.

> Passing something that's almost but not quite the name of the test
> into a dictionary is just an invitation for them to get out of sync.

I don't see that happening, because people have to make explicit
changes.

> It seems like you want to separate out the definition of the length by
> moving it to a place people will not easily change it, but that's just
> a bit like security by obscurity.

Can you be clearer about the parallels here? I'm trying to make sure
that when people change the figure:
 - its obvious to reviewers that its a ratchet figure
 - whoever is changing it has read the text about changing the figures

> I'd rather just see the assertion changed to something like
> self.check_network_effort(...) and then put a comment on that method
> about not changing it.

This doesn't meet the goals at all, or we would have just done that at
the start.
-Rob

Revision history for this message
Martin Pool (mbp) wrote :

2009/8/10 Robert Collins <email address hidden>:

>> Passing something that's almost but not quite the name of the test
>> into a dictionary is just an invitation for them to get out of sync.
>
> I don't see that happening, because people have to make explicit
> changes.

My point is: you're introducing two quoted strings in different parts
of the codebase that need to be in sync with each other. Why? The
point is that the ratchet corresponds to the test and there is already
an object (the bound method) corresponding to that context.

>> It seems like you want to separate out the definition of the length by
>> moving it to a place people will not easily change it, but that's just
>> a bit like security by obscurity.
>
> Can you be clearer about the parallels here?

The parallel to security by obscurity (and it's not a very close one)
is: why did you put the check on the number of round trips about four
logical steps away from the place it's checked? Presumably so people
don't unwittingly change it, but I'm not sure it's a good tradeoff.

> I'm trying to make sure
> that when people change the figure:
>  - its obvious to reviewers that its a ratchet figure
>  - whoever is changing it has read the text about changing the figures

To me the simplest thing that could possibly work is a specific assertion, like

>> I'd rather just see the assertion changed to something like
>> self.check_network_effort(...) and then put a comment on that method
>> about not changing it.
>
> This doesn't meet the goals at all, or we would have just done that at
> the start.

To me that would clearly meet the first. I'm not sure if it would
ensure that the person had really thought about the consequences of
changing it, but it at least gives them a reasonable warning. If
someone's just going to randomly change things until the tests pass no
amount of comments are going to stop them.

So I'd like to work out what specific requirement a clear assertion
method would not meet.

Maybe the failure message could give some guidance for both the up and
down cases.

--
Martin <http://launchpad.net/~mbp/>

Revision history for this message
Robert Collins (lifeless) wrote :

On Mon, 2009-08-10 at 04:45 +0000, Martin Pool wrote:
> 2009/8/10 Robert Collins <email address hidden>:
>
> >> Passing something that's almost but not quite the name of the test
> >> into a dictionary is just an invitation for them to get out of sync.
> >
> > I don't see that happening, because people have to make explicit
> > changes.
>
> My point is: you're introducing two quoted strings in different parts
> of the codebase that need to be in sync with each other. Why? The
> point is that the ratchet corresponds to the test and there is already
> an object (the bound method) corresponding to that context.

Ok. By the way, the disapprove vote may have thrown me off; it felt like
you were saying you didn't want to get the problem fixed, but your
review was more of a resubmit. I wonder perhaps if we shouldn't use
disapprove except when the goal of the patch is wrong?

> >> It seems like you want to separate out the definition of the length by
> >> moving it to a place people will not easily change it, but that's just
> >> a bit like security by obscurity.
> >
> > Can you be clearer about the parallels here?
>
> The parallel to security by obscurity (and it's not a very close one)
> is: why did you put the check on the number of round trips about four
> logical steps away from the place it's checked? Presumably so people
> don't unwittingly change it, but I'm not sure it's a good tradeoff.

Thats precisely it - I want to draw attention to the conceptual issues
surrounding the ratchets.

> >> I'd rather just see the assertion changed to something like
> >> self.check_network_effort(...) and then put a comment on that method
> >> about not changing it.
> >
> > This doesn't meet the goals at all, or we would have just done that at
> > the start.
>
> To me that would clearly meet the first. I'm not sure if it would
> ensure that the person had really thought about the consequences of
> changing it, but it at least gives them a reasonable warning. If
> someone's just going to randomly change things until the tests pass no
> amount of comments are going to stop them.

I don't think we're dealing with hostile contributors. I don't think a
single line phrase can really get the concept across though.

> So I'd like to work out what specific requirement a clear assertion
> method would not meet.
>
> Maybe the failure message could give some guidance for both the up and
> down cases.

I think having a custom failure message is better than embedding
assertLength in each method, but it would still concern me vs moving the
assertions somewhere specific to hpss specific code.

Another way to approach it would be to move the tests - breaking our
'tests for X are in Y' general rule, but making it possible to document
the ratchet aspect once in a single test module.

-Rob

Revision history for this message
Martin Pool (mbp) wrote :

2009/8/10 Robert Collins <email address hidden>:
> Ok. By the way, the disapprove vote may have thrown me off; it felt like
> you were saying you didn't want to get the problem fixed, but your
> review was more of a resubmit. I wonder perhaps if we shouldn't use
> disapprove except when the goal of the patch is wrong?

Oh, ok. Probably. There's probably some kind of Launchpad ui deficit here too.

>> The parallel to security by obscurity (and it's not a very close one)
>> is: why did you put the check on the number of round trips about four
>> logical steps away from the place it's checked?  Presumably so people
>> don't unwittingly change it, but I'm not sure it's a good tradeoff.
>
> Thats precisely it - I want to draw attention to the conceptual issues
> surrounding the ratchets.

So let's assume our developers are smart, and aim to get it at at
least the level of "the interface encourages you to get it right."
If it turns out that regressions of these ratchets are actually
getting through review, then we can use a bigger hammer.

It's interesting to contemplate what we could do to avoid you ever
being able to get it wrong, but I don't think that making it harder to
change is enough.

This is only one aspect of defense against the dark ^w^w performance
regressions, and people can still break cases that aren't tested, such
as old formats or larger trees.

--
Martin <http://launchpad.net/~mbp/>

Unmerged revisions

4596. By Robert Collins

Remove duplicate warnings about hpss ratchets by centralising all the ratchets in bzrlib.tests. (Robert Collins)

4595. By Canonical.com Patch Queue Manager <email address hidden>

(mbp) small tweak to bundle_info tests

4594. By Canonical.com Patch Queue Manager <email address hidden>

(mbp) fix 2a test failure in merge directive cherrypick test

4593. By Canonical.com Patch Queue Manager <email address hidden>

(robertc) Partial overhaul of check to do less duplicate work.
 (Robert Collins)

4592. By Canonical.com Patch Queue Manager <email address hidden>

Updates to buildout.cfg etc to have 'make installer-all' start being
 the preferred way to build win32 installer.

4591. By Canonical.com Patch Queue Manager <email address hidden>

(jam) Small 'const' fixes for diff-delta.c (bug #408441)

4590. By Canonical.com Patch Queue Manager <email address hidden>

(Jelmer) Document bzr_transports property in the plugin API.

4589. By Canonical.com Patch Queue Manager <email address hidden>

(bialix) Include the image format plugins in the win32 all-in-one
 installer.

4588. By Canonical.com Patch Queue Manager <email address hidden>

(jam) Get bundles working with --2a (bug #393349)

4587. By Canonical.com Patch Queue Manager <email address hidden>

(mbp) further LockableFiles cleanups

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-08-28 22:24:40 +0000
+++ NEWS 2009-08-29 03:36:19 +0000
@@ -532,6 +532,10 @@
532Testing532Testing
533*******533*******
534534
535* HPSS ratchets are now centralised in the ``setup_smart_server_with_call_log``
536 helper method, removing the need to duplicate the warning about changing
537 them throughout the code base. (Robert Collins)
538
535* Merge directive cherrypick tests must use the same root id.539* Merge directive cherrypick tests must use the same root id.
536 (Martin Pool, #409684)540 (Martin Pool, #409684)
537541
538542
=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py 2009-08-28 21:05:31 +0000
+++ bzrlib/tests/__init__.py 2009-08-29 03:36:19 +0000
@@ -2298,7 +2298,12 @@
2298 self.reduceLockdirTimeout()2298 self.reduceLockdirTimeout()
22992299
2300 def setup_smart_server_with_call_log(self):2300 def setup_smart_server_with_call_log(self):
2301 """Sets up a smart server as the transport server with a call log."""2301 """Sets up a smart server as the transport server with a call log.
2302
2303 This is typically used for hpss regression tests, and in addition to
2304 setting up the smart server a collection of hpss ratchets are created
2305 at self.hpss_ratchets.
2306 """
2302 self.transport_server = server.SmartTCPServer_for_testing2307 self.transport_server = server.SmartTCPServer_for_testing
2303 self.hpss_calls = []2308 self.hpss_calls = []
2304 import traceback2309 import traceback
@@ -2310,6 +2315,23 @@
2310 CapturedCall(params, prefix_length))2315 CapturedCall(params, prefix_length))
2311 client._SmartClient.hooks.install_named_hook(2316 client._SmartClient.hooks.install_named_hook(
2312 'call', capture_hpss_call, None)2317 'call', capture_hpss_call, None)
2318 # These figures represent the amount of work to perform each use case.
2319 # It is entirely ok to reduce a figure if a test fails due to rpc_count
2320 # being too low. If the rpc_count increases, more network roundtrips
2321 # have become necessary for this use case. Please do not adjust these
2322 # figures upwards without agreement from bzr's network support
2323 # maintainers.
2324 self.hpss_ratchets = {
2325 'push_smart_non_stacked_streaming': Ratchet(self.assertLength, 9),
2326 'push_smart_stacked_streaming': Ratchet(self.assertLength, 14),
2327 'push_smart_tags_streaming': Ratchet(self.assertLength, 11),
2328 'init_on_format': Ratchet(self.assertLength, 2),
2329 'branch_from_trivial_same_server': Ratchet(self.assertLength, 38),
2330 'branch_from_trivial': Ratchet(self.assertLength, 10),
2331 'branch_from_trivial_stacked': Ratchet(self.assertLength, 15),
2332 'pull_smart_stacked': Ratchet(self.assertLength, 18),
2333 'init_repo': Ratchet(self.assertLength, 16),
2334 }
23132335
2314 def reset_smart_call_log(self):2336 def reset_smart_call_log(self):
2315 self.hpss_calls = []2337 self.hpss_calls = []
@@ -4025,6 +4047,35 @@
4025 return None4047 return None
40264048
40274049
4050class Ratchet(object):
4051 """A Ratchet separates an assertion from code that needs to check it.
4052
4053 This is useful to allow checking ratchet style assertions - such as that
4054 no more than N round trips are made during an operation, without having
4055 the expected values spread out over the code base where the can be changed
4056 without reading documentation about them.
4057
4058 A typical use is to prepare a Ratchet in setUp, and then test it later.
4059 """
4060
4061 def __init__(self, assertion, *args):
4062 """Create a ratchet.
4063
4064 :param assertion: The assertion to invoke when the ratchet is checked.
4065 :param *args: positional arguments to pass to the assertion.
4066 """
4067 self.assertion = assertion
4068 self.args = args
4069
4070 def check(self, *args):
4071 """Check the ratchet.
4072
4073 :param args: Additional arguments to pass to the ratchet. Typically the
4074 expected value of the underlying assertion.
4075 """
4076 return self.assertion(*(self.args + args))
4077
4078
4028class _HTTPSServerFeature(Feature):4079class _HTTPSServerFeature(Feature):
4029 """Some tests want an https Server, check if one is available.4080 """Some tests want an https Server, check if one is available.
40304081
40314082
=== modified file 'bzrlib/tests/blackbox/test_branch.py'
--- bzrlib/tests/blackbox/test_branch.py 2009-08-20 12:30:49 +0000
+++ bzrlib/tests/blackbox/test_branch.py 2009-08-29 03:36:20 +0000
@@ -353,12 +353,7 @@
353 self.reset_smart_call_log()353 self.reset_smart_call_log()
354 out, err = self.run_bzr(['branch', self.get_url('from'),354 out, err = self.run_bzr(['branch', self.get_url('from'),
355 self.get_url('target')])355 self.get_url('target')])
356 # This figure represent the amount of work to perform this use case. It356 self.hpss_ratchets['branch_from_trivial_same_server'].check(self.hpss_calls)
357 # is entirely ok to reduce this number if a test fails due to rpc_count
358 # being too low. If rpc_count increases, more network roundtrips have
359 # become necessary for this use case. Please do not adjust this number
360 # upwards without agreement from bzr's network support maintainers.
361 self.assertLength(38, self.hpss_calls)
362357
363 def test_branch_from_trivial_branch_streaming_acceptance(self):358 def test_branch_from_trivial_branch_streaming_acceptance(self):
364 self.setup_smart_server_with_call_log()359 self.setup_smart_server_with_call_log()
@@ -368,12 +363,7 @@
368 self.reset_smart_call_log()363 self.reset_smart_call_log()
369 out, err = self.run_bzr(['branch', self.get_url('from'),364 out, err = self.run_bzr(['branch', self.get_url('from'),
370 'local-target'])365 'local-target'])
371 # This figure represent the amount of work to perform this use case. It366 self.hpss_ratchets['branch_from_trivial'].check(self.hpss_calls)
372 # is entirely ok to reduce this number if a test fails due to rpc_count
373 # being too low. If rpc_count increases, more network roundtrips have
374 # become necessary for this use case. Please do not adjust this number
375 # upwards without agreement from bzr's network support maintainers.
376 self.assertLength(10, self.hpss_calls)
377367
378 def test_branch_from_trivial_stacked_branch_streaming_acceptance(self):368 def test_branch_from_trivial_stacked_branch_streaming_acceptance(self):
379 self.setup_smart_server_with_call_log()369 self.setup_smart_server_with_call_log()
@@ -388,12 +378,7 @@
388 self.reset_smart_call_log()378 self.reset_smart_call_log()
389 out, err = self.run_bzr(['branch', self.get_url('feature'),379 out, err = self.run_bzr(['branch', self.get_url('feature'),
390 'local-target'])380 'local-target'])
391 # This figure represent the amount of work to perform this use case. It381 self.hpss_ratchets['branch_from_trivial_stacked'].check(self.hpss_calls)
392 # is entirely ok to reduce this number if a test fails due to rpc_count
393 # being too low. If rpc_count increases, more network roundtrips have
394 # become necessary for this use case. Please do not adjust this number
395 # upwards without agreement from bzr's network support maintainers.
396 self.assertLength(15, self.hpss_calls)
397382
398383
399class TestRemoteBranch(TestCaseWithSFTPServer):384class TestRemoteBranch(TestCaseWithSFTPServer):
400385
=== modified file 'bzrlib/tests/blackbox/test_pull.py'
--- bzrlib/tests/blackbox/test_pull.py 2009-06-15 06:47:14 +0000
+++ bzrlib/tests/blackbox/test_pull.py 2009-08-29 03:36:20 +0000
@@ -382,12 +382,7 @@
382 self.reset_smart_call_log()382 self.reset_smart_call_log()
383 self.run_bzr(['pull', '-r', '1', self.get_url('stacked')],383 self.run_bzr(['pull', '-r', '1', self.get_url('stacked')],
384 working_dir='empty')384 working_dir='empty')
385 # This figure represent the amount of work to perform this use case. It385 self.hpss_ratchets['pull_smart_stacked'].check(self.hpss_calls)
386 # is entirely ok to reduce this number if a test fails due to rpc_count
387 # being too low. If rpc_count increases, more network roundtrips have
388 # become necessary for this use case. Please do not adjust this number
389 # upwards without agreement from bzr's network support maintainers.
390 self.assertLength(18, self.hpss_calls)
391 remote = Branch.open('stacked')386 remote = Branch.open('stacked')
392 self.assertEndsWith(remote.get_stacked_on_url(), '/parent')387 self.assertEndsWith(remote.get_stacked_on_url(), '/parent')
393388
394389
=== modified file 'bzrlib/tests/blackbox/test_push.py'
--- bzrlib/tests/blackbox/test_push.py 2009-08-27 22:17:35 +0000
+++ bzrlib/tests/blackbox/test_push.py 2009-08-29 03:36:20 +0000
@@ -214,12 +214,8 @@
214 t.commit(allow_pointless=True, message='first commit')214 t.commit(allow_pointless=True, message='first commit')
215 self.reset_smart_call_log()215 self.reset_smart_call_log()
216 self.run_bzr(['push', self.get_url('to-one')], working_dir='from')216 self.run_bzr(['push', self.get_url('to-one')], working_dir='from')
217 # This figure represent the amount of work to perform this use case. It217 self.hpss_ratchets['push_smart_non_stacked_streaming'].check(
218 # is entirely ok to reduce this number if a test fails due to rpc_count218 self.hpss_calls)
219 # being too low. If rpc_count increases, more network roundtrips have
220 # become necessary for this use case. Please do not adjust this number
221 # upwards without agreement from bzr's network support maintainers.
222 self.assertLength(9, self.hpss_calls)
223219
224 def test_push_smart_stacked_streaming_acceptance(self):220 def test_push_smart_stacked_streaming_acceptance(self):
225 self.setup_smart_server_with_call_log()221 self.setup_smart_server_with_call_log()
@@ -230,12 +226,8 @@
230 self.reset_smart_call_log()226 self.reset_smart_call_log()
231 self.run_bzr(['push', '--stacked', '--stacked-on', '../parent',227 self.run_bzr(['push', '--stacked', '--stacked-on', '../parent',
232 self.get_url('public')], working_dir='local')228 self.get_url('public')], working_dir='local')
233 # This figure represent the amount of work to perform this use case. It229 self.hpss_ratchets['push_smart_stacked_streaming'].check(
234 # is entirely ok to reduce this number if a test fails due to rpc_count230 self.hpss_calls)
235 # being too low. If rpc_count increases, more network roundtrips have
236 # become necessary for this use case. Please do not adjust this number
237 # upwards without agreement from bzr's network support maintainers.
238 self.assertLength(14, self.hpss_calls)
239 remote = branch.Branch.open('public')231 remote = branch.Branch.open('public')
240 self.assertEndsWith(remote.get_stacked_on_url(), '/parent')232 self.assertEndsWith(remote.get_stacked_on_url(), '/parent')
241233
@@ -246,12 +238,7 @@
246 t.branch.tags.set_tag('new-tag', rev_id)238 t.branch.tags.set_tag('new-tag', rev_id)
247 self.reset_smart_call_log()239 self.reset_smart_call_log()
248 self.run_bzr(['push', self.get_url('to-one')], working_dir='from')240 self.run_bzr(['push', self.get_url('to-one')], working_dir='from')
249 # This figure represent the amount of work to perform this use case. It241 self.hpss_ratchets['push_smart_tags_streaming'].check(self.hpss_calls)
250 # is entirely ok to reduce this number if a test fails due to rpc_count
251 # being too low. If rpc_count increases, more network roundtrips have
252 # become necessary for this use case. Please do not adjust this number
253 # upwards without agreement from bzr's network support maintainers.
254 self.assertLength(11, self.hpss_calls)
255242
256 def test_push_smart_with_default_stacking_url_path_segment(self):243 def test_push_smart_with_default_stacking_url_path_segment(self):
257 # If the default stacked-on location is a path element then branches244 # If the default stacked-on location is a path element then branches
258245
=== modified file 'bzrlib/tests/blackbox/test_shared_repository.py'
--- bzrlib/tests/blackbox/test_shared_repository.py 2009-08-12 23:52:15 +0000
+++ bzrlib/tests/blackbox/test_shared_repository.py 2009-08-29 03:36:20 +0000
@@ -114,9 +114,4 @@
114 # be fixed.114 # be fixed.
115 self.setup_smart_server_with_call_log()115 self.setup_smart_server_with_call_log()
116 self.run_bzr(['init-repo', self.get_url('repo')])116 self.run_bzr(['init-repo', self.get_url('repo')])
117 # This figure represent the amount of work to perform this use case. It117 self.hpss_ratchets['init_repo'].check(self.hpss_calls)
118 # is entirely ok to reduce this number if a test fails due to rpc_count
119 # being too low. If rpc_count increases, more network roundtrips have
120 # become necessary for this use case. Please do not adjust this number
121 # upwards without agreement from bzr's network support maintainers.
122 self.assertLength(16, self.hpss_calls)
123118
=== modified file 'bzrlib/tests/test_bzrdir.py'
--- bzrlib/tests/test_bzrdir.py 2009-07-10 07:14:02 +0000
+++ bzrlib/tests/test_bzrdir.py 2009-08-29 03:36:19 +0000
@@ -906,13 +906,7 @@
906 self.reset_smart_call_log()906 self.reset_smart_call_log()
907 instance = new_format.initialize_on_transport(transport)907 instance = new_format.initialize_on_transport(transport)
908 self.assertIsInstance(instance, remote.RemoteBzrDir)908 self.assertIsInstance(instance, remote.RemoteBzrDir)
909 rpc_count = len(self.hpss_calls)909 self.hpss_ratchets['init_on_format'].check(self.hpss_calls)
910 # This figure represent the amount of work to perform this use case. It
911 # is entirely ok to reduce this number if a test fails due to rpc_count
912 # being too low. If rpc_count increases, more network roundtrips have
913 # become necessary for this use case. Please do not adjust this number
914 # upwards without agreement from bzr's network support maintainers.
915 self.assertEqual(2, rpc_count)
916910
917911
918class TestFormat5(TestCaseWithTransport):912class TestFormat5(TestCaseWithTransport):
919913
=== modified file 'bzrlib/tests/test_selftest.py'
--- bzrlib/tests/test_selftest.py 2009-08-26 23:25:28 +0000
+++ bzrlib/tests/test_selftest.py 2009-08-29 03:36:20 +0000
@@ -2965,3 +2965,33 @@
2965 self.verbosity)2965 self.verbosity)
2966 tests.run_suite(suite, runner_class=MyRunner, stream=StringIO())2966 tests.run_suite(suite, runner_class=MyRunner, stream=StringIO())
2967 self.assertLength(1, calls)2967 self.assertLength(1, calls)
2968<<<<<<< TREE
2969=======
2970
2971 def test_done(self):
2972 """run_suite should call result.done()"""
2973 self.calls = 0
2974 def one_more_call(): self.calls += 1
2975 def test_function():
2976 pass
2977 test = unittest.FunctionTestCase(test_function)
2978 class InstrumentedTestResult(tests.ExtendedTestResult):
2979 def done(self): one_more_call()
2980 class MyRunner(tests.TextTestRunner):
2981 def run(self, test):
2982 return InstrumentedTestResult(self.stream, self.descriptions,
2983 self.verbosity)
2984 tests.run_suite(test, runner_class=MyRunner, stream=StringIO())
2985 self.assertEquals(1, self.calls)
2986
2987
2988class TestRatchet(tests.TestCase):
2989
2990 def test_ok(self):
2991 ratchet = tests.Ratchet(self.assertLength, 3)
2992 ratchet.check([1, 2, 3])
2993
2994 def test_not_ok(self):
2995 ratchet = tests.Ratchet(self.assertLength, 3)
2996 self.assertRaises(AssertionError, ratchet.check, [1, 2])
2997>>>>>>> MERGE-SOURCE