~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# Copyright 2009, 2010 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

# pylint: disable-msg=E0211

"""Basic support for 'fixtures'.

In this case, 'fixture' means an object that has a setUp and a tearDown
method.
"""

__metaclass__ = type
__all__ = [
    'Fixtures',
    'FixtureWithCleanup',
    'IFixture',
    'run_with_fixture',
    'ServerFixture',
    'with_fixture',
    ]

from zope.component import getGlobalSiteManager, provideHandler
from twisted.python.util import mergeFunctionMetadata
from zope.interface import implements, Interface


class IFixture(Interface):
    """A fixture has a setUp and a tearDown method."""

    def setUp():
        """Set up the fixture."""

    def tearDown():
        """Tear down the fixture."""


class FixtureWithCleanup:
    """Fixture that allows arbitrary cleanup methods to be added.

    Subclass this if you'd like to define a fixture that calls 'addCleanup'.
    This is most often useful for fixtures that provide a way for users to
    acquire resources arbitrarily.

    Cleanups are run during 'tearDown' in reverse order to the order they were
    added. If any of the cleanups raise an error, this error will be bubbled
    up, causing tearDown to raise an exception, and the rest of the cleanups
    will be run in a finally block.
    """

    implements(IFixture)

    def setUp(self):
        """See `IFixture`."""
        self._cleanups = []

    def _runCleanups(self):
        if [] == self._cleanups:
            return
        f, args, kwargs = self._cleanups.pop()
        try:
            f(*args, **kwargs)
        finally:
            self._runCleanups()

    def tearDown(self):
        """See `IFixture`."""
        self._runCleanups()

    def addCleanup(self, function, *args, **kwargs):
        """Run 'function' with arguments during tear down."""
        self._cleanups.append((function, args, kwargs))


class Fixtures(FixtureWithCleanup):
    """A collection of `IFixture`s."""

    def __init__(self, fixtures):
        """Construct a fixture that groups many fixtures together.

        :param fixtures: A list of `IFixture` objects.
        """
        self._fixtures = fixtures

    def setUp(self):
        super(Fixtures, self).setUp()
        for fixture in self._fixtures:
            fixture.setUp()
            self.addCleanup(fixture.tearDown)


def with_fixture(fixture):
    """Decorate a function to run with a given fixture."""
    def decorator(f):
        def decorated(*args, **kwargs):
            return run_with_fixture(fixture, f, fixture, *args, **kwargs)
        return mergeFunctionMetadata(f, decorated)
    return decorator


def run_with_fixture(fixture, f, *args, **kwargs):
    """Run `f` within the given `fixture`."""
    try:
        fixture.setUp()
        return f(*args, **kwargs)
    finally:
        fixture.tearDown()


class ZopeEventHandlerFixture(FixtureWithCleanup):
    """A fixture that provides and then unprovides a Zope event handler."""

    def __init__(self, handler):
        self._handler = handler

    def setUp(self):
        super(ZopeEventHandlerFixture, self).setUp()
        gsm = getGlobalSiteManager()
        provideHandler(self._handler)
        self.addCleanup(gsm.unregisterHandler, self._handler)


class ServerFixture:
    """Adapt a bzrlib `Server` into an `IFixture`."""

    implements(IFixture)

    def __init__(self, server):
        self.server = server

    def setUp(self):
        self.server.start_server()

    def tearDown(self):
        self.server.stop_server()