~sschwarzer/ftputil

ftputil/test/test_with_statement.py -rw-r--r-- 6.7 KiB
77f2ca24Stefan Schwarzer Move item "Push to repository" a month ago
                                                                                
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# Copyright (C) 2008-2019, Stefan Schwarzer <sschwarzer@sschwarzer.net>
# and ftputil contributors (see `doc/contributors.txt`)
# See the file LICENSE for licensing terms.

import ftplib
import io
import pytest

import ftputil.error

import test.scripted_session as scripted_session
from test import test_base


Call = scripted_session.Call


# Exception raised by client code, i. e. code using ftputil. Used to test the
# behavior in case of client exceptions.
class ClientCodeException(Exception):
    pass


#
# Test cases
#
class TestHostContextManager:
    def test_normal_operation(self):
        """
        If an `FTPHost` instance is created, it should be closed by the host
        context manager.
        """
        script = [
            Call(method_name="__init__"),
            Call(method_name="pwd", result="/"),
            Call(method_name="close"),
        ]
        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
            assert host.closed is False
        assert host.closed is True

    def test_ftputil_exception(self):
        """
        If an `ftplib.FTP` method raises an exception, it should be caught by
        the host context manager and the host object should be closed.
        """
        script = [
            # Since `__init__` raises an exception, `pwd` isn't called.
            # However, `close` is called via the context manager.
            Call(method_name="__init__", result=ftplib.error_perm),
            Call(method_name="close"),
        ]
        with pytest.raises(ftputil.error.FTPOSError):
            with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
                pass
        # We arrived here, that's fine. Because the `FTPHost` object wasn't
        # successfully constructed, the assignment to `host` shouldn't have
        # happened.
        assert "host" not in locals()

    def test_client_code_exception(self):
        """
        If client code raises an exception in the context manager block, the
        host object should be closed by the context manager.
        """
        script = [
            Call(method_name="__init__"),
            Call(method_name="pwd", result="/"),
            Call(method_name="close"),
        ]
        try:
            with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
                assert host.closed is False
                raise ClientCodeException()
        except ClientCodeException:
            assert host.closed is True
        else:
            pytest.fail("`ClientCodeException` not raised")


class TestFileContextManager:
    def test_normal_operation(self):
        """
        If an `FTPFile` object is created in the `FTPFile` context manager, the
        context manager should close the file at the end of the `with` block.
        """
        host_script = [
            Call(method_name="__init__"),
            Call(method_name="pwd", result="/"),
            Call(method_name="close"),
        ]
        file_script = [
            Call(method_name="__init__"),
            Call(method_name="pwd", result="/"),
            Call(method_name="cwd", result=None, args=("/",)),
            Call(method_name="voidcmd", result=None, args=("TYPE I",)),
            Call(
                method_name="transfercmd",
                result=io.StringIO("line 1\nline 2"),
                args=("RETR dummy", None),
            ),
            Call(method_name="voidresp", result=None),
            Call(method_name="close"),
        ]
        multisession_factory = scripted_session.factory(host_script, file_script)
        with test_base.ftp_host_factory(multisession_factory) as host:
            with host.open("dummy", "r") as fobj:
                assert fobj.closed is False
                data = fobj.readline()
                assert data == "line 1\n"
                assert fobj.closed is False
            assert fobj.closed is True

    def test_ftputil_exception(self):
        """
        If an `ftplib.FTP` method raises an exception, the `FTPFile` context
        manager should try to close the file.
        """
        host_script = [
            Call(method_name="__init__"),
            Call(method_name="pwd", result="/"),
            Call(method_name="close"),
        ]
        file_script = [
            Call(method_name="__init__"),
            Call(method_name="pwd", result="/"),
            Call(method_name="cwd", result=None, args=("/",)),
            Call(method_name="voidcmd", result=None, args=("TYPE I",)),
            # Raise exception. `voidresp` therefore won't be called, but
            # `close` will be called by the context manager.
            Call(
                method_name="transfercmd",
                result=ftplib.error_perm,
                args=("STOR inaccessible", None),
            ),
            # Call(method_name="voidresp", result=None),
            Call(method_name="close"),
        ]
        multisession_factory = scripted_session.factory(host_script, file_script)
        with test_base.ftp_host_factory(multisession_factory) as host:
            with pytest.raises(ftputil.error.FTPIOError):
                # This should fail.
                with host.open("/inaccessible", "w") as fobj:
                    pass
            # The file construction shouldn't have succeeded, so `fobj` should
            # be absent from the local namespace.
            assert "fobj" not in locals()

    def test_client_code_exception(self):
        """
        If client code raises an exception in the `FTPFile` context manager
        block, the file object should be closed by the context manager.
        """
        host_script = [
            Call(method_name="__init__"),
            Call(method_name="pwd", result="/"),
            Call(method_name="close"),
        ]
        file_script = [
            Call(method_name="__init__"),
            Call(method_name="pwd", result="/"),
            Call(method_name="cwd", result=None, args=("/",)),
            Call(method_name="voidcmd", result=None, args=("TYPE I",)),
            Call(
                method_name="transfercmd",
                result=io.BytesIO(b""),
                args=("RETR dummy", None),
            ),
            Call(method_name="voidresp", result=None),
            Call(method_name="close"),
        ]
        multisession_factory = scripted_session.factory(host_script, file_script)
        with test_base.ftp_host_factory(multisession_factory) as host:
            try:
                with host.open("dummy", "r") as fobj:
                    assert fobj.closed is False
                    raise ClientCodeException()
            except ClientCodeException:
                assert fobj.closed is True
            else:
                pytest.fail("`ClientCodeException` not raised")