Repository: python-useful-helpers/exec-helpers

Issue:
Temporary: Stop ssh connection caching
Internal ssh connection caching looks good on high rate reconnect, but it requires complex metaclass and unsafe during connection system rework.
We should stop using current model and re-design safe solution.

Discussion:
coveralls: ## Pull Request Test Coverage Report for [Build 599](https://coveralls.io/builds/24342272)

* **10** of **10**   **(100.0%)**  changed or added relevant lines in **4** files are covered.
* **1** unchanged line in **1** file lost coverage.
* Overall coverage decreased (**-0.08%**) to **97.599%**

---


|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [exec_helpers/_ssh_client_base.py](https://coveralls.io/builds/24342272/source?filename=exec_helpers%2F_ssh_client_base.py#L241) | 1 | 97.96% |
<!-- | **Total:** | **1** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/24342272/badge)](https://coveralls.io/builds/24342272) |
| :-- | --: |
| Change from base [Build 592](https://coveralls.io/builds/23670966): |  -0.08% |
| Covered Lines: | 1177 |
| Relevant Lines: | 1192 |

---
##### 💛  - [Coveralls](https://coveralls.io)

coveralls: ## Pull Request Test Coverage Report for [Build 599](https://coveralls.io/builds/24342272)

* **10** of **10**   **(100.0%)**  changed or added relevant lines in **4** files are covered.
* **1** unchanged line in **1** file lost coverage.
* Overall coverage decreased (**-0.08%**) to **97.599%**

---


|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [exec_helpers/_ssh_client_base.py](https://coveralls.io/builds/24342272/source?filename=exec_helpers%2F_ssh_client_base.py#L241) | 1 | 97.96% |
<!-- | **Total:** | **1** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/24342272/badge)](https://coveralls.io/builds/24342272) |
| :-- | --: |
| Change from base [Build 592](https://coveralls.io/builds/23670966): |  -0.08% |
| Covered Lines: | 1177 |
| Relevant Lines: | 1192 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/README.rst b/README.rst
index 29e2edd..101c9fe 100644
--- a/README.rst
+++ b/README.rst
@@ -29,7 +29,7 @@ you can call command with timeout, but without receiving return code,
 or call command and wait for return code, but without timeout processing.
 
 In the most cases, we are need just simple SSH client with comfortable API for calls, calls via SSH proxy and checking return code/stderr.
-This library offers this functionality with connection memorizing, deadlock free polling and friendly result objects
+This library offers this functionality with deadlock free polling and friendly result objects
 (with inline decoding of XML Element tree, YAML, JSON, binary or just strings).
 In addition this library offers the same API for subprocess calls, but with specific limitation: no parallel calls
 (for protection from race conditions).
@@ -38,7 +38,6 @@ Pros:
 
 * STDOUT and STDERR polling during command execution - no deadlocks.
 * The same API for subprocess and ssh.
-* Connection memorize.
 * Free software: Apache license
 * Open Source: https://github.com/python-useful-helpers/exec-helpers
 * PyPI packaged: https://pypi.python.org/pypi/exec-helpers
@@ -55,8 +54,7 @@ Pros:
 
 This package includes:
 
-* `SSHClient` - historically the first one helper, which used for SSH connections and requires memorization
-  due to impossibility of connection close prediction.
+* `SSHClient` - historically the first one helper, which used for SSH connections.
   Several API calls for sFTP also presents.
 
 * `SSHAuth` - class for credentials storage. `SSHClient` does not store credentials as-is, but uses `SSHAuth` for it.
@@ -125,8 +123,6 @@ Passphrase is an alternate password for keys, if it differs from main password.
 If main key now correct for username - alternate keys tried, if correct key found - it became main.
 If no working key - password is used and None is set as main key.
 
-.. note:: Automatic closing connections during cache record removal supported on CPython implementation only.
-
 Context manager is available, connection is closed and lock is released on exit from context.
 
 .. note:: context manager is strictly not recommended in scenarios with fast reconnect to the same host with te same credentials.
diff --git a/doc/source/SSHClient.rst b/doc/source/SSHClient.rst
index 6e60322..cf19a91 100644
--- a/doc/source/SSHClient.rst
+++ b/doc/source/SSHClient.rst
@@ -80,10 +80,6 @@ API: SSHClient and SSHAuth.
 
         Close connection
 
-    .. py:classmethod:: close()
-
-        Close all memorized connections
-
     .. py:method:: reconnect()
 
         Reconnect SSH session
@@ -147,8 +143,7 @@ API: SSHClient and SSHAuth.
         :type open_stderr: bool
         :param verbose: produce verbose log record on command call
         :type verbose: bool
-        :param log_mask_re: regex lookup rule to mask command for logger.
-                            all MATCHED groups will be replaced by '<*masked*>'
+        :param log_mask_re: regex lookup rule to mask command for logger. all MATCHED groups will be replaced by '<*masked*>'
         :type log_mask_re: ``typing.Optional[str]``
         :param chroot_path: chroot path override
         :type chroot_path: ``typing.Optional[str]``
@@ -176,8 +171,7 @@ API: SSHClient and SSHAuth.
         :type verbose: ``bool``
         :param timeout: Timeout for command execution.
         :type timeout: ``typing.Union[int, float, None]``
-        :param log_mask_re: regex lookup rule to mask command for logger.
-                            all MATCHED groups will be replaced by '<*masked*>'
+        :param log_mask_re: regex lookup rule to mask command for logger. all MATCHED groups will be replaced by '<*masked*>'
         :type log_mask_re: ``typing.Optional[str]``
         :param stdin: pass STDIN text to the process
         :type stdin: ``typing.Union[bytes, str, bytearray, None]``
@@ -196,8 +190,7 @@ API: SSHClient and SSHAuth.
         :type verbose: ``bool``
         :param timeout: Timeout for command execution.
         :type timeout: ``typing.Union[int, float, None]``
-        :param log_mask_re: regex lookup rule to mask command for logger.
-                            all MATCHED groups will be replaced by '<*masked*>'
+        :param log_mask_re: regex lookup rule to mask command for logger. all MATCHED groups will be replaced by '<*masked*>'
         :type log_mask_re: ``typing.Optional[str]``
         :param stdin: pass STDIN text to the process
         :type stdin: ``typing.Union[bytes, str, bytearray, None]``
@@ -222,8 +215,7 @@ API: SSHClient and SSHAuth.
         :type expected: typing.Iterable[typing.Union[int, ExitCodes]]
         :param raise_on_err: Raise exception on unexpected return code
         :type raise_on_err: ``bool``
-        :param log_mask_re: regex lookup rule to mask command for logger.
-                            all MATCHED groups will be replaced by '<*masked*>'
+        :param log_mask_re: regex lookup rule to mask command for logger. all MATCHED groups will be replaced by '<*masked*>'
         :type log_mask_re: ``typing.Optional[str]``
         :param stdin: pass STDIN text to the process
         :type stdin: ``typing.Union[bytes, str, bytearray, None]``
@@ -253,8 +245,7 @@ API: SSHClient and SSHAuth.
         :type raise_on_err: ``bool``
         :param expected: expected return codes (0 by default)
         :type expected: typing.Iterable[typing.Union[int, ExitCodes]]
-        :param log_mask_re: regex lookup rule to mask command for logger.
-                            all MATCHED groups will be replaced by '<*masked*>'
+        :param log_mask_re: regex lookup rule to mask command for logger. all MATCHED groups will be replaced by '<*masked*>'
         :type log_mask_re: ``typing.Optional[str]``
         :param stdin: pass STDIN text to the process
         :type stdin: ``typing.Union[bytes, str, bytearray, None]``
@@ -285,8 +276,7 @@ API: SSHClient and SSHAuth.
         :type timeout: ``typing.Union[int, float, None]``
         :param stdin: pass STDIN text to the process
         :type stdin: typing.Union[bytes, str, bytearray, None]
-        :param log_mask_re: regex lookup rule to mask command for logger.
-                            all MATCHED groups will be replaced by '<*masked*>'
+        :param log_mask_re: regex lookup rule to mask command for logger. all MATCHED groups will be replaced by '<*masked*>'
         :type log_mask_re: typing.Optional[str]
         :param get_pty: open PTY on target machine
         :type get_pty: ``bool``
@@ -318,8 +308,7 @@ API: SSHClient and SSHAuth.
         :type raise_on_err: ``bool``
         :param stdin: pass STDIN text to the process
         :type stdin: typing.Union[bytes, str, bytearray, None]
-        :param log_mask_re: regex lookup rule to mask command for logger.
-                            all MATCHED groups will be replaced by '<*masked*>'
+        :param log_mask_re: regex lookup rule to mask command for logger. all MATCHED groups will be replaced by '<*masked*>'
         :type log_mask_re: typing.Optional[str]
         :param exception_class: Exception to raise on error. Mandatory subclass of ParallelCallProcessError
         :type exception_class: typing.Type[ParallelCallProcessError]
diff --git a/exec_helpers/_ssh_client_base.py b/exec_helpers/_ssh_client_base.py
index 668a3ef..c0515ff 100644
--- a/exec_helpers/_ssh_client_base.py
+++ b/exec_helpers/_ssh_client_base.py
@@ -19,21 +19,16 @@
 __all__ = ("SSHClientBase", "SshExecuteAsyncResult")
 
 # Standard Library
-import abc
 import base64
 import concurrent.futures
 import copy
 import datetime
 import logging
-import platform
 import stat
-import sys
 import time
 import typing
-import warnings
 
 # External Dependencies
-import advanced_descriptors
 import paramiko  # type: ignore
 import tenacity  # type: ignore
 import threaded
@@ -92,123 +87,6 @@ class SshExecuteAsyncResult(api.ExecuteAsyncResult):
         return super(SshExecuteAsyncResult, self).stdout
 
 
-CPYTHON = "CPython" == platform.python_implementation()
-
-
-class _MemorizedSSH(abc.ABCMeta):
-    """Memorize metaclass for SSHClient.
-
-    This class implements caching and managing of SSHClient connections.
-    Class is not in public scope: all required interfaces is accessible throw
-      SSHClient classmethods.
-
-    Main flow is:
-      SSHClient() -> check for cached connection and
-        - If exists the same: check for alive, reconnect if required and return
-        - If exists with different credentials: delete and continue processing
-          create new connection and cache on success
-      * Note: each invocation of SSHClient instance will return current dir to
-        the root of the current user home dir ("cd ~").
-        It is necessary to avoid unpredictable behavior when the same
-        connection is used from different places.
-        If you need to enter some directory and execute command there, please
-        use the following approach:
-        cmd1 = "cd <some dir> && <command1>"
-        cmd2 = "cd <some dir> && <command2>"
-
-    Close cached connections is allowed per-client and all stored:
-      connection will be closed, but still stored in cache for faster reconnect
-
-    Clear cache is strictly not recommended:
-      from this moment all open connections should be managed manually,
-      duplicates is possible.
-    """
-
-    __cache: typing.Dict[typing.Tuple[str, int], "SSHClientBase"] = {}
-
-    def __call__(  # type: ignore
-        cls: "_MemorizedSSH",
-        host: str,
-        port: int = 22,
-        username: typing.Optional[str] = None,
-        password: typing.Optional[str] = None,
-        private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]] = None,
-        auth: typing.Optional[ssh_auth.SSHAuth] = None,
-        verbose: bool = True,
-    ) -> "SSHClientBase":
-        """Main memorize method: check for cached instance and return it. API follows target __init__.
-
-        :param host: remote hostname
-        :type host: str
-        :param port: remote ssh port
-        :type port: int
-        :param username: remote username.
-        :type username: typing.Optional[str]
-        :param password: remote password
-        :type password: typing.Optional[str]
-        :param private_keys: private keys for connection
-        :type private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]]
-        :param auth: credentials for connection
-        :type auth: typing.Optional[ssh_auth.SSHAuth]
-        :param verbose: show additional error/warning messages
-        :type verbose: bool
-        :return: SSH client instance
-        :rtype: SSHClientBase
-        """
-        if (host, port) in cls.__cache:
-            key = host, port
-            if auth is None:
-                auth = ssh_auth.SSHAuth(username=username, password=password, keys=private_keys)
-            if hash((cls, host, port, auth)) == hash(cls.__cache[key]):
-                ssh: "SSHClientBase" = cls.__cache[key]
-                # noinspection PyBroadException
-                try:
-                    ssh.execute("cd ~", timeout=5)
-                except BaseException:  # Note: Do not change to lower level!
-                    ssh.logger.debug("Reconnect")
-                    ssh.reconnect()
-                return ssh
-            if CPYTHON and sys.getrefcount(cls.__cache[key]) == 2:  # pragma: no cover
-                # If we have only cache reference and temporary getrefcount
-                # reference: close connection before deletion
-                cls.__cache[key].logger.debug("Closing as unused")
-                cls.__cache[key].close()  # type: ignore
-            del cls.__cache[key]
-        # noinspection PyArgumentList
-        ssh = super(_MemorizedSSH, cls).__call__(
-            host=host,
-            port=port,
-            username=username,
-            password=password,
-            private_keys=private_keys,
-            auth=auth,
-            verbose=verbose,
-        )
-        cls.__cache[(ssh.hostname, ssh.port)] = ssh
-        return ssh
-
-    @classmethod
-    def clear_cache(mcs: typing.Type["_MemorizedSSH"]) -> None:
-        """Clear cached connections for initialize new instance on next call.
-
-        getrefcount is used to check for usage, so connections closed on CPYTHON only.
-        """
-        n_count = 3
-        # PY3: cache, ssh, temporary
-        for ssh in mcs.__cache.values():
-            if CPYTHON and sys.getrefcount(ssh) == n_count:  # pragma: no cover
-                ssh.logger.debug("Closing as unused")
-                ssh.close()  # type: ignore
-        mcs.__cache = {}
-
-    @classmethod
-    def close_connections(mcs: typing.Type["_MemorizedSSH"]) -> None:
-        """Close connections for selected or all cached records."""
-        for ssh in mcs.__cache.values():
-            if ssh.is_alive:
-                ssh.close()  # type: ignore
-
-
 class _SudoContext:
     """Context manager for call commands with sudo."""
 
@@ -265,7 +143,7 @@ class _KeepAliveContext:
         self.__ssh.keepalive_mode = self.__keepalive_status
 
 
-class SSHClientBase(api.ExecHelper, metaclass=_MemorizedSSH):
+class SSHClientBase(api.ExecHelper):
     """SSH Client helper."""
 
     __slots__ = ("__hostname", "__port", "__auth", "__ssh", "__sftp", "__sudo_mode", "__keepalive_mode", "__verbose")
@@ -415,7 +293,6 @@ class SSHClientBase(api.ExecHelper, metaclass=_MemorizedSSH):
             return self.__sftp
         raise paramiko.SSHException("SFTP connection failed")
 
-    @advanced_descriptors.SeparateClassMethod
     def close(self) -> None:
         """Close SSH and SFTP sessions."""
         with self.lock:
@@ -432,19 +309,6 @@ class SSHClientBase(api.ExecHelper, metaclass=_MemorizedSSH):
                     except Exception:
                         self.logger.exception("Could not close sftp connection")
 
-    # noinspection PyMethodParameters
-    @close.class_method  # type: ignore
-    def close(cls: typing.Type["SSHClientBase"]) -> None:  # pylint: disable=no-self-argument
-        """Close all memorized SSH and SFTP sessions."""
-        # noinspection PyUnresolvedReferences
-        cls.__class__.close_connections()
-
-    @classmethod
-    def _clear_cache(cls: typing.Type["SSHClientBase"]) -> None:
-        """Enforce clear memorized records."""
-        warnings.warn("_clear_cache() is dangerous and not recommended for normal use!", Warning)
-        _MemorizedSSH.clear_cache()
-
     def __del__(self) -> None:
         """Destructor helper: close channel and threads BEFORE closing others.
 
@@ -465,7 +329,7 @@ class SSHClientBase(api.ExecHelper, metaclass=_MemorizedSSH):
         .. versionchanged:: 1.2.1 disconnect enforced on close only not in keepalive mode
         """
         if not self.__keepalive_mode:
-            self.close()  # type: ignore
+            self.close()
         super(SSHClientBase, self).__exit__(exc_type, exc_val, exc_tb)
 
     @property
@@ -505,7 +369,7 @@ class SSHClientBase(api.ExecHelper, metaclass=_MemorizedSSH):
     def reconnect(self) -> None:
         """Reconnect SSH session."""
         with self.lock:
-            self.close()  # type: ignore
+            self.close()
 
             self.__ssh = paramiko.SSHClient()
             self.__ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
@@ -684,7 +548,7 @@ class SSHClientBase(api.ExecHelper, metaclass=_MemorizedSSH):
             if async_result.stderr and async_result.interface.recv_stderr_ready():
                 result.read_stderr(src=async_result.stderr, log=self.logger, verbose=verbose)
 
-        @threaded.threadpooled
+        @threaded.threadpooled  # type: ignore
         def poll_pipes() -> None:
             """Polling task for FIFO buffers."""
             while not async_result.interface.status_event.is_set():
@@ -872,7 +736,7 @@ class SSHClientBase(api.ExecHelper, metaclass=_MemorizedSSH):
         .. versionchanged:: 4.0.0 Expose stdin and log_mask_re as optional keyword-only arguments
         """
 
-        @threaded.threadpooled
+        @threaded.threadpooled  # type: ignore
         def get_result(remote: "SSHClientBase") -> exec_result.ExecResult:
             """Get result from remote call.
 
diff --git a/exec_helpers/api.py b/exec_helpers/api.py
index 6ad1cda..0101be4 100644
--- a/exec_helpers/api.py
+++ b/exec_helpers/api.py
@@ -520,6 +520,23 @@ class ExecHelper(metaclass=abc.ABCMeta):
             stdin=stdin,
             **kwargs,
         )
+        return self._handle_stderr(
+            result=result,
+            error_info=error_info,
+            raise_on_err=raise_on_err,
+            expected=expected,
+            exception_class=exception_class,
+        )
+
+    def _handle_stderr(
+        self,
+        result: exec_result.ExecResult,
+        error_info: typing.Optional[str],
+        raise_on_err: bool,
+        expected: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]],
+        exception_class: "typing.Type[exceptions.CalledProcessError]",
+    ) -> exec_result.ExecResult:
+        """Internal check_stderr logic (synchronous)."""
         append: str = error_info + "\n" if error_info else ""
         if result.stderr:
             message = (
diff --git a/exec_helpers/async_api/api.py b/exec_helpers/async_api/api.py
index 5093185..79b282d 100644
--- a/exec_helpers/async_api/api.py
+++ b/exec_helpers/async_api/api.py
@@ -379,13 +379,10 @@ class ExecHelper(api.ExecHelper, metaclass=abc.ABCMeta):
             stdin=stdin,
             **kwargs,
         )
-        append: str = error_info + "\n" if error_info else ""
-        if result.stderr:
-            message = (
-                f"{append}Command {result.cmd!r} output contains STDERR while not expected\n"
-                f"\texit code: {result.exit_code!s}"
-            )
-            self.logger.error(msg=message)
-            if raise_on_err:
-                raise exception_class(result=result, expected=expected)
-        return result
+        return self._handle_stderr(
+            result=result,
+            error_info=error_info,
+            raise_on_err=raise_on_err,
+            expected=expected,
+            exception_class=exception_class,
+        )
diff --git a/exec_helpers/subprocess_runner.py b/exec_helpers/subprocess_runner.py
index a4ec48e..5e8ba0a 100644
--- a/exec_helpers/subprocess_runner.py
+++ b/exec_helpers/subprocess_runner.py
@@ -126,12 +126,12 @@ class Subprocess(api.ExecHelper):
         .. versionadded:: 1.2.0
         """
 
-        @threaded.threadpooled
+        @threaded.threadpooled  # type: ignore
         def poll_stdout() -> None:
             """Sync stdout poll."""
             result.read_stdout(src=async_result.stdout, log=self.logger, verbose=verbose)
 
-        @threaded.threadpooled
+        @threaded.threadpooled  # type: ignore
         def poll_stderr() -> None:
             """Sync stderr poll."""
             result.read_stderr(src=async_result.stderr, log=self.logger, verbose=verbose)
diff --git a/requirements.txt b/requirements.txt
index 8e70786..feacecc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,6 +2,5 @@ paramiko>=2.4  # LGPLv2.1+
 tenacity>=4.4.0  # Apache-2.0
 six>=1.10.0  # MIT
 threaded>=2.0  # Apache-2.0
-advanced-descriptors>=1.0  # Apache-2.0
 typing >= 3.6 ; python_version < "3.7"  # PSF
 psutil >= 5.0  # BSD


Test patch:
diff --git a/test/test_sftp.py b/test/test_sftp.py
index 9add5fc..4258d55 100644
--- a/test/test_sftp.py
+++ b/test/test_sftp.py
@@ -39,10 +39,6 @@ password = "pass"
 @mock.patch("paramiko.AutoAddPolicy", autospec=True, return_value="AutoAddPolicy")
 @mock.patch("paramiko.SSHClient", autospec=True)
 class TestSftp(unittest.TestCase):
-    def tearDown(self):
-        with mock.patch("warnings.warn"):
-            exec_helpers.SSHClient._clear_cache()
-
     @staticmethod
     def prepare_sftp_file_tests(client):
         _ssh = mock.Mock()
diff --git a/test/test_ssh_client_execute.py b/test/test_ssh_client_execute.py
index f03423d..0ef8024 100644
--- a/test/test_ssh_client_execute.py
+++ b/test/test_ssh_client_execute.py
@@ -248,12 +248,6 @@ def execute(mocker, exec_result):
     return mocker.patch("exec_helpers.ssh_client.SSHClient.execute", name="execute", return_value=exec_result)
 
 
-def teardown_function(function):
-    """Clean-up after tests."""
-    with mock.patch("warnings.warn"):
-        exec_helpers.SSHClient._clear_cache()
-
-
 def test_001_execute_async(ssh, paramiko_ssh_client, ssh_transport_channel, chan_makefile, run_parameters, get_logger):
     open_stdout = run_parameters["open_stdout"]
     open_stderr = run_parameters["open_stderr"]
diff --git a/test/test_ssh_client_execute_async_special.py b/test/test_ssh_client_execute_async_special.py
index 304703c..f2d6d9b 100644
--- a/test/test_ssh_client_execute_async_special.py
+++ b/test/test_ssh_client_execute_async_special.py
@@ -113,12 +113,6 @@ def exec_result():
     return exec_helpers.ExecResult(cmd=command, stdin=None, stdout=stdout_src, stderr=stderr_src, exit_code=0)
 
 
-def teardown_function(function):
-    """Clean-up after tests."""
-    with mock.patch("warnings.warn"):
-        exec_helpers.SSHClient._clear_cache()
-
-
 def test_001_execute_async_sudo(ssh, ssh_transport_channel):
     ssh.sudo_mode = True
 
diff --git a/test/test_ssh_client_execute_special.py b/test/test_ssh_client_execute_special.py
index 899beeb..cca8fac 100644
--- a/test/test_ssh_client_execute_special.py
+++ b/test/test_ssh_client_execute_special.py
@@ -124,12 +124,6 @@ def exec_result():
     return exec_helpers.ExecResult(cmd=command, stdin=None, stdout=stdout_src, stderr=stderr_src, exit_code=0)
 
 
-def teardown_function(function):
-    """Clean-up after tests."""
-    with mock.patch("warnings.warn"):
-        exec_helpers.SSHClient._clear_cache()
-
-
 def test_001_mask_command(ssh, get_logger) -> None:
     cmd = "USE='secret=secret_pass' do task"
     log_mask_re = r"secret\s*=\s*([A-Z-a-z0-9_\-]+)"
diff --git a/test/test_ssh_client_execute_throw_host.py b/test/test_ssh_client_execute_throw_host.py
index b99049b..38f0013 100644
--- a/test/test_ssh_client_execute_throw_host.py
+++ b/test/test_ssh_client_execute_throw_host.py
@@ -131,12 +131,6 @@ def exec_result():
     return exec_helpers.ExecResult(cmd=command, stdin=None, stdout=stdout_src, stderr=stderr_src, exit_code=0)
 
 
-def teardown_function(function):
-    """Clean-up after tests."""
-    with mock.patch("warnings.warn"):
-        exec_helpers.SSHClient._clear_cache()
-
-
 def test_01_execute_through_host_no_creds(ssh, ssh_transport, exec_result) -> None:
     target = "127.0.0.2"
     result = ssh.execute_through_host(target, command)
diff --git a/test/test_ssh_client_init_basic.py b/test/test_ssh_client_init_basic.py
index f743d80..3864485 100644
--- a/test/test_ssh_client_init_basic.py
+++ b/test/test_ssh_client_init_basic.py
@@ -102,12 +102,6 @@ def run_parameters(request):
     return configs[request.param]
 
 
-def teardown_function(function):
-    """Clean-up after tests."""
-    with mock.patch("warnings.warn"):
-        exec_helpers.SSHClient._clear_cache()
-
-
 def test_init_base(paramiko_ssh_client, auto_add_policy, run_parameters, ssh_auth_logger):
     """Parametrized validation of SSH client initialization."""
     # Helper code
diff --git a/test/test_ssh_client_init_special.py b/test/test_ssh_client_init_special.py
index 174ad94..7d36352 100644
--- a/test/test_ssh_client_init_special.py
+++ b/test/test_ssh_client_init_special.py
@@ -13,7 +13,6 @@
 #    under the License.
 
 # Standard Library
-import platform
 import typing
 from unittest import mock
 
@@ -56,12 +55,6 @@ username = "user"
 password = "pass"
 
 
-def teardown_function(function):
-    """Clean-up after tests."""
-    with mock.patch("warnings.warn"):
-        exec_helpers.SSHClient._clear_cache()
-
-
 def test_001_require_key(paramiko_ssh_client, auto_add_policy, ssh_auth_logger):
     """Reject key and allow to connect without key."""
     # Helper code
@@ -383,78 +376,3 @@ def test_014_sftp_repair(paramiko_ssh_client, auto_add_policy, ssh_auth_logger,
     sftp = ssh._sftp
     assert sftp == open_sftp()
     log.assert_has_calls((mock.call.debug("SFTP is not connected, try to connect..."),))
-
-
-def test_015_memorize(paramiko_ssh_client, auto_add_policy, ssh_auth_logger, mocker):
-    """Memorize."""
-    # Helper code
-    mocker.patch("exec_helpers.exec_result.ExecResult")
-    close_conn = mocker.patch("exec_helpers.ssh_client.SSHClient.close_connections")
-    # Test
-    port1 = 2222
-    host1 = "127.0.0.2"
-
-    # 1. Normal init
-    ssh01 = exec_helpers.SSHClient(host=host)
-    ssh02 = exec_helpers.SSHClient(host=host)
-    ssh11 = exec_helpers.SSHClient(host=host, port=port1)
-    ssh12 = exec_helpers.SSHClient(host=host, port=port1)
-    ssh21 = exec_helpers.SSHClient(host=host1)
-    ssh22 = exec_helpers.SSHClient(host=host1)
-
-    assert ssh01 is ssh02
-    assert ssh11 is ssh12
-    assert ssh21 is ssh22
-
-    assert ssh01 is not ssh11
-    assert ssh01 is not ssh21
-    assert ssh11 is not ssh21
-
-    exec_helpers.SSHClient.close()
-    close_conn.assert_not_called()
-
-    # Mock returns false-connected state, so we just count close calls
-    paramiko_ssh_client().close.assert_has_calls((mock.call(), mock.call(), mock.call()))
-
-    # change creds
-    exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth(username=username))
-
-    # Change back: new connection differs from old with the same creds
-    ssh004 = exec_helpers.SSHAuth(host)
-
-    assert ssh01 is not ssh004
-
-
-def test_016_memorize_reconnect(paramiko_ssh_client, auto_add_policy, ssh_auth_logger, mocker):
-    """Re-connect."""
-    # Helper code
-    mocker.patch("exec_helpers.ssh_client.SSHClient.execute", side_effect=paramiko.SSHException)
-    # Test
-    exec_helpers.SSHClient(host=host)
-    paramiko_ssh_client.reset_mock()
-    auto_add_policy.reset_mock()
-    exec_helpers.SSHClient(host=host)
-    paramiko_ssh_client.assert_called_once()
-    auto_add_policy.assert_called_once()
-
-
-@pytest.mark.skipif(
-    "CPython" != platform.python_implementation(),
-    reason="CPython only functionality: close connections depend on refcount",
-)
-def test_017_memorize_close_unused(paramiko_ssh_client, auto_add_policy, ssh_auth_logger, mocker):
-    """Close unused connections."""
-    # Helper code
-    mocker.patch("warnings.warn")
-
-    # Test
-    ssh0 = exec_helpers.SSHClient(host=host)
-    del ssh0  # remove reference - now it's cached and unused
-    paramiko_ssh_client.reset_mock()
-    # New connection on the same host:port with different auth
-    ssh1 = exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth(username=username))
-    paramiko_ssh_client.assert_has_calls((mock.call().close(),))
-    del ssh1  # remove reference - now it's cached and unused
-    paramiko_ssh_client.reset_mock()
-    exec_helpers.SSHClient._clear_cache()
-    paramiko_ssh_client.assert_has_calls((mock.call().close(),))