|
5 | 5 | import json |
6 | 6 | import logging |
7 | 7 | import shlex |
| 8 | +import sys |
8 | 9 | import tarfile |
9 | 10 | import time |
10 | 11 | import uuid |
@@ -1745,6 +1746,157 @@ def ClientSession(self) -> _FakeHTTPSession: |
1745 | 1746 |
|
1746 | 1747 |
|
1747 | 1748 | class TestPtyExec: |
| 1749 | + @pytest.mark.asyncio |
| 1750 | + async def test_pty_exec_start_cancellation_closes_unregistered_http_session( |
| 1751 | + self, fake_sandbox: _FakeSandboxInstance |
| 1752 | + ) -> None: |
| 1753 | + from agents.extensions.sandbox.blaxel import sandbox as mod |
| 1754 | + |
| 1755 | + connect_started = asyncio.Event() |
| 1756 | + |
| 1757 | + class _BlockingSession: |
| 1758 | + def __init__(self) -> None: |
| 1759 | + self._closed = False |
| 1760 | + |
| 1761 | + async def ws_connect(self, url: str) -> None: |
| 1762 | + _ = url |
| 1763 | + connect_started.set() |
| 1764 | + await asyncio.Event().wait() |
| 1765 | + |
| 1766 | + async def close(self) -> None: |
| 1767 | + self._closed = True |
| 1768 | + |
| 1769 | + class _BlockingAiohttp: |
| 1770 | + WSMsgType = _FakeAiohttp.WSMsgType |
| 1771 | + |
| 1772 | + def __init__(self) -> None: |
| 1773 | + self.session: _BlockingSession | None = None |
| 1774 | + |
| 1775 | + def ClientSession(self) -> _BlockingSession: |
| 1776 | + self.session = _BlockingSession() |
| 1777 | + return self.session |
| 1778 | + |
| 1779 | + fake_aiohttp = _BlockingAiohttp() |
| 1780 | + session = _make_session(fake_sandbox) |
| 1781 | + |
| 1782 | + with patch.object(mod, "_import_aiohttp", return_value=fake_aiohttp): |
| 1783 | + task = asyncio.create_task(session.pty_exec_start("echo", "hello")) |
| 1784 | + await connect_started.wait() |
| 1785 | + task.cancel("connect-cancel") |
| 1786 | + |
| 1787 | + with pytest.raises(asyncio.CancelledError) as exc_info: |
| 1788 | + await task |
| 1789 | + |
| 1790 | + if sys.version_info >= (3, 11): |
| 1791 | + assert exc_info.value.args == ("connect-cancel",) |
| 1792 | + assert task.cancelled() |
| 1793 | + |
| 1794 | + assert fake_aiohttp.session is not None |
| 1795 | + assert fake_aiohttp.session._closed |
| 1796 | + assert session._pty_sessions == {} |
| 1797 | + assert session._reserved_pty_process_ids == set() |
| 1798 | + |
| 1799 | + @pytest.mark.asyncio |
| 1800 | + async def test_pty_exec_start_preserves_cancellation_during_cleanup( |
| 1801 | + self, fake_sandbox: _FakeSandboxInstance |
| 1802 | + ) -> None: |
| 1803 | + from agents.extensions.sandbox.blaxel import sandbox as mod |
| 1804 | + |
| 1805 | + cleanup_started = asyncio.Event() |
| 1806 | + allow_cleanup = asyncio.Event() |
| 1807 | + |
| 1808 | + class _TimeoutSession: |
| 1809 | + def __init__(self) -> None: |
| 1810 | + self._closed = False |
| 1811 | + |
| 1812 | + async def ws_connect(self, url: str) -> None: |
| 1813 | + _ = url |
| 1814 | + raise asyncio.TimeoutError() |
| 1815 | + |
| 1816 | + async def close(self) -> None: |
| 1817 | + cleanup_started.set() |
| 1818 | + await allow_cleanup.wait() |
| 1819 | + self._closed = True |
| 1820 | + |
| 1821 | + class _TimeoutAiohttp: |
| 1822 | + WSMsgType = _FakeAiohttp.WSMsgType |
| 1823 | + |
| 1824 | + def __init__(self) -> None: |
| 1825 | + self.session: _TimeoutSession | None = None |
| 1826 | + |
| 1827 | + def ClientSession(self) -> _TimeoutSession: |
| 1828 | + self.session = _TimeoutSession() |
| 1829 | + return self.session |
| 1830 | + |
| 1831 | + fake_aiohttp = _TimeoutAiohttp() |
| 1832 | + session = _make_session(fake_sandbox) |
| 1833 | + |
| 1834 | + with patch.object(mod, "_import_aiohttp", return_value=fake_aiohttp): |
| 1835 | + task = asyncio.create_task(session.pty_exec_start("echo", "hello")) |
| 1836 | + await cleanup_started.wait() |
| 1837 | + task.cancel("cleanup-cancel") |
| 1838 | + allow_cleanup.set() |
| 1839 | + |
| 1840 | + with pytest.raises(asyncio.CancelledError) as exc_info: |
| 1841 | + await task |
| 1842 | + |
| 1843 | + if sys.version_info >= (3, 11): |
| 1844 | + assert exc_info.value.args == ("cleanup-cancel",) |
| 1845 | + assert fake_aiohttp.session is not None |
| 1846 | + assert fake_aiohttp.session._closed |
| 1847 | + assert session._pty_sessions == {} |
| 1848 | + assert session._reserved_pty_process_ids == set() |
| 1849 | + |
| 1850 | + @pytest.mark.asyncio |
| 1851 | + async def test_pty_exec_start_preserves_cancellation_when_cleanup_fails( |
| 1852 | + self, fake_sandbox: _FakeSandboxInstance |
| 1853 | + ) -> None: |
| 1854 | + from agents.extensions.sandbox.blaxel import sandbox as mod |
| 1855 | + |
| 1856 | + cleanup_started = asyncio.Event() |
| 1857 | + allow_cleanup = asyncio.Event() |
| 1858 | + |
| 1859 | + class _FailingCleanupSession: |
| 1860 | + def __init__(self) -> None: |
| 1861 | + self._closed = False |
| 1862 | + |
| 1863 | + async def ws_connect(self, url: str) -> None: |
| 1864 | + _ = url |
| 1865 | + raise asyncio.TimeoutError() |
| 1866 | + |
| 1867 | + async def close(self) -> None: |
| 1868 | + cleanup_started.set() |
| 1869 | + await allow_cleanup.wait() |
| 1870 | + self._closed = True |
| 1871 | + raise RuntimeError("synthetic cleanup failure") |
| 1872 | + |
| 1873 | + class _FailingCleanupAiohttp: |
| 1874 | + WSMsgType = _FakeAiohttp.WSMsgType |
| 1875 | + |
| 1876 | + def __init__(self) -> None: |
| 1877 | + self.session: _FailingCleanupSession | None = None |
| 1878 | + |
| 1879 | + def ClientSession(self) -> _FailingCleanupSession: |
| 1880 | + self.session = _FailingCleanupSession() |
| 1881 | + return self.session |
| 1882 | + |
| 1883 | + fake_aiohttp = _FailingCleanupAiohttp() |
| 1884 | + session = _make_session(fake_sandbox) |
| 1885 | + |
| 1886 | + with patch.object(mod, "_import_aiohttp", return_value=fake_aiohttp): |
| 1887 | + task = asyncio.create_task(session.pty_exec_start("echo", "hello")) |
| 1888 | + await cleanup_started.wait() |
| 1889 | + task.cancel() |
| 1890 | + allow_cleanup.set() |
| 1891 | + |
| 1892 | + with pytest.raises(asyncio.CancelledError): |
| 1893 | + await task |
| 1894 | + |
| 1895 | + assert fake_aiohttp.session is not None |
| 1896 | + assert fake_aiohttp.session._closed |
| 1897 | + assert session._pty_sessions == {} |
| 1898 | + assert session._reserved_pty_process_ids == set() |
| 1899 | + |
1748 | 1900 | @pytest.mark.parametrize( |
1749 | 1901 | ("messages", "expected_output"), |
1750 | 1902 | [ |
|
0 commit comments