|
8 | 8 | from textwrap import dedent |
9 | 9 |
|
10 | 10 | import pytest |
| 11 | +from _pytest.fixtures import FixtureRequest |
11 | 12 |
|
12 | | -from anyio import open_process, run_process |
| 13 | +from anyio import CancelScope, ClosedResourceError, open_process, run_process |
13 | 14 | from anyio.streams.buffered import BufferedByteReceiveStream |
14 | 15 |
|
15 | 16 | pytestmark = pytest.mark.anyio |
@@ -176,3 +177,61 @@ async def test_run_process_inherit_stdout(capfd: pytest.CaptureFixture[str]) -> |
176 | 177 | out, err = capfd.readouterr() |
177 | 178 | assert out == "stdout-text" + os.linesep |
178 | 179 | assert err == "stderr-text" + os.linesep |
| 180 | + |
| 181 | + |
| 182 | +async def test_process_aexit_cancellation_doesnt_orphan_process() -> None: |
| 183 | + """ |
| 184 | + Regression test for #669. |
| 185 | +
|
| 186 | + Ensures that open_process.__aexit__() doesn't leave behind an orphan process when |
| 187 | + cancelled. |
| 188 | +
|
| 189 | + """ |
| 190 | + with CancelScope() as scope: |
| 191 | + async with await open_process( |
| 192 | + [sys.executable, "-c", "import time; time.sleep(1)"] |
| 193 | + ) as process: |
| 194 | + scope.cancel() |
| 195 | + |
| 196 | + assert process.returncode is not None |
| 197 | + assert process.returncode != 0 |
| 198 | + |
| 199 | + |
| 200 | +async def test_process_aexit_cancellation_closes_standard_streams( |
| 201 | + request: FixtureRequest, |
| 202 | + anyio_backend_name: str, |
| 203 | +) -> None: |
| 204 | + """ |
| 205 | + Regression test for #669. |
| 206 | +
|
| 207 | + Ensures that open_process.__aexit__() closes standard streams when cancelled. Also |
| 208 | + ensures that process.std{in.send,{out,err}.receive}() raise ClosedResourceError on a |
| 209 | + closed stream. |
| 210 | +
|
| 211 | + """ |
| 212 | + if anyio_backend_name == "asyncio": |
| 213 | + # Avoid pytest.xfail here due to https://github.com/pytest-dev/pytest/issues/9027 |
| 214 | + request.node.add_marker( |
| 215 | + pytest.mark.xfail(reason="#671 needs to be resolved first") |
| 216 | + ) |
| 217 | + |
| 218 | + with CancelScope() as scope: |
| 219 | + async with await open_process( |
| 220 | + [sys.executable, "-c", "import time; time.sleep(1)"] |
| 221 | + ) as process: |
| 222 | + scope.cancel() |
| 223 | + |
| 224 | + assert process.stdin is not None |
| 225 | + |
| 226 | + with pytest.raises(ClosedResourceError): |
| 227 | + await process.stdin.send(b"foo") |
| 228 | + |
| 229 | + assert process.stdout is not None |
| 230 | + |
| 231 | + with pytest.raises(ClosedResourceError): |
| 232 | + await process.stdout.receive(1) |
| 233 | + |
| 234 | + assert process.stderr is not None |
| 235 | + |
| 236 | + with pytest.raises(ClosedResourceError): |
| 237 | + await process.stderr.receive(1) |
0 commit comments