Skip to content

Commit

Permalink
Fix readuntil work (#6810)
Browse files Browse the repository at this point in the history
<!-- Thank you for your contribution! -->

## What do these changes do?

Fix work `readuntil()` with separator length more one character
<!-- Please give a short brief about these changes. -->

## Are there changes in behavior for the user?
No
<!-- Outline any notable behaviour for the end users. -->

## Related issue number
Fixes #6701
<!-- Are there any issues opened that will be resolved by merging this
change? -->

## Checklist

- [x] I think the code is well written
- [x] Unit tests for the changes exist
- [x] Documentation reflects the changes
- [x] If you provide code modification, please add yourself to
`CONTRIBUTORS.txt`
  * The format is &lt;Name&gt; &lt;Surname&gt;.
  * Please keep alphabetical order, the file is sorted by names.
- [x] Add a new news fragment into the `CHANGES` folder
  * name it `<issue_id>.<type>` for example (588.bugfix)
* if you don't have an `issue_id` change it to the pr id after creating
the pr
  * ensure type is one of the following:
    * `.feature`: Signifying a new feature.
    * `.bugfix`: Signifying a bug fix.
    * `.doc`: Signifying a documentation improvement.
    * `.removal`: Signifying a deprecation or removal of public API.
* `.misc`: A ticket has been closed, but it is not of interest to users.
* Make sure to use full sentences with correct case and punctuation, for
example: "Fix issue with non-ascii contents in doctest text files."

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Sam Bull <aa6bs0@sambull.org>
  • Loading branch information
3 people authored Nov 10, 2022
1 parent 1f97c55 commit ec714c8
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 38 deletions.
1 change: 1 addition & 0 deletions CHANGES/6701.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix ``readuntil`` to work with a delimiter of more than one character
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ Mathieu Dugré
Matt VanEseltine
Matthieu Hauglustaine
Matthieu Rigal
Matvey Tingaev
Meet Mangukiya
Michael Ihnatenko
Michał Górny
Expand Down
4 changes: 3 additions & 1 deletion aiohttp/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,9 @@ async def readuntil(self, separator: bytes = b"\n") -> bytes:
offset = self._buffer_offset
ichar = self._buffer[0].find(separator, offset) + 1
# Read from current offset to found separator or to the end.
data = self._read_nowait_chunk(ichar - offset if ichar else -1)
data = self._read_nowait_chunk(
ichar - offset + seplen - 1 if ichar else -1
)
chunk += data
chunk_size += len(data)
if ichar:
Expand Down
85 changes: 48 additions & 37 deletions tests/test_streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,116 +380,127 @@ async def test_readline_exception(self) -> None:
with pytest.raises(ValueError):
await stream.readline()

async def test_readuntil(self) -> None:
@pytest.mark.parametrize("separator", [b"*", b"**"])
async def test_readuntil(self, separator: bytes) -> None:
loop = asyncio.get_event_loop()
# Read one chunk. 'readuntil' will need to wait for the data
# to come from 'cb'
stream = self._make_one()
stream.feed_data(b"chunk1 ")
read_task = loop.create_task(stream.readuntil(b"*"))
read_task = loop.create_task(stream.readuntil(separator))

def cb():
stream.feed_data(b"chunk2 ")
stream.feed_data(b"chunk3 ")
stream.feed_data(b"* chunk4")
stream.feed_data(separator + b" chunk4")

loop.call_soon(cb)

line = await read_task
assert b"chunk1 chunk2 chunk3 *" == line
assert b"chunk1 chunk2 chunk3 " + separator == line

stream.feed_eof()
data = await stream.read()
assert b" chunk4" == data

async def test_readuntil_limit_with_existing_data(self) -> None:
@pytest.mark.parametrize("separator", [b"&", b"&&"])
async def test_readuntil_limit_with_existing_data(self, separator: bytes) -> None:
# Read one chunk. The data is in StreamReader's buffer
# before the event loop is run.

stream = self._make_one(limit=2)
stream.feed_data(b"li")
stream.feed_data(b"ne1&line2&")
stream.feed_data(b"ne1" + separator + b"line2" + separator)

with pytest.raises(ValueError):
await stream.readuntil(b"&")
await stream.readuntil(separator)
# The buffer should contain the remaining data after exception
stream.feed_eof()
data = await stream.read()
assert b"line2&" == data
assert b"line2" + separator == data

async def test_readuntil_limit(self) -> None:
@pytest.mark.parametrize("separator", [b"$", b"$$"])
async def test_readuntil_limit(self, separator: bytes) -> None:
loop = asyncio.get_event_loop()
# Read one chunk. StreamReaders are fed with data after
# their 'readuntil' methods are called.
stream = self._make_one(limit=4)

def cb():
stream.feed_data(b"chunk1")
stream.feed_data(b"chunk2$")
stream.feed_data(b"chunk2" + separator)
stream.feed_data(b"chunk3#")
stream.feed_eof()

loop.call_soon(cb)

with pytest.raises(ValueError):
await stream.readuntil(b"$")
with pytest.raises(ValueError, match="Chunk too big"):
await stream.readuntil(separator)
data = await stream.read()
assert b"chunk3#" == data

async def test_readuntil_nolimit_nowait(self) -> None:
@pytest.mark.parametrize("separator", [b"!", b"!!"])
async def test_readuntil_nolimit_nowait(self, separator: bytes) -> None:
# All needed data for the first 'readuntil' call will be
# in the buffer.
seplen = len(separator)
stream = self._make_one()
data = b"line1!line2!line3!"
stream.feed_data(data[:6])
stream.feed_data(data[6:])
data = b"line1" + separator + b"line2" + separator + b"line3" + separator
stream.feed_data(data[: 5 + seplen])
stream.feed_data(data[5 + seplen :])

line = await stream.readuntil(b"!")
assert b"line1!" == line
line = await stream.readuntil(separator)
assert b"line1" + separator == line

stream.feed_eof()
data = await stream.read()
assert b"line2!line3!" == data
assert b"line2" + separator + b"line3" + separator == data

async def test_readuntil_eof(self) -> None:
@pytest.mark.parametrize("separator", [b"@", b"@@"])
async def test_readuntil_eof(self, separator: bytes) -> None:
stream = self._make_one()
stream.feed_data(b"some data")
stream.feed_eof()

line = await stream.readuntil(b"@")
line = await stream.readuntil(separator)
assert b"some data" == line

async def test_readuntil_empty_eof(self) -> None:
@pytest.mark.parametrize("separator", [b"@", b"@@"])
async def test_readuntil_empty_eof(self, separator: bytes) -> None:
stream = self._make_one()
stream.feed_eof()

line = await stream.readuntil(b"@")
line = await stream.readuntil(separator)
assert b"" == line

async def test_readuntil_read_byte_count(self) -> None:
@pytest.mark.parametrize("separator", [b"!", b"!!"])
async def test_readuntil_read_byte_count(self, separator: bytes) -> None:
seplen = len(separator)
stream = self._make_one()
data = b"line1!line2!line3!"
stream.feed_data(data)
stream.feed_data(
b"line1" + separator + b"line2" + separator + b"line3" + separator
)

await stream.readuntil(b"!")
await stream.readuntil(separator)

data = await stream.read(7)
assert b"line2!l" == data
data = await stream.read(6 + seplen)
assert b"line2" + separator + b"l" == data

stream.feed_eof()
data = await stream.read()
assert b"ine3!" == data
assert b"ine3" + separator == data

async def test_readuntil_exception(self) -> None:
@pytest.mark.parametrize("separator", [b"#", b"##"])
async def test_readuntil_exception(self, separator: bytes) -> None:
stream = self._make_one()
stream.feed_data(b"line#")
stream.feed_data(b"line" + separator)

data = await stream.readuntil(b"#")
assert b"line#" == data
data = await stream.readuntil(separator)
assert b"line" + separator == data

stream.set_exception(ValueError())
with pytest.raises(ValueError):
await stream.readuntil(b"#")
stream.set_exception(ValueError("Another exception"))
with pytest.raises(ValueError, match="Another exception"):
await stream.readuntil(separator)

async def test_readexactly_zero_or_less(self) -> None:
# Read exact number of bytes (zero or less).
Expand Down

0 comments on commit ec714c8

Please sign in to comment.