|
1 | 1 | import codecs |
2 | 2 | import io |
| 3 | +import os |
| 4 | +import tempfile |
3 | 5 | import _pyio as pyio |
4 | 6 | import threading |
5 | 7 | from unittest import TestCase |
@@ -170,3 +172,52 @@ def reset_worker(): |
170 | 172 | decoder.reset() |
171 | 173 |
|
172 | 174 | run_concurrently([decode_worker] * 2 + [reset_worker] * 2) |
| 175 | + |
| 176 | + |
| 177 | +class TestFileIO(TestCase): |
| 178 | + NTHREADS = 4 |
| 179 | + ITERS = 200 |
| 180 | + |
| 181 | + def _run_io_vs_close(self, open_mode: str, io_func, data: bytes = b"") -> None: |
| 182 | + """Repeatedly race io_func against close on a fresh FileIO object.""" |
| 183 | + for _ in range(self.ITERS): |
| 184 | + with tempfile.NamedTemporaryFile(delete=False) as tmp: |
| 185 | + if data: |
| 186 | + tmp.write(data) |
| 187 | + name = tmp.name |
| 188 | + try: |
| 189 | + f = io.FileIO(name, open_mode) |
| 190 | + |
| 191 | + def io_worker(f: io.FileIO = f) -> None: |
| 192 | + for _ in range(10): |
| 193 | + try: |
| 194 | + io_func(f) |
| 195 | + except (ValueError, OSError): |
| 196 | + pass |
| 197 | + |
| 198 | + def closer(f: io.FileIO = f) -> None: |
| 199 | + try: |
| 200 | + f.close() |
| 201 | + except OSError: |
| 202 | + pass |
| 203 | + |
| 204 | + run_concurrently([io_worker] * self.NTHREADS + [closer]) |
| 205 | + finally: |
| 206 | + os.unlink(name) |
| 207 | + |
| 208 | + @threading_helper.requires_working_threading() |
| 209 | + def test_concurrent_read_and_close(self): |
| 210 | + self._run_io_vs_close("rb", lambda f: f.read(256), data=b"x" * 4096) |
| 211 | + |
| 212 | + @threading_helper.requires_working_threading() |
| 213 | + def test_concurrent_readinto_and_close(self): |
| 214 | + buf = bytearray(256) |
| 215 | + self._run_io_vs_close("rb", lambda f: f.readinto(buf), data=b"x" * 4096) |
| 216 | + |
| 217 | + @threading_helper.requires_working_threading() |
| 218 | + def test_concurrent_write_and_close(self): |
| 219 | + self._run_io_vs_close("wb", lambda f: f.write(b"x" * 256)) |
| 220 | + |
| 221 | + @threading_helper.requires_working_threading() |
| 222 | + def test_concurrent_seek_and_close(self): |
| 223 | + self._run_io_vs_close("rb", lambda f: f.seek(0), data=b"x" * 256) |
0 commit comments