Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Officially support 3.10 #164

Merged
merged 4 commits into from Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
68 changes: 27 additions & 41 deletions .circleci/config.yml
@@ -1,7 +1,7 @@
version: 2.1

orbs:
python: cjw296/python-ci@2
python: cjw296/python-ci@3

jobs:
check-package:
Expand Down Expand Up @@ -29,49 +29,35 @@ common: &common
jobs:
- python/pip-run-tests:
name: python27
image: circleci/python:2.7
image: cimg/python:2.7
- python/pip-run-tests:
name: python36
# so we test the mock monkey patches:
image: circleci/python:3.6.6
- python/pip-run-tests:
name: python37
image: circleci/python:3.7
- python/pip-run-tests:
name: python38
image: circleci/python:3.8
image: cimg/python:3.6
- python/pip-run-tests:
name: python39
image: circleci/python:3.9
image: cimg/python:3.9
- python/pip-run-tests:
name: python39-mock-backport
image: circleci/python:3.9
extra_packages: "mock"
name: python310
image: cimg/python:3.10
- python/pip-run-tests:
name: python27-django-1-9
image: circleci/python:2.7
extra_packages: "'django<1.10'"
- python/pip-run-tests:
name: python36-django-1-11
image: circleci/python:3.6
extra_packages: "'django<1.12'"
name: python310-mock-backport
image: cimg/python:3.10
extra_packages: "mock"
- python/pip-run-tests:
name: python39-django-latest
image: circleci/python:3.9
name: python310-django-latest
image: cimg/python:3.10
extra_packages: "django"

- python/coverage:
name: coverage
image: circleci/python:3.7
image: cimg/python:3.10
requires:
- python27
- python36
- python37
- python38
- python39
- python39-mock-backport
- python27-django-1-9
- python36-django-1-11
- python39-django-latest
- python310
- python310-mock-backport
- python310-django-latest

- python/pip-docs:
name: docs
Expand All @@ -88,35 +74,35 @@ common: &common

- check-package:
name: check-package-python27
image: circleci/python:2.7
image: cimg/python:2.7
requires:
- package

- check-package:
name: check-package-python39
image: circleci/python:3.9
name: check-package-python310
image: cimg/python:3.10
requires:
- package

- check-package:
name: check-package-python27-mock
image: circleci/python:2.7
image: cimg/python:2.7
extra_package: mock
imports: "testfixtures, testfixtures.mock"
requires:
- package

- check-package:
name: check-package-python39-mock
image: circleci/python:3.9
name: check-package-python310-mock
image: cimg/python:3.10
extra_package: mock
imports: "testfixtures, testfixtures.mock"
requires:
- package

- check-package:
name: check-package-python39-django
image: circleci/python:3.9
name: check-package-python310-django
image: cimg/python:3.10
extra_package: django
imports: "testfixtures, testfixtures.django"
requires:
Expand All @@ -128,9 +114,9 @@ common: &common
requires:
- check-package-python27
- check-package-python27-mock
- check-package-python39
- check-package-python39-mock
- check-package-python39-django
- check-package-python310
- check-package-python310-mock
- check-package-python310-django

workflows:
push:
Expand Down
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -38,6 +38,7 @@
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
],
packages=find_packages(),
zip_safe=False,
Expand Down
1 change: 1 addition & 0 deletions testfixtures/compat.py
Expand Up @@ -5,6 +5,7 @@

PY_36_PLUS = PY_VERSION >= (3, 6)
PY_37_PLUS = PY_VERSION >= (3, 7)
PY_310_PLUS = PY_VERSION >= (3, 10)


if PY_VERSION > (3, 0):
Expand Down
65 changes: 3 additions & 62 deletions testfixtures/mock.py
Expand Up @@ -35,71 +35,12 @@ class UnittestMockCall:
unittest_mock_call = UnittestMockCall()


def __eq__(self, other):
if other is ANY:
return True
try:
len_other = len(other)
except TypeError:
return False

self_name = ''
if len(self) == 2:
self_args, self_kwargs = self
else:
self_name, self_args, self_kwargs = self

if (getattr(self, 'parent', None) and getattr(other, 'parent', None)
and self.parent != other.parent):
return False

other_name = ''
if len_other == 0:
other_args, other_kwargs = (), {}
elif len_other == 3:
other_name, other_args, other_kwargs = other
elif len_other == 1:
value, = other
if isinstance(value, tuple):
other_args = value
other_kwargs = {}
elif isinstance(value, str):
other_name = value
other_args, other_kwargs = (), {}
else:
other_args = ()
other_kwargs = value
elif len_other == 2:
# could be (name, args) or (name, kwargs) or (args, kwargs)
first, second = other
if isinstance(first, str):
other_name = first
if isinstance(second, tuple):
other_args, other_kwargs = second, {}
else:
other_args, other_kwargs = (), second
else:
other_args, other_kwargs = first, second
else:
return False

if self_name and other_name != self_name:
return False

# this order is important for ANY to work!
return (other_args, other_kwargs) == (self_args, self_kwargs)

has_backport = backport_version is not None
has_unittest_mock = sys.version_info >= (3, 3, 0)

if (
assert (
(has_backport and backport_version[:3] > (2, 0, 0)) or
(3, 6, 7) < sys.version_info[:3] < (3, 7, 0) or
sys.version_info[:3] > (3, 7, 1)
):
parent_name = '_mock_parent'
elif has_unittest_mock or has_backport:
_Call.__eq__ = __eq__
parent_name = 'parent'
else: # pragma: no cover - only hit during testing of packaging.
parent_name = None
), 'Please upgrade Python or Mock Backport'
parent_name = '_mock_parent'
2 changes: 1 addition & 1 deletion testfixtures/popen.py
Expand Up @@ -155,7 +155,7 @@ def communicate(self, input=None, timeout=None):
self.stderr and self.stderr.read())
else:
@record
def wait(self):
def wait(self): # pragma: no cover
"Simulate calls to :meth:`subprocess.Popen.wait`"
self.returncode = self.behaviour.returncode
return self.returncode
Expand Down
41 changes: 25 additions & 16 deletions testfixtures/tests/test_popen.py
Expand Up @@ -6,7 +6,7 @@
from testfixtures import ShouldRaise, compare, Replacer

from testfixtures.popen import MockPopen, PopenBehaviour
from testfixtures.compat import BytesLiteral, PY2
from testfixtures.compat import BytesLiteral, PY2, PY_310_PLUS

import signal

Expand Down Expand Up @@ -471,10 +471,11 @@ def test_default_command_max_args(self):
], Popen.mock.method_calls)

def test_invalid_parameters(self):
message = "__init__() got an unexpected keyword argument 'foo'"
if PY_310_PLUS:
message = "MockPopenInstance." + message
Popen = MockPopen()
with ShouldRaise(TypeError(
"__init__() got an unexpected keyword argument 'foo'"
)):
with ShouldRaise(TypeError(message)):
Popen(foo='bar')

def test_invalid_method_or_attr(self):
Expand All @@ -492,39 +493,43 @@ def test_invalid_attribute(self):
process.foo

def test_invalid_communicate_call(self):
message = "communicate() got an unexpected keyword argument 'foo'"
if PY_310_PLUS:
message = "MockPopenInstance." + message
Popen = MockPopen()
Popen.set_command('bar')
process = Popen('bar')
with ShouldRaise(TypeError(
"communicate() got an unexpected keyword argument 'foo'"
)):
with ShouldRaise(TypeError(message)):
process.communicate(foo='bar')

def test_invalid_wait_call(self):
message = "wait() got an unexpected keyword argument 'foo'"
if PY_310_PLUS:
message = "MockPopenInstance." + message
Popen = MockPopen()
Popen.set_command('bar')
process = Popen('bar')
with ShouldRaise(TypeError(
"wait() got an unexpected keyword argument 'foo'"
)):
with ShouldRaise(TypeError(message)):
process.wait(foo='bar')

def test_invalid_send_signal(self):
message = "send_signal() got an unexpected keyword argument 'foo'"
if PY_310_PLUS:
message = "MockPopenInstance." + message
Popen = MockPopen()
Popen.set_command('bar')
process = Popen('bar')
with ShouldRaise(TypeError(
"send_signal() got an unexpected keyword argument 'foo'"
)):
with ShouldRaise(TypeError(message)):
process.send_signal(foo='bar')

def test_invalid_terminate(self):
message = "terminate() got an unexpected keyword argument 'foo'"
if PY_310_PLUS:
message = "MockPopenInstance." + message
Popen = MockPopen()
Popen.set_command('bar')
process = Popen('bar')
with ShouldRaise(TypeError(
"terminate() got an unexpected keyword argument 'foo'"
)):
with ShouldRaise(TypeError(message)):
process.terminate(foo='bar')

def test_invalid_kill(self):
Expand All @@ -535,6 +540,8 @@ def test_invalid_kill(self):
text = 'kill() takes exactly 1 argument (2 given)'
else:
text = 'kill() takes 1 positional argument but 2 were given'
if PY_310_PLUS:
text = "MockPopenInstance." + text
with ShouldRaise(TypeError(text)):
process.kill('moo')

Expand All @@ -546,6 +553,8 @@ def test_invalid_poll(self):
text = 'poll() takes exactly 1 argument (2 given)'
else:
text = 'poll() takes 1 positional argument but 2 were given'
if PY_310_PLUS:
text = "MockPopenInstance." + text
with ShouldRaise(TypeError(text)):
process.poll('moo')

Expand Down
24 changes: 15 additions & 9 deletions testfixtures/tests/test_replace.py
Expand Up @@ -13,7 +13,7 @@

from testfixtures.tests import sample1
from testfixtures.tests import sample2
from ..compat import PY3
from ..compat import PY3, PY_310_PLUS

from warnings import catch_warnings

Expand Down Expand Up @@ -259,19 +259,25 @@ def test_something(obj):
self.assertFalse(hasattr(sample1, 'foo'))

def test_replace_delattr_cant_remove(self):
if PY_310_PLUS:
message = "cannot set 'today' attribute of " \
"immutable type 'datetime.datetime'"
else:
message = "can't set attributes of " \
"built-in/extension type 'datetime.datetime'"
with Replacer() as r:
with ShouldRaise(TypeError(
"can't set attributes of "
"built-in/extension type 'datetime.datetime'"
)):
with ShouldRaise(TypeError(message)):
r.replace('datetime.datetime.today', not_there)

def test_replace_delattr_cant_remove_not_strict(self):
if PY_310_PLUS:
message = "cannot set 'today' attribute of " \
"immutable type 'datetime.datetime'"
else:
message = "can't set attributes of " \
"built-in/extension type 'datetime.datetime'"
with Replacer() as r:
with ShouldRaise(TypeError(
"can't set attributes of "
"built-in/extension type 'datetime.datetime'"
)):
with ShouldRaise(TypeError(message)):
r.replace('datetime.datetime.today', not_there, strict=False)

def test_replace_dict_remove_key(self):
Expand Down