199 lines
4.9 KiB
Python
199 lines
4.9 KiB
Python
|
import io
|
||
|
import os
|
||
|
|
||
|
import pytest
|
||
|
from unittest import mock
|
||
|
from unittest.mock import sentinel
|
||
|
|
||
|
import trio
|
||
|
from trio import _core
|
||
|
from trio._file_io import AsyncIOWrapper, _FILE_SYNC_ATTRS, _FILE_ASYNC_METHODS
|
||
|
|
||
|
|
||
|
@pytest.fixture
|
||
|
def path(tmpdir):
|
||
|
return os.fspath(tmpdir.join("test"))
|
||
|
|
||
|
|
||
|
@pytest.fixture
|
||
|
def wrapped():
|
||
|
return mock.Mock(spec_set=io.StringIO)
|
||
|
|
||
|
|
||
|
@pytest.fixture
|
||
|
def async_file(wrapped):
|
||
|
return trio.wrap_file(wrapped)
|
||
|
|
||
|
|
||
|
def test_wrap_invalid():
|
||
|
with pytest.raises(TypeError):
|
||
|
trio.wrap_file(str())
|
||
|
|
||
|
|
||
|
def test_wrap_non_iobase():
|
||
|
class FakeFile:
|
||
|
def close(self): # pragma: no cover
|
||
|
pass
|
||
|
|
||
|
def write(self): # pragma: no cover
|
||
|
pass
|
||
|
|
||
|
wrapped = FakeFile()
|
||
|
assert not isinstance(wrapped, io.IOBase)
|
||
|
|
||
|
async_file = trio.wrap_file(wrapped)
|
||
|
assert isinstance(async_file, AsyncIOWrapper)
|
||
|
|
||
|
del FakeFile.write
|
||
|
|
||
|
with pytest.raises(TypeError):
|
||
|
trio.wrap_file(FakeFile())
|
||
|
|
||
|
|
||
|
def test_wrapped_property(async_file, wrapped):
|
||
|
assert async_file.wrapped is wrapped
|
||
|
|
||
|
|
||
|
def test_dir_matches_wrapped(async_file, wrapped):
|
||
|
attrs = _FILE_SYNC_ATTRS.union(_FILE_ASYNC_METHODS)
|
||
|
|
||
|
# all supported attrs in wrapped should be available in async_file
|
||
|
assert all(attr in dir(async_file) for attr in attrs if attr in dir(wrapped))
|
||
|
# all supported attrs not in wrapped should not be available in async_file
|
||
|
assert not any(
|
||
|
attr in dir(async_file) for attr in attrs if attr not in dir(wrapped)
|
||
|
)
|
||
|
|
||
|
|
||
|
def test_unsupported_not_forwarded():
|
||
|
class FakeFile(io.RawIOBase):
|
||
|
def unsupported_attr(self): # pragma: no cover
|
||
|
pass
|
||
|
|
||
|
async_file = trio.wrap_file(FakeFile())
|
||
|
|
||
|
assert hasattr(async_file.wrapped, "unsupported_attr")
|
||
|
|
||
|
with pytest.raises(AttributeError):
|
||
|
getattr(async_file, "unsupported_attr")
|
||
|
|
||
|
|
||
|
def test_sync_attrs_forwarded(async_file, wrapped):
|
||
|
for attr_name in _FILE_SYNC_ATTRS:
|
||
|
if attr_name not in dir(async_file):
|
||
|
continue
|
||
|
|
||
|
assert getattr(async_file, attr_name) is getattr(wrapped, attr_name)
|
||
|
|
||
|
|
||
|
def test_sync_attrs_match_wrapper(async_file, wrapped):
|
||
|
for attr_name in _FILE_SYNC_ATTRS:
|
||
|
if attr_name in dir(async_file):
|
||
|
continue
|
||
|
|
||
|
with pytest.raises(AttributeError):
|
||
|
getattr(async_file, attr_name)
|
||
|
|
||
|
with pytest.raises(AttributeError):
|
||
|
getattr(wrapped, attr_name)
|
||
|
|
||
|
|
||
|
def test_async_methods_generated_once(async_file):
|
||
|
for meth_name in _FILE_ASYNC_METHODS:
|
||
|
if meth_name not in dir(async_file):
|
||
|
continue
|
||
|
|
||
|
assert getattr(async_file, meth_name) is getattr(async_file, meth_name)
|
||
|
|
||
|
|
||
|
def test_async_methods_signature(async_file):
|
||
|
# use read as a representative of all async methods
|
||
|
assert async_file.read.__name__ == "read"
|
||
|
assert async_file.read.__qualname__ == "AsyncIOWrapper.read"
|
||
|
|
||
|
assert "io.StringIO.read" in async_file.read.__doc__
|
||
|
|
||
|
|
||
|
async def test_async_methods_wrap(async_file, wrapped):
|
||
|
for meth_name in _FILE_ASYNC_METHODS:
|
||
|
if meth_name not in dir(async_file):
|
||
|
continue
|
||
|
|
||
|
meth = getattr(async_file, meth_name)
|
||
|
wrapped_meth = getattr(wrapped, meth_name)
|
||
|
|
||
|
value = await meth(sentinel.argument, keyword=sentinel.keyword)
|
||
|
|
||
|
wrapped_meth.assert_called_once_with(
|
||
|
sentinel.argument, keyword=sentinel.keyword
|
||
|
)
|
||
|
assert value == wrapped_meth()
|
||
|
|
||
|
wrapped.reset_mock()
|
||
|
|
||
|
|
||
|
async def test_async_methods_match_wrapper(async_file, wrapped):
|
||
|
for meth_name in _FILE_ASYNC_METHODS:
|
||
|
if meth_name in dir(async_file):
|
||
|
continue
|
||
|
|
||
|
with pytest.raises(AttributeError):
|
||
|
getattr(async_file, meth_name)
|
||
|
|
||
|
with pytest.raises(AttributeError):
|
||
|
getattr(wrapped, meth_name)
|
||
|
|
||
|
|
||
|
async def test_open(path):
|
||
|
f = await trio.open_file(path, "w")
|
||
|
|
||
|
assert isinstance(f, AsyncIOWrapper)
|
||
|
|
||
|
await f.aclose()
|
||
|
|
||
|
|
||
|
async def test_open_context_manager(path):
|
||
|
async with await trio.open_file(path, "w") as f:
|
||
|
assert isinstance(f, AsyncIOWrapper)
|
||
|
assert not f.closed
|
||
|
|
||
|
assert f.closed
|
||
|
|
||
|
|
||
|
async def test_async_iter():
|
||
|
async_file = trio.wrap_file(io.StringIO("test\nfoo\nbar"))
|
||
|
expected = list(async_file.wrapped)
|
||
|
result = []
|
||
|
async_file.wrapped.seek(0)
|
||
|
|
||
|
async for line in async_file:
|
||
|
result.append(line)
|
||
|
|
||
|
assert result == expected
|
||
|
|
||
|
|
||
|
async def test_aclose_cancelled(path):
|
||
|
with _core.CancelScope() as cscope:
|
||
|
f = await trio.open_file(path, "w")
|
||
|
cscope.cancel()
|
||
|
|
||
|
with pytest.raises(_core.Cancelled):
|
||
|
await f.write("a")
|
||
|
|
||
|
with pytest.raises(_core.Cancelled):
|
||
|
await f.aclose()
|
||
|
|
||
|
assert f.closed
|
||
|
|
||
|
|
||
|
async def test_detach_rewraps_asynciobase():
|
||
|
raw = io.BytesIO()
|
||
|
buffered = io.BufferedReader(raw)
|
||
|
|
||
|
async_file = trio.wrap_file(buffered)
|
||
|
|
||
|
detached = await async_file.detach()
|
||
|
|
||
|
assert isinstance(detached, AsyncIOWrapper)
|
||
|
assert detached.wrapped is raw
|