Repository: celery/py-amqp

Issue:
5.0.6: test suite is using `case` which uses `nose` (which is outdated)
`nose` is for python 2.x and is no longer maintained https://nose.readthedocs.io/en/latest/
```console
+ /usr/bin/pytest -ra
=========================================================================== test session starts ============================================================================
platform linux -- Python 3.8.11, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
benchmark: 3.4.1 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
Using --randomly-seed=670535953
rootdir: /home/tkloczko/rpmbuild/BUILD/py-amqp-5.0.6, configfile: setup.cfg, testpaths: t/unit/, t/integration/
plugins: forked-1.3.0, shutil-1.7.0, virtualenv-1.7.0, expect-1.1.0, flake8-1.0.7, timeout-1.4.2, betamax-0.8.1, freezegun-0.4.2, aspectlib-1.5.2, toolbox-0.5, rerunfailures-9.1.1, requests-mock-1.9.3, cov-2.12.1, pyfakefs-4.5.0, flaky-3.7.0, benchmark-3.4.1, xdist-2.3.0, pylama-7.7.1, datadir-1.3.1, regressions-2.2.0, cases-3.6.3, xprocess-0.18.1, black-0.3.12, anyio-3.3.0, Faker-8.11.0, asyncio-0.15.1, trio-0.7.0, httpbin-1.0.0, subtests-0.5.0, isort-2.0.0, hypothesis-6.14.6, mock-3.6.1, profiling-1.7.0, randomly-3.8.0, checkdocs-2.7.1
collected 235 items / 2 errors / 233 selected

================================================================================== ERRORS ==================================================================================
_________________________________________________________________ ERROR collecting t/unit/test_channel.py __________________________________________________________________
ImportError while importing test module '/home/tkloczko/rpmbuild/BUILD/py-amqp-5.0.6/t/unit/test_channel.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib64/python3.8/importlib/__init__.py:127: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
t/unit/test_channel.py:6: in <module>
    from case import ContextMock
E   ModuleNotFoundError: No module named 'case'
________________________________________________________________ ERROR collecting t/unit/test_connection.py ________________________________________________________________
ImportError while importing test module '/home/tkloczko/rpmbuild/BUILD/py-amqp-5.0.6/t/unit/test_connection.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib64/python3.8/importlib/__init__.py:127: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
t/unit/test_connection.py:7: in <module>
    from case import ContextMock
E   ModuleNotFoundError: No module named 'case'
========================================================================= short test summary info ==========================================================================
ERROR t/unit/test_channel.py
ERROR t/unit/test_connection.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 2 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
============================================================================ 2 errors in 0.73s =============================================================================
pytest-xprocess reminder::Be sure to terminate the started process by running 'pytest --xkill' if you have not explicitly done so in your fixture with 'xprocess.getinfo(<process_name>).terminate()'.
```


Comments:

Patch:
diff --git a/t/mocks.py b/t/mocks.py
new file mode 100644
index 0000000..f91ae9b
--- /dev/null
+++ b/t/mocks.py
@@ -0,0 +1,24 @@
+from unittest.mock import Mock
+
+class _ContextMock(Mock):
+    """Dummy class implementing __enter__ and __exit__
+    as the :keyword:`with` statement requires these to be implemented
+    in the class, not just the instance."""
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *exc_info):
+        pass
+
+
+def ContextMock(*args, **kwargs):
+    """Mock that mocks :keyword:`with` statement contexts."""
+    obj = _ContextMock(*args, **kwargs)
+    obj.attach_mock(_ContextMock(), '__enter__')
+    obj.attach_mock(_ContextMock(), '__exit__')
+    obj.__enter__.return_value = obj
+    # if __exit__ return a value the exception is ignored,
+    # so it must return None here.
+    obj.__exit__.return_value = None
+    return obj


Test patch:
diff --git a/requirements/test.txt b/requirements/test.txt
index 57c3734..242016f 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -1,4 +1,3 @@
-case>=1.3.1
 pytest>=3.0,<=5.3.5
 pytest-sugar>=0.9.1
 pytest-rerunfailures>=6.0
diff --git a/t/unit/conftest.py b/t/unit/conftest.py
new file mode 100644
index 0000000..9a59124
--- /dev/null
+++ b/t/unit/conftest.py
@@ -0,0 +1,54 @@
+from unittest.mock import MagicMock
+import pytest
+
+sentinel = object()
+
+class _patching:
+
+    def __init__(self, monkeypatch, request):
+        self.monkeypatch = monkeypatch
+        self.request = request
+
+    def __getattr__(self, name):
+        return getattr(self.monkeypatch, name)
+
+    def __call__(self, path, value=sentinel, name=None,
+                 new=MagicMock, **kwargs):
+        value = self._value_or_mock(value, new, name, path, **kwargs)
+        self.monkeypatch.setattr(path, value)
+        return value
+
+    def _value_or_mock(self, value, new, name, path, **kwargs):
+        if value is sentinel:
+            value = new(name=name or path.rpartition('.')[2])
+        for k, v in kwargs.items():
+            setattr(value, k, v)
+        return value
+
+    def setattr(self, target, name=sentinel, value=sentinel, **kwargs):
+        # alias to __call__ with the interface of pytest.monkeypatch.setattr
+        if value is sentinel:
+            value, name = name, None
+        return self(target, value, name=name)
+
+    def setitem(self, dic, name, value=sentinel, new=MagicMock, **kwargs):
+        # same as pytest.monkeypatch.setattr but default value is MagicMock
+        value = self._value_or_mock(value, new, name, dic, **kwargs)
+        self.monkeypatch.setitem(dic, name, value)
+        return value
+
+
+@pytest.fixture
+def patching(monkeypatch, request):
+    """Monkeypath.setattr shortcut.
+    Example:
+        .. code-block:: python
+        def test_foo(patching):
+            # execv value here will be mock.MagicMock by default.
+            execv = patching('os.execv')
+            patching('sys.platform', 'darwin')  # set concrete value
+            patching.setenv('DJANGO_SETTINGS_MODULE', 'x.settings')
+            # val will be of type mock.MagicMock by default
+            val = patching.setitem('path.to.dict', 'KEY')
+    """
+    return _patching(monkeypatch, request)
diff --git a/t/unit/test_channel.py b/t/unit/test_channel.py
index 096ffcc..79eb951 100644
--- a/t/unit/test_channel.py
+++ b/t/unit/test_channel.py
@@ -3,7 +3,6 @@ from struct import pack
 from unittest.mock import ANY, MagicMock, Mock, patch
 
 import pytest
-from case import ContextMock
 from vine import promise
 
 from amqp import spec
@@ -13,6 +12,7 @@ from amqp.exceptions import (ConsumerCancelled, MessageNacked, NotFound,
                              RecoverableConnectionError)
 from amqp.serialization import dumps
 
+from t.mocks import ContextMock
 
 class test_Channel:
 
diff --git a/t/unit/test_connection.py b/t/unit/test_connection.py
index 4515585..21faebd 100644
--- a/t/unit/test_connection.py
+++ b/t/unit/test_connection.py
@@ -4,7 +4,6 @@ import warnings
 from unittest.mock import Mock, call, patch
 
 import pytest
-from case import ContextMock
 
 from amqp import Connection, spec
 from amqp.connection import SSLError
@@ -13,6 +12,8 @@ from amqp.exceptions import (ConnectionError, NotFound,
 from amqp.sasl import AMQPLAIN, EXTERNAL, GSSAPI, PLAIN, SASL
 from amqp.transport import TCPTransport
 
+from t.mocks import ContextMock
+
 
 class test_Connection: