From 8e2bfbff953556b56e894651568eb8e4480608b1 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Tue, 11 Feb 2020 20:07:59 +0100 Subject: [PATCH 01/15] Reduce verbosity of passed tests --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2bc77802..0eb47468 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,10 +39,10 @@ before_script: - tools/kubernetes-client.sh script: - - pytest -v --cov=kopf --cov-branch + - pytest --cov=kopf --cov-branch - coveralls - codecov --flags unit - - pytest -v --only-e2e # NB: after the coverage uploads! + - pytest --only-e2e # NB: after the coverage uploads! - mypy kopf --strict --pretty deploy: From e185249a0d6245a22deb51802bfb18d552a18597 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Tue, 11 Feb 2020 20:12:21 +0100 Subject: [PATCH 02/15] Fail on any warnings in tests --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0eb47468..91c9fc6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,10 +39,10 @@ before_script: - tools/kubernetes-client.sh script: - - pytest --cov=kopf --cov-branch + - pytest -Werror --cov=kopf --cov-branch - coveralls - codecov --flags unit - - pytest --only-e2e # NB: after the coverage uploads! + - pytest -Werror --only-e2e # NB: after the coverage uploads! - mypy kopf --strict --pretty deploy: From 3a2d3402d6cd11bf7be458ced1f9af525dbd7b15 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Tue, 11 Feb 2020 18:58:22 +0100 Subject: [PATCH 03/15] Fix ResourceWarnings for unclosed aiohttp sessions --- tests/authentication/test_credentials.py | 55 ++++++++++++------- tests/authentication/test_reauthentication.py | 24 ++++---- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/tests/authentication/test_credentials.py b/tests/authentication/test_credentials.py index 3d10a855..4f34d064 100644 --- a/tests/authentication/test_credentials.py +++ b/tests/authentication/test_credentials.py @@ -141,9 +141,10 @@ async def test_basic_auth(vault): }) session = await fn() - assert session._default_auth.login == 'username' - assert session._default_auth.password == 'password' - assert 'Authorization' not in session._default_headers + async with session: + assert session._default_auth.login == 'username' + assert session._default_auth.password == 'password' + assert 'Authorization' not in session._default_headers async def test_header_with_token_only(vault): @@ -155,8 +156,9 @@ async def test_header_with_token_only(vault): }) session = await fn() - assert session._default_auth is None - assert session._default_headers['Authorization'] == 'Bearer token' + async with session: + assert session._default_auth is None + assert session._default_headers['Authorization'] == 'Bearer token' async def test_header_with_schema_only(vault): @@ -168,8 +170,9 @@ async def test_header_with_schema_only(vault): }) session = await fn() - assert session._default_auth is None - assert session._default_headers['Authorization'] == 'Digest xyz' + async with session: + assert session._default_auth is None + assert session._default_headers['Authorization'] == 'Digest xyz' async def test_header_with_schema_and_token(vault): @@ -182,8 +185,9 @@ async def test_header_with_schema_and_token(vault): }) session = await fn() - assert session._default_auth is None - assert session._default_headers['Authorization'] == 'Digest xyz' + async with session: + assert session._default_auth is None + assert session._default_headers['Authorization'] == 'Digest xyz' async def test_ca_insecure(vault, cafile): @@ -195,8 +199,9 @@ async def test_ca_insecure(vault, cafile): }) session = await fn() - ctx = session.connector._ssl - assert ctx.verify_mode == ssl.CERT_NONE + async with session: + ctx = session.connector._ssl + assert ctx.verify_mode == ssl.CERT_NONE async def test_ca_as_path(vault, cafile): @@ -208,10 +213,11 @@ async def test_ca_as_path(vault, cafile): }) session = await fn() - ctx = session.connector._ssl - assert len(ctx.get_ca_certs()) == 1 - assert ctx.cert_store_stats()['x509'] == 1 - assert ctx.cert_store_stats()['x509_ca'] == 1 + async with session: + ctx = session.connector._ssl + assert len(ctx.get_ca_certs()) == 1 + assert ctx.cert_store_stats()['x509'] == 1 + assert ctx.cert_store_stats()['x509_ca'] == 1 async def test_ca_as_data(vault, cabase64): @@ -223,10 +229,11 @@ async def test_ca_as_data(vault, cabase64): }) session = await fn() - ctx = session.connector._ssl - assert len(ctx.get_ca_certs()) == 1 - assert ctx.cert_store_stats()['x509'] == 1 - assert ctx.cert_store_stats()['x509_ca'] == 1 + async with session: + ctx = session.connector._ssl + assert len(ctx.get_ca_certs()) == 1 + assert ctx.cert_store_stats()['x509'] == 1 + assert ctx.cert_store_stats()['x509_ca'] == 1 # TODO: find a way to test that the client certificates/pkeys are indeed loaded. @@ -240,7 +247,10 @@ async def test_clientcert_as_path(vault, cafile, certfile, pkeyfile): private_key_path=pkeyfile, ), }) - await fn() + session = await fn() + + async with session: + pass async def test_clientcert_as_data(vault, cafile, certbase64, pkeybase64): @@ -252,4 +262,7 @@ async def test_clientcert_as_data(vault, cafile, certbase64, pkeybase64): private_key_data=pkeybase64, ), }) - await fn() + session = await fn() + + async with session: + pass diff --git a/tests/authentication/test_reauthentication.py b/tests/authentication/test_reauthentication.py index bae37e71..4a72382e 100644 --- a/tests/authentication/test_reauthentication.py +++ b/tests/authentication/test_reauthentication.py @@ -33,8 +33,9 @@ async def test_session_is_injected_to_request( context, result = await request_fn(1) - assert context is not None - assert result == 101 + async with context.session: + assert context is not None + assert result == 101 async def test_session_is_injected_to_stream( @@ -49,9 +50,10 @@ async def test_session_is_injected_to_stream( async for context, result in stream_fn(1): counter += 1 - assert context is not None - assert result == 101 - assert counter == 1 + async with context.session: + assert context is not None + assert result == 101 + assert counter == 1 async def test_session_is_passed_through_to_request( @@ -64,8 +66,9 @@ async def test_session_is_passed_through_to_request( explicit_context = APIContext(ConnectionInfo(server='http://irrelevant/')) context, result = await request_fn(1, context=explicit_context) - assert context is explicit_context - assert result == 101 + async with context.session: + assert context is explicit_context + assert result == 101 async def test_session_is_passed_through_to_stream( @@ -80,6 +83,7 @@ async def test_session_is_passed_through_to_stream( async for context, result in stream_fn(1, context=explicit_context): counter += 1 - assert context is explicit_context - assert result == 101 - assert counter == 1 + async with context.session: + assert context is explicit_context + assert result == 101 + assert counter == 1 From 810c67fd65c96befb8167ad9e1adca7b95cb504e Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Tue, 11 Feb 2020 18:58:37 +0100 Subject: [PATCH 04/15] Fix ResourceWarnings for unclosed file --- tests/authentication/test_tempfiles.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/authentication/test_tempfiles.py b/tests/authentication/test_tempfiles.py index fc4d3317..a2d0599e 100644 --- a/tests/authentication/test_tempfiles.py +++ b/tests/authentication/test_tempfiles.py @@ -8,7 +8,8 @@ def test_created(): tempfiles = _TempFiles() path = tempfiles[b'hello'] assert os.path.isfile(path) - assert open(path, 'rb').read() == b'hello' + with open(path, 'rb') as f: + assert f.read() == b'hello' def test_reused(): From bc4fed5046540f1647d2a2165382b11e2c03b516 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Tue, 11 Feb 2020 19:07:32 +0100 Subject: [PATCH 05/15] Fix ResourceWarnings with unclosed event loops in KopfRunner --- kopf/toolkits/runner.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kopf/toolkits/runner.py b/kopf/toolkits/runner.py index e7c33204..36db797c 100644 --- a/kopf/toolkits/runner.py +++ b/kopf/toolkits/runner.py @@ -90,6 +90,7 @@ def __exit__( # but instead wait for the thread+loop (CLI command) to finish. self._stop.set() self._thread.join(timeout=self.timeout) + self._loop.close() # If the thread is not finished, it is a bigger problem than exceptions. if self._thread.is_alive(): From 20857604dc5e878fdbcff682d3c73a9c98c0281d Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Wed, 12 Feb 2020 11:28:57 +0100 Subject: [PATCH 06/15] Fix ResourceWarnings with unclosed transports in KopfRunner --- kopf/toolkits/runner.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kopf/toolkits/runner.py b/kopf/toolkits/runner.py index 36db797c..2ce28b42 100644 --- a/kopf/toolkits/runner.py +++ b/kopf/toolkits/runner.py @@ -90,6 +90,7 @@ def __exit__( # but instead wait for the thread+loop (CLI command) to finish. self._stop.set() self._thread.join(timeout=self.timeout) + self._loop.run_until_complete(self._loop.shutdown_asyncgens()) self._loop.close() # If the thread is not finished, it is a bigger problem than exceptions. From 06e0ae6e5f336b3832059dfdcdd965c56464cb16 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Tue, 11 Feb 2020 19:24:11 +0100 Subject: [PATCH 07/15] Fix ResourceWarnings with unclosed event loops --- examples/12-embedded/example.py | 12 +++++++----- tests/primitives/test_toggles.py | 6 ++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/12-embedded/example.py b/examples/12-embedded/example.py index 625b47aa..26a684c1 100644 --- a/examples/12-embedded/example.py +++ b/examples/12-embedded/example.py @@ -1,4 +1,5 @@ import asyncio +import contextlib import threading import time @@ -22,13 +23,14 @@ def kopf_thread( ): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) + with contextlib.closing(loop): - kopf.configure(verbose=True) # log formatting + kopf.configure(verbose=True) # log formatting - loop.run_until_complete(kopf.operator( - ready_flag=ready_flag, - stop_flag=stop_flag, - )) + loop.run_until_complete(kopf.operator( + ready_flag=ready_flag, + stop_flag=stop_flag, + )) def main(steps=3): diff --git a/tests/primitives/test_toggles.py b/tests/primitives/test_toggles.py index 9516f9ef..8b0ba82b 100644 --- a/tests/primitives/test_toggles.py +++ b/tests/primitives/test_toggles.py @@ -1,4 +1,5 @@ import asyncio +import contextlib import pytest @@ -13,8 +14,9 @@ async def test_creation_with_default_loop(): async def test_creation_with_explicit_loop(): loop = asyncio.new_event_loop() - toggle = Toggle(loop=loop) - assert toggle.loop is loop + with contextlib.closing(loop): + toggle = Toggle(loop=loop) + assert toggle.loop is loop async def test_created_as_off(): From 0aacfd4df16d85a6e7844e8db845389ef803c9f5 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Tue, 11 Feb 2020 19:24:37 +0100 Subject: [PATCH 08/15] Fix RuntimeWarnings with noop() never awaited (means: called) --- tests/posting/test_threadsafety.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/posting/test_threadsafety.py b/tests/posting/test_threadsafety.py index 968244b8..c2604396 100644 --- a/tests/posting/test_threadsafety.py +++ b/tests/posting/test_threadsafety.py @@ -52,7 +52,7 @@ def awakener(event_loop): handles = [] - async def noop(): + def noop(): pass def awaken_fn(delay, fn=noop): From 93fbaad3d23cacfbdeaa18a6f22bf75186d64c94 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Tue, 11 Feb 2020 19:39:04 +0100 Subject: [PATCH 09/15] Hide all the leaked logs during the tests --- kopf/config.py | 9 ++++++--- tests/conftest.py | 9 ++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/kopf/config.py b/kopf/config.py index c6ec29ef..e5a53250 100644 --- a/kopf/config.py +++ b/kopf/config.py @@ -48,9 +48,12 @@ def configure( del logger.handlers[1:] # everything except the default NullHandler # Prevent the low-level logging unless in the debug verbosity mode. Keep only the operator's messages. - logging.getLogger('urllib3').propagate = bool(debug) - logging.getLogger('asyncio').propagate = bool(debug) - logging.getLogger('kubernetes').propagate = bool(debug) + # For no-propagation loggers, add a dummy null handler to prevent printing the messages. + for name in ['urllib3', 'asyncio', 'kubernetes']: + logger = logging.getLogger(name) + logger.propagate = bool(debug) + if not debug: + logger.handlers[:] = [logging.NullHandler()] loop = asyncio.get_event_loop() loop.set_debug(bool(debug)) diff --git a/tests/conftest.py b/tests/conftest.py index ff7f521b..ceba93ff 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -482,14 +482,21 @@ def logstream(caplog): logger = logging.getLogger() handlers = list(logger.handlers) + # Setup all log levels of sub-libraries. A sife-effect: the handlers are also added. configure(verbose=True) + # Remove any stream handlers added in the step above. But keep the caplog's handlers. + for handler in list(logger.handlers): + if isinstance(handler, logging.StreamHandler) and handler.stream is sys.stderr: + logger.removeHandler(handler) + + # Inject our stream-intercepting handler. stream = io.StringIO() handler = logging.StreamHandler(stream) formatter = ObjectPrefixingFormatter('prefix %(message)s') handler.setFormatter(formatter) - logger.addHandler(handler) + try: with caplog.at_level(logging.DEBUG): yield stream From 25a6e4585a1fde79f53f044edd71212f43fed439 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Tue, 11 Feb 2020 20:27:09 +0100 Subject: [PATCH 10/15] Hide all the leaked stdout/stderr output from kubectl --- examples/09-testing/test_example_09.py | 6 ++++-- examples/11-filtering-handlers/test_example_11.py | 6 ++++-- tests/e2e/conftest.py | 12 ++++++++---- tests/e2e/test_examples.py | 6 ++++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/examples/09-testing/test_example_09.py b/examples/09-testing/test_example_09.py index 1220c4c3..7dcbf572 100644 --- a/examples/09-testing/test_example_09.py +++ b/examples/09-testing/test_example_09.py @@ -35,9 +35,11 @@ def test_resource_lifecycle(mocker): # Run an operator and simulate some activity with the operated resource. with kopf.testing.KopfRunner(['run', '--verbose', '--standalone', example_py], timeout=60) as runner: - subprocess.run(f"kubectl create -f {obj_yaml}", shell=True, check=True) + subprocess.run(f"kubectl create -f {obj_yaml}", + shell=True, check=True, timeout=10, capture_output=True) time.sleep(5) # give it some time to react - subprocess.run(f"kubectl delete -f {obj_yaml}", shell=True, check=True) + subprocess.run(f"kubectl delete -f {obj_yaml}", + shell=True, check=True, timeout=10, capture_output=True) time.sleep(1) # give it some time to react # Ensure that the operator did not die on start, or during the operation. diff --git a/examples/11-filtering-handlers/test_example_11.py b/examples/11-filtering-handlers/test_example_11.py index 46b08a00..b2a63039 100644 --- a/examples/11-filtering-handlers/test_example_11.py +++ b/examples/11-filtering-handlers/test_example_11.py @@ -35,9 +35,11 @@ def test_handler_filtering(mocker): # Run an operator and simulate some activity with the operated resource. with kopf.testing.KopfRunner(['run', '--verbose', '--standalone', example_py]) as runner: - subprocess.run(f"kubectl create -f {obj_yaml}", shell=True, check=True) + subprocess.run(f"kubectl create -f {obj_yaml}", + shell=True, check=True, timeout=10, capture_output=True) time.sleep(5) # give it some time to react - subprocess.run(f"kubectl delete -f {obj_yaml}", shell=True, check=True) + subprocess.run(f"kubectl delete -f {obj_yaml}", + shell=True, check=True, timeout=10, capture_output=True) time.sleep(1) # give it some time to react # Ensure that the operator did not die on start, or during the operation. diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 6458bf1d..1d532f9d 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -18,19 +18,23 @@ def exampledir(request): @pytest.fixture() def with_crd(): - subprocess.run("kubectl apply -f examples/crd.yaml", shell=True, check=True) + subprocess.run("kubectl apply -f examples/crd.yaml", + shell=True, check=True, timeout=10, capture_output=True) @pytest.fixture() def with_peering(): - subprocess.run("kubectl apply -f peering.yaml", shell=True, check=True) + subprocess.run("kubectl apply -f peering.yaml", + shell=True, check=True, timeout=10, capture_output=True) @pytest.fixture() def no_crd(): - subprocess.run("kubectl delete customresourcedefinition kopfexamples.zalando.org", shell=True, check=True) + subprocess.run("kubectl delete customresourcedefinition kopfexamples.zalando.org", + shell=True, check=True, timeout=10, capture_output=True) @pytest.fixture() def no_peering(): - subprocess.run("kubectl delete customresourcedefinition kopfpeerings.zalando.org", shell=True, check=True) + subprocess.run("kubectl delete customresourcedefinition kopfpeerings.zalando.org", + shell=True, check=True, timeout=10, capture_output=True) diff --git a/tests/e2e/test_examples.py b/tests/e2e/test_examples.py index 68f0bd7e..d067a05c 100644 --- a/tests/e2e/test_examples.py +++ b/tests/e2e/test_examples.py @@ -56,13 +56,15 @@ def test_all_examples_are_runnable(mocker, with_crd, exampledir, caplog): patterns=e2e_startup_stop_words or ['Client is configured']) # Trigger the reaction. Give it some time to react and to sleep and to retry. - subprocess.run("kubectl apply -f examples/obj.yaml", shell=True, check=True) + subprocess.run("kubectl apply -f examples/obj.yaml", + shell=True, check=True, timeout=10, capture_output=True) _sleep_till_stopword(caplog=caplog, delay=e2e_creation_time_limit, patterns=e2e_creation_stop_words) # Trigger the reaction. Give it some time to react. - subprocess.run("kubectl delete -f examples/obj.yaml", shell=True, check=True) + subprocess.run("kubectl delete -f examples/obj.yaml", + shell=True, check=True, timeout=10, capture_output=True) _sleep_till_stopword(caplog=caplog, delay=e2e_deletion_time_limit, patterns=e2e_deletion_stop_words) From b3df6f1c719a761c2b696ef10fe2f58f58ce421f Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Tue, 11 Feb 2020 17:38:52 +0100 Subject: [PATCH 11/15] Catch and assert all DeprecationWarnings explicitly where expected --- kopf/reactor/causation.py | 2 +- kopf/reactor/handlers.py | 2 +- kopf/utilities/piggybacking.py | 5 +- tests/authentication/test_login.py | 29 +++-- tests/basic-structs/test_causes.py | 8 +- tests/basic-structs/test_handlers.py | 9 +- .../test_handlers_deprecated_cooldown.py | 64 ++++++----- .../legacy/test_legacy_decorators.py | 40 +++++-- .../legacy/test_legacy_global_registry.py | 9 +- .../legacy/test_legacy_handler_matching.py | 103 ++++++++++++------ .../legacy/test_legacy_id_detection.py | 15 ++- .../legacy/test_legacy_registering.py | 23 ++-- tests/registries/test_decorators.py | 16 --- .../test_decorators_deprecated_cooldown.py | 88 +++++++++------ 14 files changed, 261 insertions(+), 152 deletions(-) diff --git a/kopf/reactor/causation.py b/kopf/reactor/causation.py index 8c350572..9f82dae8 100644 --- a/kopf/reactor/causation.py +++ b/kopf/reactor/causation.py @@ -125,7 +125,7 @@ class ResourceChangingCause(ResourceCause): @property def event(self) -> Reason: - warnings.warn("`cause.event` is deprecated; use `cause.reason`.", DeprecationWarning) + warnings.warn("cause.event is deprecated; use cause.reason.", DeprecationWarning) return self.reason @property diff --git a/kopf/reactor/handlers.py b/kopf/reactor/handlers.py index 12f50307..6aba37a9 100644 --- a/kopf/reactor/handlers.py +++ b/kopf/reactor/handlers.py @@ -64,5 +64,5 @@ class ResourceHandler(BaseHandler): @property def event(self) -> Optional[causation.Reason]: - warnings.warn("`handler.event` is deprecated; use `handler.reason`.", DeprecationWarning) + warnings.warn("handler.event is deprecated; use handler.reason.", DeprecationWarning) return self.reason diff --git a/kopf/utilities/piggybacking.py b/kopf/utilities/piggybacking.py index f49ccb19..d4e93deb 100644 --- a/kopf/utilities/piggybacking.py +++ b/kopf/utilities/piggybacking.py @@ -11,6 +11,7 @@ :mod:`credentials` and :func:`authentication`. """ import logging +import warnings from typing import Any, Union, Optional, Sequence from kopf.clients import auth @@ -89,7 +90,9 @@ def login_via_pykube( # to inject custom authentication methods. Support these hacks if possible. config: pykube.KubeConfig try: - config = auth.get_pykube_cfg() + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + config = auth.get_pykube_cfg() logger.debug("Pykube is configured via monkey-patched get_pykube_cfg().") except NotImplementedError: try: diff --git a/tests/authentication/test_login.py b/tests/authentication/test_login.py index f176b0aa..e542c762 100644 --- a/tests/authentication/test_login.py +++ b/tests/authentication/test_login.py @@ -2,13 +2,16 @@ Remember: We do not test the clients, we assume they work when used properly. We test our own functions here, and check if the clients were called. """ +import pytest + import pykube from kopf import login def test_client_login_works_incluster(login_mocks, kubernetes): - login() + with pytest.deprecated_call(match=r"cease using kopf.login\(\)"): + login() assert login_mocks.client_in_cluster.called assert not login_mocks.client_from_file.called @@ -17,14 +20,16 @@ def test_client_login_works_incluster(login_mocks, kubernetes): def test_client_login_works_viaconfig(login_mocks, kubernetes): login_mocks.client_in_cluster.side_effect = kubernetes.config.ConfigException - login() + with pytest.deprecated_call(match=r"cease using kopf.login\(\)"): + login() assert login_mocks.client_in_cluster.called assert login_mocks.client_from_file.called def test_pykube_login_works_incluster(login_mocks, pykube): - login() + with pytest.deprecated_call(match=r"cease using kopf.login\(\)"): + login() assert login_mocks.pykube_in_cluster.called assert not login_mocks.pykube_from_file.called @@ -33,7 +38,8 @@ def test_pykube_login_works_incluster(login_mocks, pykube): def test_pykube_login_works_viaconfig(login_mocks, pykube): login_mocks.pykube_in_cluster.side_effect = FileNotFoundError - login() + with pytest.deprecated_call(match=r"cease using kopf.login\(\)"): + login() assert login_mocks.pykube_in_cluster.called assert login_mocks.pykube_from_file.called @@ -47,7 +53,8 @@ def test_monkeypatched_get_pykube_cfg_overrides_pykube(mocker, login_mocks): 'clusters': [{'name': 'self', 'cluster': {'server': 'https://localhost'}}], }) - login() + with pytest.deprecated_call(match=r"cease using kopf.login\(\)"): + login() assert get_pykube_cfg.called assert not login_mocks.pykube_in_cluster.called @@ -55,7 +62,8 @@ def test_monkeypatched_get_pykube_cfg_overrides_pykube(mocker, login_mocks): def test_pykube_is_independent_of_client_incluster(login_mocks, no_kubernetes, pykube): - login() + with pytest.deprecated_call(match=r"cease using kopf.login\(\)"): + login() assert login_mocks.pykube_in_cluster.called assert not login_mocks.pykube_from_file.called @@ -64,14 +72,16 @@ def test_pykube_is_independent_of_client_incluster(login_mocks, no_kubernetes, p def test_pykube_is_independent_of_client_viaconfig(login_mocks, no_kubernetes, pykube): login_mocks.pykube_in_cluster.side_effect = FileNotFoundError - login() + with pytest.deprecated_call(match=r"cease using kopf.login\(\)"): + login() assert login_mocks.pykube_in_cluster.called assert login_mocks.pykube_from_file.called def test_client_is_independent_of_pykube_incluster(login_mocks, no_pykube, kubernetes): - login() + with pytest.deprecated_call(match=r"cease using kopf.login\(\)"): + login() assert login_mocks.client_in_cluster.called assert not login_mocks.client_from_file.called @@ -80,7 +90,8 @@ def test_client_is_independent_of_pykube_incluster(login_mocks, no_pykube, kuber def test_client_is_independent_of_pykube_viaconfig(login_mocks, no_pykube, kubernetes): login_mocks.client_in_cluster.side_effect = kubernetes.config.ConfigException - login() + with pytest.deprecated_call(match=r"cease using kopf.login\(\)"): + login() assert login_mocks.client_in_cluster.called assert login_mocks.client_from_file.called diff --git a/tests/basic-structs/test_causes.py b/tests/basic-structs/test_causes.py index 6b057dbc..b8469e07 100644 --- a/tests/basic-structs/test_causes.py +++ b/tests/basic-structs/test_causes.py @@ -72,7 +72,6 @@ def test_resource_changing_cause_with_all_args(mocker): assert cause.resource is resource assert cause.logger is logger assert cause.reason is reason - assert cause.event is reason # deprecated assert cause.initial is initial assert cause.body is body assert cause.patch is patch @@ -81,6 +80,9 @@ def test_resource_changing_cause_with_all_args(mocker): assert cause.old is old assert cause.new is new + with pytest.deprecated_call(match=r"use cause.reason"): + assert cause.event is reason + def test_resource_changing_cause_with_only_required_args(mocker): logger = mocker.Mock() @@ -102,7 +104,6 @@ def test_resource_changing_cause_with_only_required_args(mocker): assert cause.resource is resource assert cause.logger is logger assert cause.reason is reason - assert cause.event is reason # deprecated assert cause.initial is initial assert cause.body is body assert cause.patch is patch @@ -111,3 +112,6 @@ def test_resource_changing_cause_with_only_required_args(mocker): assert not cause.diff assert cause.old is None assert cause.new is None + + with pytest.deprecated_call(match=r"use cause.reason"): + assert cause.event is reason diff --git a/tests/basic-structs/test_handlers.py b/tests/basic-structs/test_handlers.py index 2d38772b..56437c5c 100644 --- a/tests/basic-structs/test_handlers.py +++ b/tests/basic-structs/test_handlers.py @@ -33,7 +33,6 @@ def test_activity_handler_with_all_args(mocker): assert handler.timeout is timeout assert handler.retries is retries assert handler.backoff is backoff - assert handler.cooldown is backoff # deprecated alias assert handler.activity is activity @@ -68,14 +67,18 @@ def test_resource_handler_with_all_args(mocker): assert handler.fn is fn assert handler.id is id assert handler.reason is reason - assert handler.event is reason # deprecated assert handler.field is field assert handler.errors is errors assert handler.timeout is timeout assert handler.retries is retries assert handler.backoff is backoff - assert handler.cooldown is backoff # deprecated alias assert handler.initial is initial assert handler.labels is labels assert handler.annotations is annotations assert handler.requires_finalizer is requires_finalizer + + with pytest.deprecated_call(match=r"use handler.reason"): + assert handler.event is reason + + with pytest.deprecated_call(match=r"use handler.backoff"): + assert handler.cooldown is backoff diff --git a/tests/basic-structs/test_handlers_deprecated_cooldown.py b/tests/basic-structs/test_handlers_deprecated_cooldown.py index 3a54a67f..b9dc5a96 100644 --- a/tests/basic-structs/test_handlers_deprecated_cooldown.py +++ b/tests/basic-structs/test_handlers_deprecated_cooldown.py @@ -1,4 +1,6 @@ # Original test-file: tests/basic-structs/test_handlers.py +import pytest + from kopf.reactor.handlers import ActivityHandler, ResourceHandler @@ -10,25 +12,30 @@ def test_activity_handler_with_deprecated_cooldown_instead_of_backoff(mocker): retries = mocker.Mock() backoff = mocker.Mock() activity = mocker.Mock() - handler = ActivityHandler( - fn=fn, - id=id, - errors=errors, - timeout=timeout, - retries=retries, - backoff=None, - cooldown=backoff, # deprecated, but still required - activity=activity, - ) + + with pytest.deprecated_call(match=r"use backoff="): + handler = ActivityHandler( + fn=fn, + id=id, + errors=errors, + timeout=timeout, + retries=retries, + backoff=None, + cooldown=backoff, # deprecated, but still required + activity=activity, + ) + assert handler.fn is fn assert handler.id is id assert handler.errors is errors assert handler.timeout is timeout assert handler.retries is retries assert handler.backoff is backoff - assert handler.cooldown is backoff # deprecated alias assert handler.activity is activity + with pytest.deprecated_call(match=r"use handler.backoff"): + assert handler.cooldown is backoff + def test_resource_handler_with_deprecated_cooldown_instead_of_backoff(mocker): fn = mocker.Mock() @@ -43,31 +50,32 @@ def test_resource_handler_with_deprecated_cooldown_instead_of_backoff(mocker): labels = mocker.Mock() annotations = mocker.Mock() requires_finalizer = mocker.Mock() - handler = ResourceHandler( - fn=fn, - id=id, - reason=reason, - field=field, - errors=errors, - timeout=timeout, - retries=retries, - backoff=None, - cooldown=backoff, # deprecated, but still required - initial=initial, - labels=labels, - annotations=annotations, - requires_finalizer=requires_finalizer, - ) + + with pytest.deprecated_call(match=r"use backoff="): + handler = ResourceHandler( + fn=fn, + id=id, + reason=reason, + field=field, + errors=errors, + timeout=timeout, + retries=retries, + backoff=None, + cooldown=backoff, # deprecated, but still required + initial=initial, + labels=labels, + annotations=annotations, + requires_finalizer=requires_finalizer, + ) + assert handler.fn is fn assert handler.id is id assert handler.reason is reason - assert handler.event is reason # deprecated assert handler.field is field assert handler.errors is errors assert handler.timeout is timeout assert handler.retries is retries assert handler.backoff is backoff - assert handler.cooldown is backoff # deprecated alias assert handler.initial is initial assert handler.labels is labels assert handler.annotations is annotations diff --git a/tests/registries/legacy/test_legacy_decorators.py b/tests/registries/legacy/test_legacy_decorators.py index 2baa749a..590c55ca 100644 --- a/tests/registries/legacy/test_legacy_decorators.py +++ b/tests/registries/legacy/test_legacy_decorators.py @@ -16,7 +16,9 @@ def test_on_create_minimal(mocker): def fn(**_): pass - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) + assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.CREATE @@ -36,7 +38,9 @@ def test_on_update_minimal(mocker): def fn(**_): pass - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) + assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.UPDATE @@ -56,7 +60,9 @@ def test_on_delete_minimal(mocker): def fn(**_): pass - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) + assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.DELETE @@ -77,7 +83,9 @@ def test_on_field_minimal(mocker): def fn(**_): pass - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) + assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None @@ -111,7 +119,9 @@ def test_on_create_with_all_kwargs(mocker): def fn(**_): pass - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) + assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.CREATE @@ -138,7 +148,9 @@ def test_on_update_with_all_kwargs(mocker): def fn(**_): pass - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) + assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.UPDATE @@ -170,7 +182,9 @@ def test_on_delete_with_all_kwargs(mocker, optional): def fn(**_): pass - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) + assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.DELETE @@ -199,7 +213,9 @@ def test_on_field_with_all_kwargs(mocker): def fn(**_): pass - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) + assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None @@ -221,7 +237,9 @@ def test_subhandler_declaratively(mocker): def fn(**_): pass - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use ResourceChangingRegistry.get_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) + assert len(handlers) == 1 assert handlers[0].fn is fn @@ -236,6 +254,8 @@ def fn(**_): pass kopf.register(fn) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use ResourceChangingRegistry.get_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) + assert len(handlers) == 1 assert handlers[0].fn is fn diff --git a/tests/registries/legacy/test_legacy_global_registry.py b/tests/registries/legacy/test_legacy_global_registry.py index 49e02180..39bd4f41 100644 --- a/tests/registries/legacy/test_legacy_global_registry.py +++ b/tests/registries/legacy/test_legacy_global_registry.py @@ -1,5 +1,7 @@ import collections +import pytest + from kopf import GlobalRegistry from kopf.structs.resources import Resource @@ -11,8 +13,11 @@ def some_fn(): def test_resources(): registry = GlobalRegistry() - registry.register_cause_handler('group1', 'version1', 'plural1', some_fn) - registry.register_cause_handler('group2', 'version2', 'plural2', some_fn) + + with pytest.deprecated_call(match=r"use OperatorRegistry.register_resource_changing_handler"): + registry.register_cause_handler('group1', 'version1', 'plural1', some_fn) + with pytest.deprecated_call(match=r"use OperatorRegistry.register_resource_changing_handler"): + registry.register_cause_handler('group2', 'version2', 'plural2', some_fn) resources = registry.resources diff --git a/tests/registries/legacy/test_legacy_handler_matching.py b/tests/registries/legacy/test_legacy_handler_matching.py index 9b0b4e5b..b7e9e93a 100644 --- a/tests/registries/legacy/test_legacy_handler_matching.py +++ b/tests/registries/legacy/test_legacy_handler_matching.py @@ -22,10 +22,12 @@ def registry(request): @pytest.fixture() def register_fn(registry, resource): if isinstance(registry, SimpleRegistry): - return registry.register - if isinstance(registry, GlobalRegistry): - return functools.partial(registry.register_cause_handler, resource.group, resource.version, resource.plural) - raise Exception(f"Unsupported registry type: {registry}") + yield registry.register + elif isinstance(registry, GlobalRegistry): + with pytest.deprecated_call(match=r"GlobalRegistry.register_cause_handler\(\) is deprecated"): + yield functools.partial(registry.register_cause_handler, resource.group, resource.version, resource.plural) + else: + raise Exception(f"Unsupported registry type: {registry}") @pytest.fixture(params=[ @@ -62,19 +64,22 @@ def cause_any_diff(resource, request): def test_catchall_handlers_without_field_found(cause_any_diff, registry, register_fn): register_fn(some_fn, reason=None, field=None) - handlers = registry.get_cause_handlers(cause_any_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_any_diff) assert handlers def test_catchall_handlers_with_field_found(cause_with_diff, registry, register_fn): register_fn(some_fn, reason=None, field='some-field') - handlers = registry.get_cause_handlers(cause_with_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_with_diff) assert handlers def test_catchall_handlers_with_field_ignored(cause_no_diff, registry, register_fn): register_fn(some_fn, reason=None, field='some-field') - handlers = registry.get_cause_handlers(cause_no_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_no_diff) assert not handlers @@ -85,7 +90,8 @@ def test_catchall_handlers_with_field_ignored(cause_no_diff, registry, register_ def test_catchall_handlers_with_labels_satisfied(registry, register_fn, resource, labels): cause = Mock(resource=resource, reason='some-reason', diff=None, body={'metadata': {'labels': labels}}) register_fn(some_fn, reason=None, field=None, labels={'somelabel': 'somevalue'}) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause) assert handlers @@ -97,7 +103,8 @@ def test_catchall_handlers_with_labels_satisfied(registry, register_fn, resource def test_catchall_handlers_with_labels_not_satisfied(registry, register_fn, resource, labels): cause = Mock(resource=resource, reason='some-reason', diff=None, body={'metadata': {'labels': labels}}) register_fn(some_fn, reason=None, field=None, labels={'somelabel': 'somevalue'}) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause) assert not handlers @@ -108,7 +115,8 @@ def test_catchall_handlers_with_labels_not_satisfied(registry, register_fn, reso def test_catchall_handlers_with_labels_exist(registry, register_fn, resource, labels): cause = Mock(resource=resource, reason='some-reason', diff=None, body={'metadata': {'labels': labels}}) register_fn(some_fn, reason=None, field=None, labels={'somelabel': None}) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause) assert handlers @@ -119,7 +127,8 @@ def test_catchall_handlers_with_labels_exist(registry, register_fn, resource, la def test_catchall_handlers_with_labels_not_exist(registry, register_fn, resource, labels): cause = Mock(resource=resource, reason='some-reason', diff=None, body={'metadata': {'labels': labels}}) register_fn(some_fn, reason=None, field=None, labels={'somelabel': None}) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause) assert not handlers @@ -133,7 +142,8 @@ def test_catchall_handlers_with_labels_not_exist(registry, register_fn, resource def test_catchall_handlers_without_labels(registry, register_fn, resource, labels): cause = Mock(resource=resource, reason='some-reason', diff=None, body={'metadata': {'labels': labels}}) register_fn(some_fn, reason=None, field=None, labels=None) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause) assert handlers @@ -144,7 +154,8 @@ def test_catchall_handlers_without_labels(registry, register_fn, resource, label def test_catchall_handlers_with_annotations_satisfied(registry, register_fn, resource, annotations): cause = Mock(resource=resource, reason='some-reason', diff=None, body={'metadata': {'annotations': annotations}}) register_fn(some_fn, reason=None, field=None, annotations={'someannotation': 'somevalue'}) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause) assert handlers @@ -156,7 +167,8 @@ def test_catchall_handlers_with_annotations_satisfied(registry, register_fn, res def test_catchall_handlers_with_annotations_not_satisfied(registry, register_fn, resource, annotations): cause = Mock(resource=resource, reason='some-reason', diff=None, body={'metadata': {'annotations': annotations}}) register_fn(some_fn, reason=None, field=None, annotations={'someannotation': 'somevalue'}) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause) assert not handlers @@ -167,7 +179,8 @@ def test_catchall_handlers_with_annotations_not_satisfied(registry, register_fn, def test_catchall_handlers_with_annotations_exist(registry, register_fn, resource, annotations): cause = Mock(resource=resource, reason='some-reason', diff=None, body={'metadata': {'annotations': annotations}}) register_fn(some_fn, reason=None, field=None, annotations={'someannotation': None}) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause) assert handlers @@ -178,7 +191,8 @@ def test_catchall_handlers_with_annotations_exist(registry, register_fn, resourc def test_catchall_handlers_with_annotations_not_exist(registry, register_fn, resource, annotations): cause = Mock(resource=resource, reason='some-reason', diff=None, body={'metadata': {'annotations': annotations}}) register_fn(some_fn, reason=None, field=None, annotations={'someannotation': None}) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause) assert not handlers @@ -192,7 +206,8 @@ def test_catchall_handlers_with_annotations_not_exist(registry, register_fn, res def test_catchall_handlers_without_annotations(registry, register_fn, resource, annotations): cause = Mock(resource=resource, reason='some-reason', diff=None, body={'metadata': {'annotations': annotations}}) register_fn(some_fn, reason=None, field=None, annotations=None) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause) assert handlers @@ -205,7 +220,8 @@ def test_catchall_handlers_without_annotations(registry, register_fn, resource, def test_catchall_handlers_with_labels_and_annotations_satisfied(registry, register_fn, resource, labels, annotations): cause = Mock(resource=resource, reason='some-reason', diff=None, body={'metadata': {'labels': labels, 'annotations': annotations}}) register_fn(some_fn, reason=None, field=None, labels={'somelabel': 'somevalue'}, annotations={'someannotation': 'somevalue'}) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause) assert handlers @@ -219,7 +235,8 @@ def test_catchall_handlers_with_labels_and_annotations_satisfied(registry, regis def test_catchall_handlers_with_labels_and_annotations_not_satisfied(registry, register_fn, resource, labels): cause = Mock(resource=resource, reason='some-reason', diff=None, body={'metadata': {'labels': labels}}) register_fn(some_fn, reason=None, field=None, labels={'somelabel': 'somevalue'}, annotations={'someannotation': 'somevalue'}) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause) assert not handlers @@ -232,78 +249,91 @@ def test_catchall_handlers_with_labels_and_annotations_not_satisfied(registry, r def test_relevant_handlers_without_field_found(cause_any_diff, registry, register_fn): register_fn(some_fn, reason='some-reason') - handlers = registry.get_cause_handlers(cause_any_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_any_diff) assert handlers def test_relevant_handlers_with_field_found(cause_with_diff, registry, register_fn): register_fn(some_fn, reason='some-reason', field='some-field') - handlers = registry.get_cause_handlers(cause_with_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_with_diff) assert handlers def test_relevant_handlers_with_field_ignored(cause_no_diff, registry, register_fn): register_fn(some_fn, reason='some-reason', field='some-field') - handlers = registry.get_cause_handlers(cause_no_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_no_diff) assert not handlers def test_relevant_handlers_with_labels_satisfied(cause_any_diff, registry, register_fn): register_fn(some_fn, reason='some-reason', labels={'somelabel': None}) - handlers = registry.get_cause_handlers(cause_any_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_any_diff) assert handlers def test_relevant_handlers_with_labels_not_satisfied(cause_any_diff, registry, register_fn): register_fn(some_fn, reason='some-reason', labels={'otherlabel': None}) - handlers = registry.get_cause_handlers(cause_any_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_any_diff) assert not handlers def test_relevant_handlers_with_annotations_satisfied(cause_any_diff, registry, register_fn): register_fn(some_fn, reason='some-reason', annotations={'someannotation': None}) - handlers = registry.get_cause_handlers(cause_any_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_any_diff) assert handlers def test_relevant_handlers_with_annotations_not_satisfied(cause_any_diff, registry, register_fn): register_fn(some_fn, reason='some-reason', annotations={'otherannotation': None}) - handlers = registry.get_cause_handlers(cause_any_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_any_diff) assert not handlers def test_irrelevant_handlers_without_field_ignored(cause_any_diff, registry, register_fn): register_fn(some_fn, reason='another-reason') - handlers = registry.get_cause_handlers(cause_any_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_any_diff) assert not handlers def test_irrelevant_handlers_with_field_ignored(cause_any_diff, registry, register_fn): register_fn(some_fn, reason='another-reason', field='another-field') - handlers = registry.get_cause_handlers(cause_any_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_any_diff) assert not handlers def test_irrelevant_handlers_with_labels_satisfied(cause_any_diff, registry, register_fn): register_fn(some_fn, reason='another-reason', labels={'somelabel': None}) - handlers = registry.get_cause_handlers(cause_any_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_any_diff) assert not handlers def test_irrelevant_handlers_with_labels_not_satisfied(cause_any_diff, registry, register_fn): register_fn(some_fn, reason='another-reason', labels={'otherlabel': None}) - handlers = registry.get_cause_handlers(cause_any_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_any_diff) assert not handlers def test_irrelevant_handlers_with_annotations_satisfied(cause_any_diff, registry, register_fn): register_fn(some_fn, reason='another-reason', annotations={'someannotation': None}) - handlers = registry.get_cause_handlers(cause_any_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_any_diff) assert not handlers def test_irrelevant_handlers_with_annotations_not_satisfied(cause_any_diff, registry, register_fn): register_fn(some_fn, reason='another-reason', annotations={'otherannotation': None}) - handlers = registry.get_cause_handlers(cause_any_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_any_diff) assert not handlers @@ -319,7 +349,8 @@ def test_order_persisted_a(cause_with_diff, registry, register_fn): register_fn(functools.partial(some_fn, 4), reason=None, field='filtered-out-reason') register_fn(functools.partial(some_fn, 5), reason=None, field='some-field') - handlers = registry.get_cause_handlers(cause_with_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_with_diff) # Order must be preserved -- same as registered. assert len(handlers) == 3 @@ -338,7 +369,8 @@ def test_order_persisted_b(cause_with_diff, registry, register_fn): register_fn(functools.partial(some_fn, 4), reason='some-reason') register_fn(functools.partial(some_fn, 5), reason=None) - handlers = registry.get_cause_handlers(cause_with_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_with_diff) # Order must be preserved -- same as registered. assert len(handlers) == 3 @@ -358,7 +390,8 @@ def test_deduplicated(cause_with_diff, registry, register_fn): register_fn(some_fn, reason=None, id='a') register_fn(some_fn, reason=None, id='b') - handlers = registry.get_cause_handlers(cause_with_diff) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(cause_with_diff) assert len(handlers) == 1 assert handlers[0].id == 'a' # the first found one is returned diff --git a/tests/registries/legacy/test_legacy_id_detection.py b/tests/registries/legacy/test_legacy_id_detection.py index 2caf4ce4..2b49b9ea 100644 --- a/tests/registries/legacy/test_legacy_id_detection.py +++ b/tests/registries/legacy/test_legacy_id_detection.py @@ -76,7 +76,8 @@ def test_with_no_hints(mocker): registry = SimpleRegistry() registry.register(some_fn) - handlers = registry.get_cause_handlers(mocker.MagicMock()) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(mocker.MagicMock()) assert get_fn_id.called @@ -90,7 +91,8 @@ def test_with_prefix(mocker): registry = SimpleRegistry(prefix='some-prefix') registry.register(some_fn) - handlers = registry.get_cause_handlers(mocker.MagicMock()) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(mocker.MagicMock()) assert get_fn_id.called @@ -105,7 +107,8 @@ def test_with_suffix(mocker, field): registry = SimpleRegistry() registry.register(some_fn, field=field) - handlers = registry.get_cause_handlers(mocker.MagicMock(diff=diff)) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(mocker.MagicMock(diff=diff)) assert get_fn_id.called @@ -120,7 +123,8 @@ def test_with_prefix_and_suffix(mocker, field): registry = SimpleRegistry(prefix='some-prefix') registry.register(some_fn, field=field) - handlers = registry.get_cause_handlers(mocker.MagicMock(diff=diff)) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(mocker.MagicMock(diff=diff)) assert get_fn_id.called @@ -135,7 +139,8 @@ def test_with_explicit_id_and_prefix_and_suffix(mocker, field): registry = SimpleRegistry(prefix='some-prefix') registry.register(some_fn, id='explicit-id', field=field) - handlers = registry.get_cause_handlers(mocker.MagicMock(diff=diff)) + with pytest.deprecated_call(match=r"get_cause_handlers\(\) is deprecated"): + handlers = registry.get_cause_handlers(mocker.MagicMock(diff=diff)) assert not get_fn_id.called diff --git a/tests/registries/legacy/test_legacy_registering.py b/tests/registries/legacy/test_legacy_registering.py index 2d737a8a..d7810cef 100644 --- a/tests/registries/legacy/test_legacy_registering.py +++ b/tests/registries/legacy/test_legacy_registering.py @@ -1,5 +1,7 @@ import collections.abc +import pytest + from kopf import SimpleRegistry, GlobalRegistry @@ -19,7 +21,8 @@ def test_simple_registry_via_iter(mocker): assert not isinstance(iterator, collections.abc.Container) assert not isinstance(iterator, (list, tuple)) - handlers = list(iterator) + with pytest.deprecated_call(match=r"use ResourceChangingRegistry.iter_handlers\(\)"): + handlers = list(iterator) assert not handlers @@ -27,7 +30,8 @@ def test_simple_registry_via_list(mocker): cause = mocker.Mock(event=None, diff=None) registry = SimpleRegistry() - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use ResourceChangingRegistry.get_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) assert isinstance(handlers, collections.abc.Iterable) assert isinstance(handlers, collections.abc.Container) @@ -40,7 +44,8 @@ def test_simple_registry_with_minimal_signature(mocker): registry = SimpleRegistry() registry.register(some_fn) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use ResourceChangingRegistry.get_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is some_fn @@ -57,7 +62,8 @@ def test_global_registry_via_iter(mocker, resource): assert not isinstance(iterator, collections.abc.Container) assert not isinstance(iterator, (list, tuple)) - handlers = list(iterator) + with pytest.deprecated_call(match=r"use OperatorRegistry.iter_resource_changing_handlers\(\)"): + handlers = list(iterator) assert not handlers @@ -65,7 +71,8 @@ def test_global_registry_via_list(mocker, resource): cause = mocker.Mock(resource=resource, event=None, diff=None) registry = GlobalRegistry() - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) assert isinstance(handlers, collections.abc.Iterable) assert isinstance(handlers, collections.abc.Container) @@ -77,8 +84,10 @@ def test_global_registry_with_minimal_signature(mocker, resource): cause = mocker.Mock(resource=resource, event=None, diff=None) registry = GlobalRegistry() - registry.register_cause_handler(resource.group, resource.version, resource.plural, some_fn) - handlers = registry.get_cause_handlers(cause) + with pytest.deprecated_call(match=r"use OperatorRegistry.register_resource_changing_handler\(\)"): + registry.register_cause_handler(resource.group, resource.version, resource.plural, some_fn) + with pytest.deprecated_call(match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"): + handlers = registry.get_cause_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is some_fn diff --git a/tests/registries/test_decorators.py b/tests/registries/test_decorators.py index 018f1cd7..4591300a 100644 --- a/tests/registries/test_decorators.py +++ b/tests/registries/test_decorators.py @@ -23,7 +23,6 @@ def fn(**_): assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].backoff is None - assert handlers[0].cooldown is None # deprecated alias def test_on_cleanup_minimal(): @@ -41,7 +40,6 @@ def fn(**_): assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].backoff is None - assert handlers[0].cooldown is None # deprecated alias def test_on_probe_minimal(): @@ -59,7 +57,6 @@ def fn(**_): assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].backoff is None - assert handlers[0].cooldown is None # deprecated alias # Resume handlers are mixed-in into all resource-changing reactions with initial listing. @@ -82,7 +79,6 @@ def fn(**_): assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].backoff is None - assert handlers[0].cooldown is None # deprecated alias assert handlers[0].labels is None assert handlers[0].annotations is None assert handlers[0].when is None @@ -106,7 +102,6 @@ def fn(**_): assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].backoff is None - assert handlers[0].cooldown is None # deprecated alias assert handlers[0].labels is None assert handlers[0].annotations is None assert handlers[0].when is None @@ -130,7 +125,6 @@ def fn(**_): assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].backoff is None - assert handlers[0].cooldown is None # deprecated alias assert handlers[0].labels is None assert handlers[0].annotations is None assert handlers[0].when is None @@ -154,7 +148,6 @@ def fn(**_): assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].backoff is None - assert handlers[0].cooldown is None # deprecated alias assert handlers[0].labels is None assert handlers[0].annotations is None assert handlers[0].when is None @@ -179,7 +172,6 @@ def fn(**_): assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].backoff is None - assert handlers[0].cooldown is None # deprecated alias assert handlers[0].labels is None assert handlers[0].annotations is None assert handlers[0].when is None @@ -210,7 +202,6 @@ def fn(**_): assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias def test_on_cleanup_with_all_kwargs(mocker): @@ -231,7 +222,6 @@ def fn(**_): assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias def test_on_probe_with_all_kwargs(mocker): @@ -252,7 +242,6 @@ def fn(**_): assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias # Resume handlers are mixed-in into all resource-changing reactions with initial listing. @@ -285,7 +274,6 @@ def fn(**_): assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias assert handlers[0].deleted == True assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} @@ -319,7 +307,6 @@ def fn(**_): assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when @@ -352,7 +339,6 @@ def fn(**_): assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when @@ -390,7 +376,6 @@ def fn(**_): assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when @@ -424,7 +409,6 @@ def fn(**_): assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when diff --git a/tests/registries/test_decorators_deprecated_cooldown.py b/tests/registries/test_decorators_deprecated_cooldown.py index 116c3be1..6f921225 100644 --- a/tests/registries/test_decorators_deprecated_cooldown.py +++ b/tests/registries/test_decorators_deprecated_cooldown.py @@ -8,43 +8,52 @@ def test_on_startup_with_cooldown(): registry = kopf.get_default_registry() - @kopf.on.startup(cooldown=78) - def fn(**_): - pass + with pytest.deprecated_call(match=r"use backoff="): + @kopf.on.startup(cooldown=78) + def fn(**_): + pass handlers = registry.get_activity_handlers(activity=Activity.STARTUP) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias + + with pytest.deprecated_call(match=r"use handler.backoff"): + assert handlers[0].cooldown == 78 def test_on_cleanup_with_cooldown(): registry = kopf.get_default_registry() - @kopf.on.cleanup(cooldown=78) - def fn(**_): - pass + with pytest.deprecated_call(match=r"use backoff="): + @kopf.on.cleanup(cooldown=78) + def fn(**_): + pass handlers = registry.get_activity_handlers(activity=Activity.CLEANUP) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias + + with pytest.deprecated_call(match=r"use handler.backoff"): + assert handlers[0].cooldown == 78 def test_on_probe_with_cooldown(): registry = kopf.get_default_registry() - @kopf.on.probe(cooldown=78) - def fn(**_): - pass + with pytest.deprecated_call(match=r"use backoff="): + @kopf.on.probe(cooldown=78) + def fn(**_): + pass handlers = registry.get_activity_handlers(activity=Activity.PROBE) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias + + with pytest.deprecated_call(match=r"use handler.backoff"): + assert handlers[0].cooldown == 78 # Resume handlers are mixed-in into all resource-changing reactions with initial listing. @@ -55,15 +64,18 @@ def test_on_resume_with_cooldown(mocker, reason): cause = mocker.MagicMock(resource=resource, reason=reason, initial=True, deleted=False) mocker.patch('kopf.reactor.registries.match', return_value=True) - @kopf.on.resume('group', 'version', 'plural', cooldown=78) - def fn(**_): - pass + with pytest.deprecated_call(match=r"use backoff="): + @kopf.on.resume('group', 'version', 'plural', cooldown=78) + def fn(**_): + pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias + + with pytest.deprecated_call(match=r"use handler.backoff"): + assert handlers[0].cooldown == 78 def test_on_create_with_cooldown(mocker): @@ -72,15 +84,18 @@ def test_on_create_with_cooldown(mocker): cause = mocker.MagicMock(resource=resource, reason=Reason.CREATE) mocker.patch('kopf.reactor.registries.match', return_value=True) - @kopf.on.create('group', 'version', 'plural', cooldown=78) - def fn(**_): - pass + with pytest.deprecated_call(match=r"use backoff="): + @kopf.on.create('group', 'version', 'plural', cooldown=78) + def fn(**_): + pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias + + with pytest.deprecated_call(match=r"use handler.backoff"): + assert handlers[0].cooldown == 78 def test_on_update_with_cooldown(mocker): @@ -89,15 +104,18 @@ def test_on_update_with_cooldown(mocker): cause = mocker.MagicMock(resource=resource, reason=Reason.UPDATE) mocker.patch('kopf.reactor.registries.match', return_value=True) - @kopf.on.update('group', 'version', 'plural', cooldown=78) - def fn(**_): - pass + with pytest.deprecated_call(match=r"use backoff="): + @kopf.on.update('group', 'version', 'plural', cooldown=78) + def fn(**_): + pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias + + with pytest.deprecated_call(match=r"use handler.backoff"): + assert handlers[0].cooldown == 78 @pytest.mark.parametrize('optional', [ @@ -110,15 +128,18 @@ def test_on_delete_with_cooldown(mocker, optional): cause = mocker.MagicMock(resource=resource, reason=Reason.DELETE) mocker.patch('kopf.reactor.registries.match', return_value=True) - @kopf.on.delete('group', 'version', 'plural', cooldown=78) - def fn(**_): - pass + with pytest.deprecated_call(match=r"use backoff="): + @kopf.on.delete('group', 'version', 'plural', cooldown=78) + def fn(**_): + pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias + + with pytest.deprecated_call(match=r"use handler.backoff"): + assert handlers[0].cooldown == 78 def test_on_field_with_cooldown(mocker): @@ -128,12 +149,15 @@ def test_on_field_with_cooldown(mocker): cause = mocker.MagicMock(resource=resource, reason=Reason.UPDATE, diff=diff) mocker.patch('kopf.reactor.registries.match', return_value=True) - @kopf.on.field('group', 'version', 'plural', 'field.subfield', cooldown=78) - def fn(**_): - pass + with pytest.deprecated_call(match=r"use backoff="): + @kopf.on.field('group', 'version', 'plural', 'field.subfield', cooldown=78) + def fn(**_): + pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].backoff == 78 - assert handlers[0].cooldown == 78 # deprecated alias + + with pytest.deprecated_call(match=r"use handler.backoff"): + assert handlers[0].cooldown == 78 From 9bfebd24a57d3b1099fd27c3caddf821d62aa7a0 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Wed, 12 Feb 2020 19:21:01 +0100 Subject: [PATCH 12/15] Satisfy the security checks on unclosed files in `setup.py` It was not actually a problem per se, but some code checkers complained on a possible resource leakage. So, why not fix it? --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 01dd8fa9..fdb94938 100644 --- a/setup.py +++ b/setup.py @@ -2,8 +2,10 @@ from setuptools import setup, find_packages -LONG_DESCRIPTION = open(os.path.join(os.path.dirname(__file__), 'README.md')).read() -DESCRIPTION = LONG_DESCRIPTION.splitlines()[0].lstrip('#').strip() +with open(os.path.join(os.path.dirname(__file__), 'README.md')) as f: + LONG_DESCRIPTION = f.read() + DESCRIPTION = LONG_DESCRIPTION.splitlines()[0].lstrip('#').strip() + PROJECT_URLS = { 'Documentation': 'https://kopf.readthedocs.io', 'Bug Tracker': 'https://github.com/zalando-incubator/kopf/issues', From 4bfdb9b4c651091096f0bc5dc67742d8624fb565 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Wed, 19 Feb 2020 19:24:30 +0100 Subject: [PATCH 13/15] Fail on warnings by default: even from CLI, not only in Travis CI --- .travis.yml | 4 ++-- tests/conftest.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 91c9fc6f..0eb47468 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,10 +39,10 @@ before_script: - tools/kubernetes-client.sh script: - - pytest -Werror --cov=kopf --cov-branch + - pytest --cov=kopf --cov-branch - coveralls - codecov --flags unit - - pytest -Werror --only-e2e # NB: after the coverage uploads! + - pytest --only-e2e # NB: after the coverage uploads! - mypy kopf --strict --pretty deploy: diff --git a/tests/conftest.py b/tests/conftest.py index ceba93ff..6e5fe5cf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,6 +26,9 @@ def pytest_configure(config): config.addinivalue_line('markers', "e2e: end-to-end tests with real operators.") config.addinivalue_line('markers', "resource_clustered: (internal parameterizatiom mark).") + # Unexpected warnings should fail the tests. Use `-Wignore` to explicitly disable it. + config.addinivalue_line('filterwarnings', 'error') + def pytest_addoption(parser): parser.addoption("--only-e2e", action="store_true", help="Execute end-to-end tests only.") From 6ec65b3742dc94c5040a23723d1bbf75361d2c49 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Thu, 20 Feb 2020 10:44:14 +0100 Subject: [PATCH 14/15] Switch examples from `kubernetes` to `pykube-ng` The examples are used as the e2e test. With the warnings converted to errors, they fails quite often (randomly) due to SSL sockets not closed properly (ResourceWarnings), noticed within the executor's threads (which are used only for the sync-handlers). Switching to `pykube-ng` and closing the request's session explicitly (instead of on garbage collection) seems to fix it. So as not having the session0owning object module-scoped. --- examples/02-children/example.py | 10 ++++++---- examples/10-builtins/example.py | 8 ++------ examples/99-all-at-once/example.py | 8 ++------ 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/examples/02-children/example.py b/examples/02-children/example.py index 52420072..f6be10a4 100644 --- a/examples/02-children/example.py +++ b/examples/02-children/example.py @@ -1,5 +1,5 @@ import kopf -import kubernetes.client +import pykube import yaml @@ -28,8 +28,10 @@ def create_fn(spec, **kwargs): kopf.adopt(doc) # Actually create an object by requesting the Kubernetes API. - api = kubernetes.client.CoreV1Api() - pod = api.create_namespaced_pod(namespace=doc['metadata']['namespace'], body=doc) + api = pykube.HTTPClient(pykube.KubeConfig.from_env()) + pod = pykube.Pod(api, doc) + pod.create() + api.session.close() # Update the parent's status. - return {'children': [pod.metadata.uid]} + return {'children': [pod.metadata['uid']]} diff --git a/examples/10-builtins/example.py b/examples/10-builtins/example.py index 01d99b7a..6bd2576c 100644 --- a/examples/10-builtins/example.py +++ b/examples/10-builtins/example.py @@ -5,12 +5,6 @@ tasks = {} # dict{namespace: dict{name: asyncio.Task}} -try: - cfg = pykube.KubeConfig.from_service_account() -except FileNotFoundError: - cfg = pykube.KubeConfig.from_file() -api = pykube.HTTPClient(cfg) - @kopf.on.resume('', 'v1', 'pods') @kopf.on.create('', 'v1', 'pods') @@ -36,8 +30,10 @@ async def pod_killer(namespace, name, logger, timeout=30): await asyncio.sleep(timeout) logger.info(f"=== Pod killing happens NOW!") + api = pykube.HTTPClient(pykube.KubeConfig.from_env()) pod = pykube.Pod.objects(api, namespace=namespace).get_by_name(name) pod.delete() + api.session.close() except asyncio.CancelledError: logger.info(f"=== Pod killing is cancelled!") diff --git a/examples/99-all-at-once/example.py b/examples/99-all-at-once/example.py index efe7664b..9bc43d9c 100644 --- a/examples/99-all-at-once/example.py +++ b/examples/99-all-at-once/example.py @@ -18,12 +18,6 @@ E2E_FAILURE_COUNTS = {} E2E_TRACEBACKS = True -try: - cfg = pykube.KubeConfig.from_service_account() -except FileNotFoundError: - cfg = pykube.KubeConfig.from_file() -api = pykube.HTTPClient(cfg) - @kopf.on.startup() async def startup_fn_simple(logger, **kwargs): @@ -112,8 +106,10 @@ def create_pod(**kwargs): kopf.label(pod_data, {'application': 'kopf-example-10'}) # Actually create an object by requesting the Kubernetes API. + api = pykube.HTTPClient(pykube.KubeConfig.from_env()) pod = pykube.Pod(api, pod_data) pod.create() + api.session.close() @kopf.on.event('', 'v1', 'pods', labels={'application': 'kopf-example-10'}) From 379dbb9faa31a99a8c9ba86aadb40a48ec7244a3 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Thu, 20 Feb 2020 10:46:47 +0100 Subject: [PATCH 15/15] Move the loop finalisation within the loop-owning thread --- kopf/toolkits/runner.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/kopf/toolkits/runner.py b/kopf/toolkits/runner.py index 2ce28b42..06044720 100644 --- a/kopf/toolkits/runner.py +++ b/kopf/toolkits/runner.py @@ -67,7 +67,6 @@ def __init__( self.kwargs = kwargs self.reraise = reraise self.timeout = timeout - self._loop = asyncio.new_event_loop() self._stop = threading.Event() self._ready = threading.Event() # NB: not asyncio.Event! self._thread = threading.Thread(target=self._target) @@ -90,8 +89,6 @@ def __exit__( # but instead wait for the thread+loop (CLI command) to finish. self._stop.set() self._thread.join(timeout=self.timeout) - self._loop.run_until_complete(self._loop.shutdown_asyncgens()) - self._loop.close() # If the thread is not finished, it is a bigger problem than exceptions. if self._thread.is_alive(): @@ -115,7 +112,8 @@ def _target(self) -> None: # Every thread must have its own loop. The parent thread (pytest) # needs to know when the loop is set up, to be able to shut it down. - asyncio.set_event_loop(self._loop) + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) self._ready.set() # Execute the requested CLI command in the thread & thread's loop. @@ -128,6 +126,17 @@ def _target(self) -> None: self._future.set_exception(e) else: self._future.set_result(result) + finally: + + # Shut down the API-watching streams. + loop.run_until_complete(loop.shutdown_asyncgens()) + + # Shut down the transports and prevent ResourceWarning: unclosed transport. + # See: https://docs.aiohttp.org/en/stable/client_advanced.html#graceful-shutdown + # TODO: Try a hack: https://github.com/aio-libs/aiohttp/issues/1925#issuecomment-575754386 + loop.run_until_complete(asyncio.sleep(1.0)) + + loop.close() @property def future(self) -> ResultFuture: