fabric / fabric
1
"""
2
`pytest <https://pytest.org>`_ fixtures for easy use of Fabric test helpers.
3

4
To get Fabric plus this module's dependencies (as well as those of the main
5
`fabric.testing.base` module which these fixtures wrap), ``pip install
6
fabric[pytest]``.
7

8
The simplest way to get these fixtures loaded into your test suite so Pytest
9
notices them is to import them into a ``conftest.py`` (`docs
10
<http://pytest.readthedocs.io/en/latest/fixture.html#conftest-py-sharing-fixture-functions>`_).
11
For example, if you intend to use the `remote` and `client` fixtures::
12

13
    from fabric.testing.fixtures import client, remote
14

15
.. versionadded:: 2.1
16
"""
17

18 6
try:
19 6
    from pytest import fixture
20 6
    from mock import patch, Mock
21 0
except ImportError:
22 0
    import warnings
23

24 0
    warning = (
25
        "You appear to be missing some optional test-related dependencies;"
26
        "please 'pip install fabric[pytest]'."
27
    )
28 0
    warnings.warn(warning, ImportWarning)
29 0
    raise
30

31 6
from .. import Connection
32 6
from ..transfer import Transfer
33

34
# TODO: if we find a lot of people somehow ending up _with_ pytest but
35
# _without_ mock and other deps from testing.base, consider doing the
36
# try/except here too. But, really?
37

38 6
from .base import MockRemote, MockSFTP
39

40

41 6
@fixture
42 2
def connection():
43
    """
44
    Yields a `.Connection` object with mocked methods.
45

46
    Specifically:
47

48
    - the hostname is set to ``"host"`` and the username to ``"user"``;
49
    - the primary API members (`.Connection.run`, `.Connection.local`, etc) are
50
      replaced with ``mock.Mock`` instances;
51
    - the ``run.in_stream`` config option is set to ``False`` to avoid attempts
52
      to read from stdin (which typically plays poorly with pytest and other
53
      capturing test runners);
54

55
    .. versionadded:: 2.1
56
    """
57 0
    c = Connection(host="host", user="user")
58 0
    c.config.run.in_stream = False
59 0
    c.run = Mock()
60 0
    c.local = Mock()
61
    # TODO: rest of API should get mocked too
62
    # TODO: is there a nice way to mesh with MockRemote et al? Is that ever
63
    # really that useful for code that just wants to assert about how run() and
64
    # friends were called?
65 0
    yield c
66

67

68
#: A convenience rebinding of `connection`.
69
#:
70
#: .. versionadded:: 2.1
71 6
cxn = connection
72

73

74 6
@fixture
75 2
def remote():
76
    """
77
    Fixture allowing setup of a mocked remote session & access to sub-mocks.
78

79
    Yields a `.MockRemote` object (which may need to be updated via
80
    `.MockRemote.expect`, `.MockRemote.expect_sessions`, etc; otherwise a
81
    default session will be used) & calls `.MockRemote.sanity` and
82
    `.MockRemote.stop` on teardown.
83

84
    .. versionadded:: 2.1
85
    """
86 6
    remote = MockRemote()
87 6
    yield remote
88 6
    remote.sanity()
89 6
    remote.stop()
90

91

92 6
@fixture
93 2
def sftp():
94
    """
95
    Fixture allowing setup of a mocked remote SFTP session.
96

97
    Yields a 3-tuple of: Transfer() object, SFTPClient object, and mocked OS
98
    module.
99

100
    For many/most tests which only want the Transfer and/or SFTPClient objects,
101
    see `sftp_objs` and `transfer` which wrap this fixture.
102

103
    .. versionadded:: 2.1
104
    """
105 6
    mock = MockSFTP(autostart=False)
106 6
    client, mock_os = mock.start()
107 6
    transfer = Transfer(Connection("host"))
108 6
    yield transfer, client, mock_os
109
    # TODO: old mock_sftp() lacked any 'stop'...why? feels bad man
110

111

112 6
@fixture
113 2
def sftp_objs(sftp):
114
    """
115
    Wrapper for `sftp` which only yields the Transfer and SFTPClient.
116

117
    .. versionadded:: 2.1
118
    """
119 6
    yield sftp[:2]
120

121

122 6
@fixture
123 2
def transfer(sftp):
124
    """
125
    Wrapper for `sftp` which only yields the Transfer object.
126

127
    .. versionadded:: 2.1
128
    """
129 6
    yield sftp[0]
130

131

132 6
@fixture
133 2
def client():
134
    """
135
    Mocks `~paramiko.client.SSHClient` for testing calls to ``connect()``.
136

137
    Yields a mocked ``SSHClient`` instance.
138

139
    This fixture updates `~paramiko.client.SSHClient.get_transport` to return a
140
    mock that appears active on first check, then inactive after, matching most
141
    tests' needs by default:
142

143
    - `.Connection` instantiates, with a None ``.transport``.
144
    - Calls to ``.open()`` test ``.is_connected``, which returns ``False`` when
145
      ``.transport`` is falsey, and so the first open will call
146
      ``SSHClient.connect`` regardless.
147
    - ``.open()`` then sets ``.transport`` to ``SSHClient.get_transport()``, so
148
      ``Connection.transport`` is effectively
149
      ``client.get_transport.return_value``.
150
    - Subsequent activity will want to think the mocked SSHClient is
151
      "connected", meaning we want the mocked transport's ``.active`` to be
152
      ``True``.
153
    - This includes `.Connection.close`, which short-circuits if
154
      ``.is_connected``; having a statically ``True`` active flag means a full
155
      open -> close cycle will run without error. (Only tests that double-close
156
      or double-open should have issues here.)
157

158
    End result is that:
159

160
    - ``.is_connected`` behaves False after instantiation and before ``.open``,
161
      then True after ``.open``
162
    - ``.close`` will work normally on 1st call
163
    - ``.close`` will behave "incorrectly" on subsequent calls (since it'll
164
      think connection is still live.) Tests that check the idempotency of
165
      ``.close`` will need to tweak their mock mid-test.
166

167
    For 'full' fake remote session interaction (i.e. stdout/err
168
    reading/writing, channel opens, etc) see `remote`.
169

170
    .. versionadded:: 2.1
171
    """
172 6
    with patch("fabric.connection.SSHClient") as SSHClient:
173 6
        client = SSHClient.return_value
174 6
        client.get_transport.return_value = Mock(active=True)
175 6
        yield client

Read our documentation on viewing source code .

Loading