(mocker: MockerFixture, lock: AsyncSoftFileLock)
| 767 | |
| 768 | @contextmanager |
| 769 | def _close_after_commit(mocker: MockerFixture, lock: AsyncSoftFileLock) -> Iterator[tuple[OSError, list[int]]]: |
| 770 | real_close = os.close |
| 771 | close_error = OSError(EINTR, "close failed") |
| 772 | attempts: list[int] = [] |
| 773 | # close is an attribute of the shared os module, so this patch binds for every close in the process while the |
| 774 | # window is open, including one a GC-run finalizer (a multiprocessing pipe from an earlier test, say) makes on this |
| 775 | # very thread. Fail only the descriptor this lock lets go of: it clears its bookkeeping right before the one close |
| 776 | # attempt, so record the descriptor as it does. |
| 777 | released: list[int | None] = [] |
| 778 | real_mark_released = lock._mark_descriptor_released |
| 779 | |
| 780 | def mark_released() -> None: |
| 781 | released.append(lock._context.lock_file_fd) |
| 782 | real_mark_released() |
| 783 | |
| 784 | def close(fd: int) -> None: |
| 785 | real_close(fd) |
| 786 | if fd not in released: |
| 787 | return |
| 788 | attempts.append(fd) |
| 789 | raise close_error |
| 790 | |
| 791 | mocker.patch.object(lock, "_mark_descriptor_released", side_effect=mark_released) |
| 792 | close_mock = mocker.patch("filelock._soft.os.close", side_effect=close) |
| 793 | try: |
| 794 | yield close_error, attempts |
| 795 | finally: |
| 796 | mocker.stop(close_mock) |
| 797 | |
| 798 | |
| 799 | @pytest.mark.asyncio |
no outgoing calls
no test coverage detected