Skip to content

Commit fadd91b

Browse files
authored
Fix read_line to raise EOFError if nothing was read (#86)
1 parent 5cc7a99 commit fadd91b

File tree

3 files changed

+45
-9
lines changed

3 files changed

+45
-9
lines changed

src/pyproject_api/_backend.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,17 +112,18 @@ def run(argv):
112112
return 0
113113

114114

115-
def read_line():
115+
def read_line(fd=0):
116116
# for some reason input() seems to break (hangs forever) so instead we read byte by byte the unbuffered stream
117117
content = bytearray()
118118
while True:
119-
try:
120-
char = os.read(0, 1)
121-
except EOFError: # pragma: no cover # when the stdout is closed without exit
122-
break # pragma: no cover
123-
if char == b"\n": # pragma: no cover
119+
char = os.read(fd, 1)
120+
if not char:
121+
if not content:
122+
raise EOFError("EOF without reading anything") # we didn't get a line at all, let the caller know
123+
break
124+
if char == b"\n":
124125
break
125-
if char != b"\r": # pragma: win32 cover
126+
if char != b"\r":
126127
content += char
127128
return content
128129

src/pyproject_api/_backend.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ class BackendProxy:
1212
def _optional_commands(self) -> dict[str, bool]: ...
1313

1414
def run(argv: Sequence[str]) -> int: ...
15-
def read_line() -> bytearray: ...
15+
def read_line(fd: int = 0) -> bytearray: ...
1616
def flush() -> None: ...

tests/test_backend.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from __future__ import annotations
22

33
import json
4+
import os
45
from typing import TYPE_CHECKING, Any
56

67
import pytest
78

8-
from pyproject_api._backend import BackendProxy, run
9+
from pyproject_api._backend import BackendProxy, read_line, run
910

1011
if TYPE_CHECKING:
1112
from pathlib import Path
@@ -127,3 +128,37 @@ def fake_backend(name: str, *args: Any, **kwargs: Any) -> Any: # noqa: ARG001
127128
assert "Backend: run command dummy_command_b with args {'baz': 'qux'}" in captured.out
128129
assert "Backend: run command dummy_command_c with args {'win': 'wow'}" in captured.out
129130
assert "SystemExit: 2" in captured.err
131+
132+
133+
def test_read_line_success() -> None:
134+
r, w = os.pipe()
135+
try:
136+
line_in = b"this is a line\r\n"
137+
os.write(w, line_in)
138+
line_out = read_line(fd=r)
139+
assert line_out == bytearray(b"this is a line")
140+
finally:
141+
os.close(r)
142+
os.close(w)
143+
144+
145+
def test_read_line_eof_before_newline() -> None:
146+
r, w = os.pipe()
147+
try:
148+
line_in = b"this is a line"
149+
os.write(w, line_in)
150+
os.close(w)
151+
line_out = read_line(fd=r)
152+
assert line_out == bytearray(b"this is a line")
153+
finally:
154+
os.close(r)
155+
156+
157+
def test_read_line_eof_at_the_beginning() -> None:
158+
r, w = os.pipe()
159+
try:
160+
os.close(w)
161+
with pytest.raises(EOFError):
162+
read_line(fd=r)
163+
finally:
164+
os.close(r)

0 commit comments

Comments
 (0)