Mercurial > hg
annotate tests/test-ctxmanager.py @ 27703:4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
This is similar in spirit to contextlib.nested in Python <= 2.6,
but uses an extra level of indirection to avoid its inability to
clean up if an __enter__ method raises an exception.
Why add this mechanism? It greatly simplifies scoped resource
management, and lets us eliminate several hundred lines of try/finally
blocks. In many of these cases the "finally" is separated from the
"try" by hundreds of lines of code, which makes the connection
between resource acquisition and disposal difficult to follow.
(The preferred mechanism would be the "multi-with" syntax of 2.7+,
but Mercurial can't move to 2.7 for a while.)
Intended use:
>>> with ctxmanager(lambda: file('foo'), lambda: file('bar')) as c:
>>> f1, f2 = c()
This will open both foo and bar when c() is invoked, and will close
both upon exit from the block. If the attempt to open bar raises
an exception, the block will not be entered - but foo will still
be closed.
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Mon, 11 Jan 2016 15:25:43 -0800 |
parents | |
children | ba427b51f1d8 |
rev | line source |
---|---|
27703
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
1 from __future__ import absolute_import |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
2 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
3 import silenttestrunner |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
4 import unittest |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
5 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
6 from mercurial.util import ctxmanager |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
7 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
8 class contextmanager(object): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
9 def __init__(self, name, trace): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
10 self.name = name |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
11 self.entered = False |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
12 self.exited = False |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
13 self.trace = trace |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
14 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
15 def __enter__(self): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
16 self.entered = True |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
17 self.trace(('enter', self.name)) |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
18 return self |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
19 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
20 def __exit__(self, exc_type, exc_val, exc_tb): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
21 self.exited = exc_type, exc_val, exc_tb |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
22 self.trace(('exit', self.name)) |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
23 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
24 def __repr__(self): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
25 return '<ctx %r>' % self.name |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
26 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
27 class ctxerror(Exception): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
28 pass |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
29 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
30 class raise_on_enter(contextmanager): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
31 def __enter__(self): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
32 self.trace(('raise', self.name)) |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
33 raise ctxerror(self.name) |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
34 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
35 class raise_on_exit(contextmanager): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
36 def __exit__(self, exc_type, exc_val, exc_tb): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
37 self.trace(('raise', self.name)) |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
38 raise ctxerror(self.name) |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
39 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
40 def ctxmgr(name, trace): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
41 return lambda: contextmanager(name, trace) |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
42 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
43 class test_ctxmanager(unittest.TestCase): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
44 def test_basics(self): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
45 trace = [] |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
46 addtrace = trace.append |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
47 with ctxmanager(ctxmgr('a', addtrace), ctxmgr('b', addtrace)) as c: |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
48 a, b = c() |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
49 c.atexit(addtrace, ('atexit', 'x')) |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
50 c.atexit(addtrace, ('atexit', 'y')) |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
51 self.assertEqual(trace, [('enter', 'a'), ('enter', 'b'), |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
52 ('atexit', 'y'), ('atexit', 'x'), |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
53 ('exit', 'b'), ('exit', 'a')]) |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
54 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
55 def test_raise_on_enter(self): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
56 trace = [] |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
57 addtrace = trace.append |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
58 with self.assertRaises(ctxerror): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
59 with ctxmanager(ctxmgr('a', addtrace), |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
60 lambda: raise_on_enter('b', addtrace)) as c: |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
61 c() |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
62 addtrace('unreachable') |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
63 self.assertEqual(trace, [('enter', 'a'), ('raise', 'b'), ('exit', 'a')]) |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
64 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
65 def test_raise_on_exit(self): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
66 trace = [] |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
67 addtrace = trace.append |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
68 with self.assertRaises(ctxerror): |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
69 with ctxmanager(ctxmgr('a', addtrace), |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
70 lambda: raise_on_exit('b', addtrace)) as c: |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
71 c() |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
72 addtrace('running') |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
73 self.assertEqual(trace, [('enter', 'a'), ('enter', 'b'), 'running', |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
74 ('raise', 'b'), ('exit', 'a')]) |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
75 |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
76 if __name__ == '__main__': |
4e27c0a70574
util: introduce ctxmanager, to avoid nested try/finally blocks
Bryan O'Sullivan <bos@serpentine.com>
parents:
diff
changeset
|
77 silenttestrunner.main(__name__) |