diff --git a/CHANGES/5494.bugfix b/CHANGES/5494.bugfix new file mode 100644 index 00000000000..20db5581f46 --- /dev/null +++ b/CHANGES/5494.bugfix @@ -0,0 +1,4 @@ +Fixed the multipart POST requests processing to always release file +descriptors for the ``tempfile.Temporaryfile``-created +``_io.BufferedRandom`` instances of files sent within multipart request +bodies via HTTP POST requests -- by :user:`webknjaz`. diff --git a/CHANGES/5494.misc b/CHANGES/5494.misc new file mode 100644 index 00000000000..fc00335ff1f --- /dev/null +++ b/CHANGES/5494.misc @@ -0,0 +1,3 @@ +Made sure to always close most of file descriptors and release other +resouces in tests. Started ignoring ``ResourceWarning``s in pytest for +warnings that are hard to track -- by :user:`webknjaz`. diff --git a/aiohttp/web_request.py b/aiohttp/web_request.py index d48a37461c9..ed5aae1adf4 100644 --- a/aiohttp/web_request.py +++ b/aiohttp/web_request.py @@ -720,6 +720,7 @@ async def post(self) -> "MultiDictProxy[Union[str, bytes, FileField]]": tmp.write(chunk) size += len(chunk) if 0 < max_size < size: + tmp.close() raise HTTPRequestEntityTooLarge( max_size=max_size, actual_size=size ) diff --git a/setup.cfg b/setup.cfg index 55843384cf9..8e1fdfbbe04 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,6 +54,11 @@ addopts = filterwarnings = error ignore:module 'ssl' has no attribute 'OP_NO_COMPRESSION'. The Python interpreter is compiled against OpenSSL < 1.0.0. Ref. https.//docs.python.org/3/library/ssl.html#ssl.OP_NO_COMPRESSION:UserWarning + ignore:Exception ignored in. :pytest.PytestUnraisableExceptionWarning:_pytest.unraisableexception # Temporarily ignore warnings internal to Python 3.9.7, can be removed again in 3.9.8. ignore:The loop argument is deprecated since Python 3.8, and scheduled for removal in Python 3.10.:DeprecationWarning:asyncio junit_suite_name = aiohttp_test_suite diff --git a/tests/test_client_functional.py b/tests/test_client_functional.py index b0750fa561e..36ef35bdf3b 100644 --- a/tests/test_client_functional.py +++ b/tests/test_client_functional.py @@ -2581,6 +2581,8 @@ async def handler(request): with pytest.raises(TypeError): await aiohttp.request("GET", server.make_url("/")) + server.close() + async def test_yield_from_in_session_request(aiohttp_client) -> None: # a test for backward compatibility with yield from syntax @@ -2770,7 +2772,7 @@ def connection_lost(self, exc): await r.read() assert 0 == len(connector._conns) await session.close() - connector.close() + await connector.close() server.close() await server.wait_closed() @@ -2812,7 +2814,7 @@ def connection_lost(self, exc): assert 0 == len(connector._conns) await session.close() - connector.close() + await connector.close() server.close() await server.wait_closed() diff --git a/tests/test_client_request.py b/tests/test_client_request.py index 04ea8fa87e2..09136ad766a 100644 --- a/tests/test_client_request.py +++ b/tests/test_client_request.py @@ -279,6 +279,7 @@ def test_default_loop(loop) -> None: asyncio.set_event_loop(loop) req = ClientRequest("get", URL("http://python.org/")) assert req.loop is loop + loop.run_until_complete(req.close()) def test_default_headers_useragent(make_request) -> None: @@ -584,6 +585,8 @@ async def test_connection_header(loop, conn) -> None: await req.send(conn) assert req.headers.get("CONNECTION") == "close" + await req.close() + async def test_no_content_length(loop, conn) -> None: req = ClientRequest("get", URL("http://python.org"), loop=loop) @@ -606,6 +609,7 @@ async def test_content_type_auto_header_get(loop, conn) -> None: resp = await req.send(conn) assert "CONTENT-TYPE" not in req.headers resp.close() + await req.close() async def test_content_type_auto_header_form(loop, conn) -> None: @@ -615,6 +619,7 @@ async def test_content_type_auto_header_form(loop, conn) -> None: resp = await req.send(conn) assert "application/x-www-form-urlencoded" == req.headers.get("CONTENT-TYPE") resp.close() + await req.close() async def test_content_type_auto_header_bytes(loop, conn) -> None: @@ -622,6 +627,7 @@ async def test_content_type_auto_header_bytes(loop, conn) -> None: resp = await req.send(conn) assert "application/octet-stream" == req.headers.get("CONTENT-TYPE") resp.close() + await req.close() async def test_content_type_skip_auto_header_bytes(loop, conn) -> None: @@ -635,6 +641,7 @@ async def test_content_type_skip_auto_header_bytes(loop, conn) -> None: resp = await req.send(conn) assert "CONTENT-TYPE" not in req.headers resp.close() + await req.close() async def test_content_type_skip_auto_header_form(loop, conn) -> None: @@ -648,6 +655,7 @@ async def test_content_type_skip_auto_header_form(loop, conn) -> None: resp = await req.send(conn) assert "CONTENT-TYPE" not in req.headers resp.close() + await req.close() async def test_content_type_auto_header_content_length_no_skip(loop, conn) -> None: @@ -661,6 +669,7 @@ async def test_content_type_auto_header_content_length_no_skip(loop, conn) -> No resp = await req.send(conn) assert req.headers.get("CONTENT-LENGTH") == "3" resp.close() + await req.close() async def test_urlencoded_formdata_charset(loop, conn) -> None: @@ -674,6 +683,7 @@ async def test_urlencoded_formdata_charset(loop, conn) -> None: assert "application/x-www-form-urlencoded; charset=koi8-r" == req.headers.get( "CONTENT-TYPE" ) + await req.close() async def test_post_data(loop, conn) -> None: @@ -710,6 +720,7 @@ async def test_pass_falsy_data_file(loop, tmpdir) -> None: ) assert req.headers.get("CONTENT-LENGTH", None) is not None await req.close() + testfile.close() # Elasticsearch API requires to send request body with GET-requests @@ -908,6 +919,7 @@ async def test_expect100(loop, conn) -> None: assert req._continue is not None req.terminate() resp.close() + await req.close() async def test_expect_100_continue_header(loop, conn) -> None: @@ -919,6 +931,7 @@ async def test_expect_100_continue_header(loop, conn) -> None: assert req._continue is not None req.terminate() resp.close() + await req.close() async def test_data_stream(loop, buf, conn) -> None: @@ -1120,6 +1133,8 @@ async def test_oserror_on_write_bytes(loop, conn) -> None: exc = conn.protocol.set_exception.call_args[0][0] assert isinstance(exc, aiohttp.ClientOSError) + await req.close() + async def test_terminate(loop, conn) -> None: req = ClientRequest("get", URL("http://python.org"), loop=loop) @@ -1132,6 +1147,8 @@ async def test_terminate(loop, conn) -> None: writer.cancel.assert_called_with() resp.close() + await req.close() + def test_terminate_with_closed_loop(loop, conn) -> None: req = resp = writer = None @@ -1147,6 +1164,7 @@ async def go(): loop.run_until_complete(go()) + loop.run_until_complete(req.close()) loop.close() req.terminate() assert req._writer is None @@ -1161,6 +1179,8 @@ def test_terminate_without_writer(loop) -> None: req.terminate() assert req._writer is None + loop.run_until_complete(req.close()) + async def test_custom_req_rep(loop) -> None: conn = None @@ -1259,3 +1279,5 @@ def test_loose_cookies_types(loop) -> None: for loose_cookies_type in accepted_types: req.update_cookies(cookies=loose_cookies_type) + + loop.run_until_complete(req.close()) diff --git a/tests/test_client_response.py b/tests/test_client_response.py index 1798c5b5d16..f8bee42be49 100644 --- a/tests/test_client_response.py +++ b/tests/test_client_response.py @@ -46,6 +46,7 @@ async def test_http_processing_error(session) -> None: await response.start(connection) assert info.value.request_info is request_info + response.close() def test_del(session) -> None: diff --git a/tests/test_client_session.py b/tests/test_client_session.py index 4e2824b21a6..02219816c96 100644 --- a/tests/test_client_session.py +++ b/tests/test_client_session.py @@ -30,7 +30,7 @@ async def make_conn(): proto = mock.Mock() conn._conns["a"] = [(proto, 123)] yield conn - conn.close() + loop.run_until_complete(conn.close()) @pytest.fixture @@ -292,7 +292,7 @@ async def test_connector(create_session, loop, mocker) -> None: await session.close() assert connector.close.called - connector.close() + await connector.close() async def test_create_connector(create_session, loop, mocker) -> None: @@ -314,7 +314,6 @@ async def make_connector(): connector = another_loop.run_until_complete(make_connector()) - stack.enter_context(contextlib.closing(connector)) with pytest.raises(RuntimeError) as ctx: async def make_sess(): @@ -326,8 +325,11 @@ async def make_sess(): == str(ctx.value).strip() ) + # Cannot use `AsyncExitStack` as it's Python 3.7+: + another_loop.run_until_complete(connector.close()) -def test_detach(session) -> None: + +def test_detach(loop, session) -> None: conn = session.connector try: assert not conn.closed @@ -336,7 +338,7 @@ def test_detach(session) -> None: assert session.closed assert not conn.closed finally: - conn.close() + loop.run_until_complete(conn.close()) async def test_request_closed_session(session) -> None: @@ -345,10 +347,10 @@ async def test_request_closed_session(session) -> None: await session.request("get", "/") -def test_close_flag_for_closed_connector(session) -> None: +def test_close_flag_for_closed_connector(loop, session) -> None: conn = session.connector assert not session.closed - conn.close() + loop.run_until_complete(conn.close()) assert session.closed @@ -514,6 +516,7 @@ async def handler(request): async def test_session_default_version(loop) -> None: session = aiohttp.ClientSession(loop=loop) assert session.version == aiohttp.HttpVersion11 + await session.close() async def test_session_loop(loop) -> None: @@ -639,6 +642,8 @@ async def test_request_tracing_exception() -> None: ) assert not on_request_end.called + await session.close() + async def test_request_tracing_interpose_headers(loop, aiohttp_client) -> None: async def handler(request): @@ -681,6 +686,7 @@ async def test_client_session_custom_attr(loop) -> None: session = ClientSession(loop=loop) with pytest.warns(DeprecationWarning): session.custom = None + await session.close() async def test_client_session_timeout_args(loop) -> None: @@ -701,15 +707,20 @@ async def test_client_session_timeout_args(loop) -> None: loop=loop, timeout=client.ClientTimeout(total=10 * 60), conn_timeout=30 * 60 ) + await session1.close() + await session2.close() + async def test_client_session_timeout_default_args(loop) -> None: session1 = ClientSession() assert session1.timeout == client.DEFAULT_TIMEOUT + await session1.close() async def test_client_session_timeout_argument() -> None: session = ClientSession(timeout=500) assert session.timeout == 500 + await session.close() async def test_client_session_timeout_zero() -> None: @@ -724,11 +735,13 @@ async def test_client_session_timeout_zero() -> None: async def test_requote_redirect_url_default() -> None: session = ClientSession() assert session.requote_redirect_url + await session.close() async def test_requote_redirect_url_default_disable() -> None: session = ClientSession(requote_redirect_url=False) assert not session.requote_redirect_url + await session.close() async def test_requote_redirect_setter() -> None: @@ -737,3 +750,4 @@ async def test_requote_redirect_setter() -> None: with pytest.warns(DeprecationWarning): session.requote_redirect_url = False assert not session.requote_redirect_url + await session.close() diff --git a/tests/test_connector.py b/tests/test_connector.py index 52dd83bbd68..74034dcceeb 100644 --- a/tests/test_connector.py +++ b/tests/test_connector.py @@ -273,7 +273,7 @@ async def test_close(loop) -> None: conn = aiohttp.BaseConnector(loop=loop) assert not conn.closed conn._conns[("host", 8080, False)] = [(proto, object())] - conn.close() + await conn.close() assert not conn._conns assert proto.close.called @@ -287,7 +287,7 @@ async def test_get(loop) -> None: proto = mock.Mock() conn._conns[1] = [(proto, loop.time())] assert conn._get(1) == proto - conn.close() + await conn.close() async def test_get_unconnected_proto(loop) -> None: @@ -331,7 +331,7 @@ async def test_get_expired(loop) -> None: conn._conns[key] = [(proto, loop.time() - 1000)] assert conn._get(key) is None assert not conn._conns - conn.close() + await conn.close() async def test_get_expired_ssl(loop) -> None: @@ -345,7 +345,7 @@ async def test_get_expired_ssl(loop) -> None: assert conn._get(key) is None assert not conn._conns assert conn._cleanup_closed_transports == [transport] - conn.close() + await conn.close() async def test_release_acquired(loop, key) -> None: @@ -364,7 +364,7 @@ async def test_release_acquired(loop, key) -> None: assert 0 == len(conn._acquired) assert 0 == len(conn._acquired_per_host) - conn.close() + await conn.close() async def test_release_acquired_closed(loop, key) -> None: @@ -379,7 +379,7 @@ async def test_release_acquired_closed(loop, key) -> None: assert 1 == len(conn._acquired) assert 1 == len(conn._acquired_per_host[key]) assert not conn._release_waiter.called - conn.close() + await conn.close() async def test_release(loop, key) -> None: @@ -397,7 +397,7 @@ async def test_release(loop, key) -> None: assert conn._conns[key][0][0] == proto assert conn._conns[key][0][1] == pytest.approx(loop.time(), abs=0.1) assert not conn._cleanup_closed_transports - conn.close() + await conn.close() async def test_release_ssl_transport(loop, ssl_key) -> None: @@ -411,7 +411,7 @@ async def test_release_ssl_transport(loop, ssl_key) -> None: conn._release(ssl_key, proto, should_close=True) assert conn._cleanup_closed_transports == [transport] - conn.close() + await conn.close() async def test_release_already_closed(loop) -> None: @@ -420,7 +420,7 @@ async def test_release_already_closed(loop) -> None: proto = mock.Mock() key = 1 conn._acquired.add(proto) - conn.close() + await conn.close() conn._release_waiters = mock.Mock() conn._release_acquired = mock.Mock() @@ -439,7 +439,7 @@ async def test_release_waiter_no_limit(loop, key, key2) -> None: conn._release_waiter() assert len(conn._waiters[key]) == 0 assert w.done.called - conn.close() + await conn.close() async def test_release_waiter_first_available(loop, key, key2) -> None: @@ -456,7 +456,7 @@ async def test_release_waiter_first_available(loop, key, key2) -> None: or not w1.set_result.called and w2.set_result.called ) - conn.close() + await conn.close() async def test_release_waiter_release_first(loop, key, key2) -> None: @@ -468,7 +468,7 @@ async def test_release_waiter_release_first(loop, key, key2) -> None: conn._release_waiter() assert w1.set_result.called assert not w2.set_result.called - conn.close() + await conn.close() async def test_release_waiter_skip_done_waiter(loop, key, key2) -> None: @@ -480,7 +480,7 @@ async def test_release_waiter_skip_done_waiter(loop, key, key2) -> None: conn._release_waiter() assert not w1.set_result.called assert w2.set_result.called - conn.close() + await conn.close() async def test_release_waiter_per_host(loop, key, key2) -> None: @@ -495,7 +495,7 @@ async def test_release_waiter_per_host(loop, key, key2) -> None: assert (w1.set_result.called and not w2.set_result.called) or ( not w1.set_result.called and w2.set_result.called ) - conn.close() + await conn.close() async def test_release_waiter_no_available(loop, key, key2) -> None: @@ -508,7 +508,7 @@ async def test_release_waiter_no_available(loop, key, key2) -> None: conn._release_waiter() assert len(conn._waiters) == 1 assert not w.done.called - conn.close() + await conn.close() async def test_release_close(loop, key) -> None: @@ -661,7 +661,7 @@ def get_extra_info(param): conn._loop.create_connection = create_connection - await conn.connect(req, [], ClientTimeout()) + established_connection = await conn.connect(req, [], ClientTimeout()) assert ips == ips_tried assert os_error @@ -670,6 +670,8 @@ def get_extra_info(param): assert fingerprint_error assert connected + established_connection.close() + async def test_tcp_connector_resolve_host(loop) -> None: conn = aiohttp.TCPConnector(loop=loop, use_dns_cache=True) @@ -765,7 +767,7 @@ async def test_tcp_connector_dns_throttle_requests_cancelled_when_close( f = loop.create_task(conn._resolve_host("localhost", 8080)) await asyncio.sleep(0) - conn.close() + await conn.close() with pytest.raises(asyncio.CancelledError): await f @@ -980,7 +982,7 @@ async def test_release_close_do_not_delete_existing_connections(key) -> None: conn._release(key, proto) assert conn._conns[key] == [(proto1, 1)] assert proto.close.called - conn.close() + await conn.close() async def test_release_not_started(loop) -> None: @@ -994,7 +996,7 @@ async def test_release_not_started(loop) -> None: assert rec[0][0] == proto assert rec[0][1] == pytest.approx(loop.time(), abs=0.05) assert not proto.close.called - conn.close() + await conn.close() async def test_release_not_opened(loop, key) -> None: @@ -1074,7 +1076,7 @@ async def test_close_during_connect(loop) -> None: task = loop.create_task(conn.connect(req, None, ClientTimeout())) await asyncio.sleep(0) - conn.close() + await conn.close() fut.set_result(proto) with pytest.raises(aiohttp.ClientConnectionError): @@ -1143,7 +1145,7 @@ async def test_cleanup2() -> None: assert conn._cleanup_handle is not None loop.call_at.assert_called_with(310, mock.ANY, mock.ANY) - conn.close() + await conn.close() async def test_cleanup3(key) -> None: @@ -1161,7 +1163,7 @@ async def test_cleanup3(key) -> None: assert conn._cleanup_handle is not None loop.call_at.assert_called_with(319, mock.ANY, mock.ANY) - conn.close() + await conn.close() async def test_cleanup_closed(loop, mocker) -> None: @@ -1314,14 +1316,14 @@ async def test_close_twice(loop) -> None: conn = aiohttp.BaseConnector(loop=loop) conn._conns[1] = [(proto, object())] - conn.close() + await conn.close() assert not conn._conns assert proto.close.called assert conn.closed conn._conns = "Invalid" # fill with garbage - conn.close() + await conn.close() assert conn.closed @@ -1329,7 +1331,7 @@ async def test_close_cancels_cleanup_handle(loop) -> None: conn = aiohttp.BaseConnector(loop=loop) conn._release(1, mock.Mock(should_close=False)) assert conn._cleanup_handle is not None - conn.close() + await conn.close() assert conn._cleanup_handle is None @@ -1338,7 +1340,7 @@ async def test_close_abort_closed_transports(loop) -> None: conn = aiohttp.BaseConnector(loop=loop) conn._cleanup_closed_transports.append(tr) - conn.close() + await conn.close() assert not conn._cleanup_closed_transports assert tr.abort.called @@ -1348,7 +1350,7 @@ async def test_close_abort_closed_transports(loop) -> None: async def test_close_cancels_cleanup_closed_handle(loop) -> None: conn = aiohttp.BaseConnector(loop=loop, enable_cleanup_closed=True) assert conn._cleanup_closed_handle is not None - conn.close() + await conn.close() assert conn._cleanup_closed_handle is None @@ -1397,7 +1399,7 @@ async def f(): await asyncio.sleep(0) assert acquired await task - conn.close() + await conn.close() async def test_connect_queued_operation_tracing(loop, key) -> None: @@ -1443,7 +1445,7 @@ async def f(): await asyncio.sleep(0.01) connection1.release() await task - conn.close() + await conn.close() async def test_connect_reuseconn_tracing(loop, key) -> None: @@ -1473,7 +1475,7 @@ async def test_connect_reuseconn_tracing(loop, key) -> None: on_connection_reuseconn.assert_called_with( session, trace_config_ctx, aiohttp.TraceConnectionReuseconnParams() ) - conn.close() + await conn.close() async def test_connect_with_limit_and_limit_per_host(loop, key) -> None: @@ -1507,7 +1509,7 @@ async def f(): await asyncio.sleep(0) assert acquired await task - conn.close() + await conn.close() async def test_connect_with_no_limit_and_limit_per_host(loop, key) -> None: @@ -1539,7 +1541,7 @@ async def f(): await asyncio.sleep(0) assert acquired await task - conn.close() + await conn.close() async def test_connect_with_no_limits(loop, key) -> None: @@ -1571,7 +1573,7 @@ async def f(): assert acquired connection1.release() await task - conn.close() + await conn.close() async def test_connect_with_limit_cancelled(loop) -> None: @@ -1599,6 +1601,8 @@ async def test_connect_with_limit_cancelled(loop) -> None: await asyncio.wait_for(conn.connect(req, None, ClientTimeout()), 0.01) connection.close() + await conn.close() + async def test_connect_with_capacity_release_waiters(loop) -> None: def check_with_exc(err): @@ -1672,7 +1676,7 @@ async def f(start=True): await asyncio.wait(tasks) await f() - conn.close() + await conn.close() assert max_connections == num_connections @@ -1735,7 +1739,7 @@ async def test_close_with_acquired_connection(loop) -> None: connection = await conn.connect(req, None, ClientTimeout()) assert 1 == len(conn._acquired) - conn.close() + await conn.close() assert 0 == len(conn._acquired) assert conn.closed proto.close.assert_called_with() @@ -1754,26 +1758,26 @@ async def test_limit_property(loop) -> None: conn = aiohttp.BaseConnector(loop=loop, limit=15) assert 15 == conn.limit - conn.close() + await conn.close() async def test_limit_per_host_property(loop) -> None: conn = aiohttp.BaseConnector(loop=loop, limit_per_host=15) assert 15 == conn.limit_per_host - conn.close() + await conn.close() async def test_limit_property_default(loop) -> None: conn = aiohttp.BaseConnector(loop=loop) assert conn.limit == 100 - conn.close() + await conn.close() async def test_limit_per_host_property_default(loop) -> None: conn = aiohttp.BaseConnector(loop=loop) assert conn.limit_per_host == 0 - conn.close() + await conn.close() async def test_force_close_and_explicit_keep_alive(loop) -> None: @@ -2055,7 +2059,7 @@ async def handler(request): r.close() await session.close() - conn.close() + await conn.close() async def test_tcp_connector_uses_provided_local_addr(aiohttp_server) -> None: @@ -2079,7 +2083,7 @@ async def handler(request): assert first_conn.transport.get_extra_info("sockname") == ("127.0.0.1", port) r.close() await session.close() - conn.close() + await conn.close() @pytest.mark.skipif(not hasattr(socket, "AF_UNIX"), reason="requires UNIX sockets") @@ -2264,3 +2268,5 @@ async def allow_connection_and_add_dummy_waiter(): await_connection_and_check_waiters(), allow_connection_and_add_dummy_waiter(), ) + + await connector.close() diff --git a/tests/test_proxy.py b/tests/test_proxy.py index 1cdcc72067c..25bcb647fa2 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -73,6 +73,8 @@ async def make_conn(): ssl=None, ) + conn.close() + @mock.patch("aiohttp.connector.ClientRequest") def test_proxy_headers(self, ClientRequestMock) -> None: req = ClientRequest( @@ -113,6 +115,8 @@ async def make_conn(): ssl=None, ) + conn.close() + def test_proxy_auth(self) -> None: with self.assertRaises(ValueError) as ctx: ClientRequest( diff --git a/tests/test_proxy_functional.py b/tests/test_proxy_functional.py index 2f4ee56f0a7..e20a6961ac6 100644 --- a/tests/test_proxy_functional.py +++ b/tests/test_proxy_functional.py @@ -164,6 +164,9 @@ async def test_secure_https_proxy_absolute_path( await sess.close() await conn.close() + # https://docs.aiohttp.org/en/v3.8.0/client_advanced.html#graceful-shutdown + await asyncio.sleep(0.1) + @secure_proxy_xfail_under_py310_except_macos(raises=AssertionError) @pytest.mark.xfail( diff --git a/tests/test_run_app.py b/tests/test_run_app.py index e03a5fd6c90..44b27132605 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -646,27 +646,29 @@ def test_run_app_multiple_preexisting_sockets(patched_loop) -> None: def test_sigint() -> None: skip_if_on_windows() - proc = subprocess.Popen( - [sys.executable, "-u", "-c", _script_test_signal], stdout=subprocess.PIPE - ) - for line in proc.stdout: - if line.startswith(b"======== Running on"): - break - proc.send_signal(signal.SIGINT) - assert proc.wait() == 0 + with subprocess.Popen( + [sys.executable, "-u", "-c", _script_test_signal], + stdout=subprocess.PIPE, + ) as proc: + for line in proc.stdout: + if line.startswith(b"======== Running on"): + break + proc.send_signal(signal.SIGINT) + assert proc.wait() == 0 def test_sigterm() -> None: skip_if_on_windows() - proc = subprocess.Popen( - [sys.executable, "-u", "-c", _script_test_signal], stdout=subprocess.PIPE - ) - for line in proc.stdout: - if line.startswith(b"======== Running on"): - break - proc.terminate() - assert proc.wait() == 0 + with subprocess.Popen( + [sys.executable, "-u", "-c", _script_test_signal], + stdout=subprocess.PIPE, + ) as proc: + for line in proc.stdout: + if line.startswith(b"======== Running on"): + break + proc.terminate() + assert proc.wait() == 0 def test_startup_cleanup_signals_even_on_failure(patched_loop) -> None: diff --git a/tests/test_streams.py b/tests/test_streams.py index b2f909a8797..01e12d90c9b 100644 --- a/tests/test_streams.py +++ b/tests/test_streams.py @@ -89,6 +89,12 @@ def test_ctor_global_loop(self) -> None: assert stream._loop is loop + # Cleanup, leaks into `test_at_eof` otherwise: + loop.stop() + loop.run_forever() + loop.close() + gc.collect() + async def test_at_eof(self) -> None: stream = self._make_one() assert not stream.at_eof() diff --git a/tests/test_web_app.py b/tests/test_web_app.py index 9879fe83740..160d8ac81e7 100644 --- a/tests/test_web_app.py +++ b/tests/test_web_app.py @@ -1,4 +1,5 @@ import asyncio +import gc from unittest import mock import pytest @@ -48,6 +49,12 @@ def test_set_loop_default_loop() -> None: assert app.loop is loop asyncio.set_event_loop(None) + # Cleanup, leaks into `test_app_make_handler_debug_exc[True]` otherwise: + loop.stop() + loop.run_forever() + loop.close() + gc.collect() + def test_set_loop_with_different_loops() -> None: loop = asyncio.new_event_loop() @@ -59,6 +66,12 @@ def test_set_loop_with_different_loops() -> None: with pytest.raises(RuntimeError): app._set_loop(loop=object()) + # Cleanup, leaks into `test_app_make_handler_debug_exc[True]` otherwise: + loop.stop() + loop.run_forever() + loop.close() + gc.collect() + @pytest.mark.parametrize("debug", [True, False]) async def test_app_make_handler_debug_exc(mocker, debug) -> None: diff --git a/tests/test_web_functional.py b/tests/test_web_functional.py index 55dea84c301..88d0dfd1c7f 100644 --- a/tests/test_web_functional.py +++ b/tests/test_web_functional.py @@ -325,7 +325,8 @@ async def handler(request): fname = here / "data.unknown_mime_type" - resp = await client.post("/", data=[fname.open("rb")]) + with fname.open("rb") as fd: + resp = await client.post("/", data=[fd]) assert 200 == resp.status @@ -876,12 +877,15 @@ async def handler(request): async def test_response_with_file(aiohttp_client, fname) -> None: + outer_file_descriptor = None with fname.open("rb") as f: data = f.read() async def handler(request): - return web.Response(body=fname.open("rb")) + nonlocal outer_file_descriptor + outer_file_descriptor = fname.open("rb") + return web.Response(body=outer_file_descriptor) app = web.Application() app.router.add_get("/", handler) @@ -902,15 +906,21 @@ async def handler(request): assert resp.headers.get("Content-Length") == str(len(resp_data)) assert resp.headers.get("Content-Disposition") == expected_content_disposition + outer_file_descriptor.close() + async def test_response_with_file_ctype(aiohttp_client, fname) -> None: + outer_file_descriptor = None with fname.open("rb") as f: data = f.read() async def handler(request): + nonlocal outer_file_descriptor + outer_file_descriptor = fname.open("rb") + return web.Response( - body=fname.open("rb"), headers={"content-type": "text/binary"} + body=outer_file_descriptor, headers={"content-type": "text/binary"} ) app = web.Application() @@ -928,14 +938,19 @@ async def handler(request): assert resp.headers.get("Content-Length") == str(len(resp_data)) assert resp.headers.get("Content-Disposition") == expected_content_disposition + outer_file_descriptor.close() + async def test_response_with_payload_disp(aiohttp_client, fname) -> None: + outer_file_descriptor = None with fname.open("rb") as f: data = f.read() async def handler(request): - pl = aiohttp.get_payload(fname.open("rb")) + nonlocal outer_file_descriptor + outer_file_descriptor = fname.open("rb") + pl = aiohttp.get_payload(outer_file_descriptor) pl.set_content_disposition("inline", filename="test.txt") return web.Response(body=pl, headers={"content-type": "text/binary"}) @@ -954,6 +969,8 @@ async def handler(request): == "inline; filename=\"test.txt\"; filename*=utf-8''test.txt" ) + outer_file_descriptor.close() + async def test_response_with_payload_stringio(aiohttp_client, fname) -> None: async def handler(request): @@ -1566,6 +1583,7 @@ async def handler(request): assert ( "Maximum request body size 10 exceeded, " "actual body size 1024" in resp_text ) + data["file"].close() async def test_post_max_client_size_for_file(aiohttp_client) -> None: @@ -1619,11 +1637,12 @@ async def handler(request): f = tmpdir.join("foobar.txt") f.write_text("test", encoding="utf8") - data = {"file": open(str(f), "rb")} - resp = await client.post("/", data=data) + with open(str(f), "rb") as fd: + data = {"file": fd} + resp = await client.post("/", data=data) - assert 200 == resp.status - body = await resp.read() + assert 200 == resp.status + body = await resp.read() assert body == b"test" disp = multipart.parse_content_disposition(resp.headers["content-disposition"]) @@ -1701,12 +1720,15 @@ async def handler(request): app = web.Application() app.router.add_route("GET", "/", handler) server = await aiohttp_server(app) - resp = await aiohttp.ClientSession().get(server.make_url("/")) + session = aiohttp.ClientSession() + resp = await session.get(server.make_url("/")) async with resp: assert resp.status == 200 assert resp.connection is None assert resp.connection is None + await session.close() + async def test_response_context_manager_error(aiohttp_server) -> None: async def handler(request): @@ -1727,6 +1749,8 @@ async def handler(request): assert len(session._connector._conns) == 1 + await session.close() + async def aiohttp_client_api_context_manager(aiohttp_server): async def handler(request): diff --git a/tests/test_web_urldispatcher.py b/tests/test_web_urldispatcher.py index 354be354496..f27002883f6 100644 --- a/tests/test_web_urldispatcher.py +++ b/tests/test_web_urldispatcher.py @@ -153,6 +153,7 @@ async def test_access_to_the_file_with_spaces( r = await client.get(url) assert r.status == 200 assert (await r.text()) == data + await r.release() async def test_access_non_existing_resource(tmp_dir_path, aiohttp_client) -> None: