Hello community, here is the log from the commit of package python-testfixtures for openSUSE:Factory checked in at 2020-03-27 00:21:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-testfixtures (Old) and /work/SRC/openSUSE:Factory/.python-testfixtures.new.3160 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-testfixtures" Fri Mar 27 00:21:36 2020 rev:11 rq:784177 version:6.14.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-testfixtures/python-testfixtures.changes 2019-07-22 17:19:51.969903066 +0200 +++ /work/SRC/openSUSE:Factory/.python-testfixtures.new.3160/python-testfixtures.changes 2020-03-27 00:21:40.156146535 +0100 @@ -1,0 +2,49 @@ +Thu Mar 12 08:05:21 UTC 2020 - Tomáš Chvátal <tchva...@suse.com> + +- Fix build without python2 + +------------------------------------------------------------------- +Wed Mar 11 12:16:59 UTC 2020 - pgaj...@suse.com + +- version update to 6.14.0 + 6.14.0 (24 Feb 2020) + -------------------- + - Add support for non-deterministic logging order when using :meth:`twisted.LogCapture`. + 6.13.1 (20 Feb 2020) + -------------------- + - Fix for using :func:`compare` to compare two-element :func:`~unittest.mock.call` + objects. + 6.13.0 (18 Feb 2020) + -------------------- + - Allow any attributes that need to be ignored to be specified directly when calling + :func:`~testfixtures.comparison.compare_object`. This is handy when writing + comparers for :func:`compare`. + 6.12.1 (16 Feb 2020) + -------------------- + - Fix a bug that occured when using :func:`compare` to compare a string with a + slotted object that had the same :func:`repr` as the string. + 6.12.0 (6 Feb 2020) + ------------------- + - Add support for ``universal_newlines``, ``text``, ``encoding`` and ``errors`` to + :class:`popen.MockPopen`, but only for Python 3. + 6.11.0 (29 Jan 2020) + -------------------- + - :class:`decimal.Decimal` now has better representation when :func:`compare` displays a failed + comparison, particularly on Python 2. + - Add support to :func:`compare` for explicitly naming objects to be compared as ``x`` and ``y``. + This allows symmetry with the ``x_label`` and ``y_label`` parameters that are now documented. + - Restore ability for :class:`Comparison` to compare properties and methods, although these uses + are not recommended. + 6.10.3 (22 Nov 2019) + -------------------- + - Fix bug where new-style classes had their attributes checked with :func:`compare` even + when they were of different types. + 6.10.2 (15 Nov 2019) + -------------------- + - Fix bugs in :func:`compare` when comparing objects which have both ``__slots__`` + and a ``__dict__``. + 6.10.1 (1 Nov 2019) + ------------------- + - Fix edge case where string interning made dictionary comparison output much less useful. + +------------------------------------------------------------------- Old: ---- testfixtures-6.10.0.tar.gz New: ---- testfixtures-6.14.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-testfixtures.spec ++++++ --- /var/tmp/diff_new_pack.mQrO5Q/_old 2020-03-27 00:21:42.848147898 +0100 +++ /var/tmp/diff_new_pack.mQrO5Q/_new 2020-03-27 00:21:42.852147900 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-testfixtures # -# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2020 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,30 +17,32 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} +%bcond_without python2 Name: python-testfixtures -Version: 6.10.0 +Version: 6.14.0 Release: 0 Summary: A collection of helpers and mock objects for unit tests and doc tests License: MIT -Group: Development/Languages/Python URL: https://github.com/Simplistix/testfixtures Source: https://files.pythonhosted.org/packages/source/t/testfixtures/testfixtures-%{version}.tar.gz -BuildRequires: %{python_module pytest >= 3.6} -BuildRequires: %{python_module setuptools} -BuildRequires: fdupes -BuildRequires: python-rpm-macros -BuildRequires: python2-mock # Test case dependencies BuildRequires: %{python_module Django} BuildRequires: %{python_module Twisted} +BuildRequires: %{python_module pytest >= 3.6} BuildRequires: %{python_module pytest-django} +BuildRequires: %{python_module setuptools} BuildRequires: %{python_module sybil} BuildRequires: %{python_module zope.component} +BuildRequires: fdupes +BuildRequires: python-rpm-macros Suggests: python-Django Suggests: python-Twisted Suggests: python-sybil Suggests: python-zope.component BuildArch: noarch +%if %{with python2} +BuildRequires: python2-mock +%endif %python_subpackages %description @@ -68,7 +70,7 @@ %check export DJANGO_SETTINGS_MODULE=testfixtures.tests.test_django.settings -%python_expand $python -m pytest testfixtures/tests +%pytest testfixtures/tests %files %{python_files} %license LICENSE.txt ++++++ testfixtures-6.10.0.tar.gz -> testfixtures-6.14.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/.circleci/config.yml new/testfixtures-6.14.0/.circleci/config.yml --- old/testfixtures-6.10.0/.circleci/config.yml 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/.circleci/config.yml 2020-02-24 16:21:42.000000000 +0100 @@ -38,6 +38,9 @@ name: python37 image: circleci/python:3.7 - python/pip-run-tests: + name: python38 + image: circleci/python:3.8 + - python/pip-run-tests: name: python36-mock-backport # so we test the mock monkey patches aren't used: image: circleci/python:3.6.4 @@ -71,6 +74,7 @@ - python27 - python36 - python37 + - python38 - python36-mock-backport - python37-mock-backport - python27-django-1-9 @@ -99,8 +103,8 @@ - package - check-package: - name: check-package-python37 - image: circleci/python:3.7 + name: check-package-python38 + image: circleci/python:3.8 requires: - package @@ -113,8 +117,8 @@ - package - check-package: - name: check-package-python37-mock - image: circleci/python:3.7 + name: check-package-python38-mock + image: circleci/python:3.8 extra_package: mock imports: "testfixtures, testfixtures.mock" requires: @@ -129,8 +133,8 @@ - package - check-package: - name: check-package-python37-django - image: circleci/python:3.7 + name: check-package-python38-django + image: circleci/python:3.8 extra_package: django imports: "testfixtures, testfixtures.django" requires: @@ -143,9 +147,9 @@ - check-package-python27 - check-package-python27-mock - check-package-python27-django - - check-package-python37 - - check-package-python37-mock - - check-package-python37-django + - check-package-python38 + - check-package-python38-mock + - check-package-python38-django workflows: push: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/.readthedocs.yml new/testfixtures-6.14.0/.readthedocs.yml --- old/testfixtures-6.10.0/.readthedocs.yml 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/.readthedocs.yml 2020-02-24 16:21:42.000000000 +0100 @@ -5,4 +5,6 @@ - method: pip path: . extra_requirements: - - build + - docs +sphinx: + fail_on_warning: true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/CHANGELOG.rst new/testfixtures-6.14.0/CHANGELOG.rst --- old/testfixtures-6.10.0/CHANGELOG.rst 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/CHANGELOG.rst 2020-02-24 16:21:42.000000000 +0100 @@ -3,9 +3,72 @@ .. currentmodule:: testfixtures -6.10.0 (19 Jun 2019) +6.14.0 (24 Feb 2020) +-------------------- + +- Add support for non-deterministic logging order when using :meth:`twisted.LogCapture`. + +6.13.1 (20 Feb 2020) +-------------------- + +- Fix for using :func:`compare` to compare two-element :func:`~unittest.mock.call` + objects. + +Thanks to Daniel Fortunov for the fix. + +6.13.0 (18 Feb 2020) +-------------------- + +- Allow any attributes that need to be ignored to be specified directly when calling + :func:`~testfixtures.comparison.compare_object`. This is handy when writing + comparers for :func:`compare`. + +6.12.1 (16 Feb 2020) +-------------------- + +- Fix a bug that occured when using :func:`compare` to compare a string with a + slotted object that had the same :func:`repr` as the string. + +6.12.0 (6 Feb 2020) ------------------- +- Add support for ``universal_newlines``, ``text``, ``encoding`` and ``errors`` to + :class:`popen.MockPopen`, but only for Python 3. + +6.11.0 (29 Jan 2020) +-------------------- + +- :class:`decimal.Decimal` now has better representation when :func:`compare` displays a failed + comparison, particularly on Python 2. + +- Add support to :func:`compare` for explicitly naming objects to be compared as ``x`` and ``y``. + This allows symmetry with the ``x_label`` and ``y_label`` parameters that are now documented. + +- Restore ability for :class:`Comparison` to compare properties and methods, although these uses + are not recommended. + +Thanks to Daniel Fortunov for all of the above. + +6.10.3 (22 Nov 2019) +-------------------- + +- Fix bug where new-style classes had their attributes checked with :func:`compare` even + when they were of different types. + +6.10.2 (15 Nov 2019) +-------------------- + +- Fix bugs in :func:`compare` when comparing objects which have both ``__slots__`` + and a ``__dict__``. + +6.10.1 (1 Nov 2019) +------------------- + +- Fix edge case where string interning made dictionary comparison output much less useful. + +6.10.0 (19 Jun 2019) +-------------------- + - Better feedback where objects do not :func:`compare` equal but do have the same representation. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/PKG-INFO new/testfixtures-6.14.0/PKG-INFO --- old/testfixtures-6.10.0/PKG-INFO 2019-06-19 08:07:50.000000000 +0200 +++ new/testfixtures-6.14.0/PKG-INFO 2020-02-24 16:21:45.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: testfixtures -Version: 6.10.0 +Version: 6.14.0 Summary: A collection of helpers and mock objects for unit tests and doc tests. Home-page: https://github.com/Simplistix/testfixtures Author: Chris Withers @@ -75,6 +75,7 @@ Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Provides-Extra: test Provides-Extra: docs Provides-Extra: build diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/docs/api.txt new/testfixtures-6.14.0/docs/api.txt --- old/testfixtures-6.10.0/docs/api.txt 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/docs/api.txt 2020-02-24 16:21:42.000000000 +0100 @@ -3,6 +3,8 @@ .. currentmodule:: testfixtures +.. autofunction:: compare(x, y, prefix=None, suffix=None, raises=True, recursive=True, strict=False, comparers=None, **kw) + .. autoclass:: Comparison .. autoclass:: LogCapture @@ -42,30 +44,6 @@ .. autoclass:: TempDirectory :members: -.. autofunction:: compare(x, y, prefix=None, suffix=None, raises=True, recursive=True, strict=False, comparers=None, **kw) - -.. autofunction:: testfixtures.comparison.register - -.. autofunction:: testfixtures.comparison.compare_simple - -.. autofunction:: testfixtures.comparison.compare_object - -.. autofunction:: testfixtures.comparison.compare_exception - -.. autofunction:: testfixtures.comparison.compare_with_type - -.. autofunction:: testfixtures.comparison.compare_sequence - -.. autofunction:: testfixtures.comparison.compare_generator - -.. autofunction:: testfixtures.comparison.compare_tuple - -.. autofunction:: testfixtures.comparison.compare_dict - -.. autofunction:: testfixtures.comparison.compare_set - -.. autofunction:: testfixtures.comparison.compare_text - .. autofunction:: diff .. autofunction:: generator @@ -347,6 +325,32 @@ A singleton used to represent the absence of a particular attribute. + +.. automodule:: testfixtures.comparison + +.. autofunction:: testfixtures.comparison.register + +.. autofunction:: testfixtures.comparison.compare_simple + +.. autofunction:: testfixtures.comparison.compare_object + +.. autofunction:: testfixtures.comparison.compare_exception + +.. autofunction:: testfixtures.comparison.compare_with_type + +.. autofunction:: testfixtures.comparison.compare_sequence + +.. autofunction:: testfixtures.comparison.compare_generator + +.. autofunction:: testfixtures.comparison.compare_tuple + +.. autofunction:: testfixtures.comparison.compare_dict + +.. autofunction:: testfixtures.comparison.compare_set + +.. autofunction:: testfixtures.comparison.compare_text + + .. currentmodule:: testfixtures.popen .. automodule:: testfixtures.popen @@ -361,8 +365,10 @@ automatically set to make comparing django :class:`~django.db.models.Model` instances easier. + .. automodule:: testfixtures.mock + .. automodule:: testfixtures.twisted :member-order: bysource :members: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/setup.cfg new/testfixtures-6.14.0/setup.cfg --- old/testfixtures-6.10.0/setup.cfg 2019-06-19 08:07:50.000000000 +0200 +++ new/testfixtures-6.14.0/setup.cfg 2020-02-24 16:21:45.000000000 +0100 @@ -6,6 +6,7 @@ django_settings_module = testfixtures.tests.test_django.settings filterwarnings = ignore::DeprecationWarning + ignore::PendingDeprecationWarning [egg_info] tag_build = diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/setup.py new/testfixtures-6.14.0/setup.py --- old/testfixtures-6.10.0/setup.py 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/setup.py 2020-02-24 16:21:42.000000000 +0100 @@ -8,6 +8,15 @@ name = 'testfixtures' base_dir = os.path.dirname(__file__) +optional = [ + 'mock;python_version<"3"', + 'zope.component', + 'django<2;python_version<"3"', + 'django;python_version>="3"', + 'sybil', + 'twisted' +] + setup( name=name, version=open(os.path.join(base_dir, name, 'version.txt')).read().strip(), @@ -27,6 +36,7 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', ], packages=find_packages(), zip_safe=False, @@ -35,13 +45,8 @@ test=['pytest>=3.6', 'pytest-cov', 'pytest-django', - 'mock;python_version<"3"', - 'sybil', - 'zope.component', - 'django<2;python_version<"3"', - 'django;python_version>="3"', - 'twisted'], - docs=['sphinx'], + ]+optional, + docs=['sphinx']+optional, build=['setuptools-git', 'wheel', 'twine'] ) ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/testfixtures/comparison.py new/testfixtures-6.14.0/testfixtures/comparison.py --- old/testfixtures-6.10.0/testfixtures/comparison.py 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/testfixtures/comparison.py 2020-02-24 16:21:42.000000000 +0100 @@ -1,3 +1,9 @@ +""" +testfixtures.comparison +----------------------- +""" + +from decimal import Decimal from difflib import unified_diff from functools import partial from pprint import pformat @@ -19,6 +25,8 @@ repr_x = repr(x) repr_y = repr(y) if repr_x == repr_y: + if type(x) is not type(y): + return compare_with_type(x, y, context) x_attrs = _extract_attrs(x) y_attrs = _extract_attrs(y) diff = _compare_mapping(x_attrs, y_attrs, context, x, @@ -32,33 +40,45 @@ def _extract_attrs(obj, ignore=None): + try: + attrs = vars(obj).copy() + except TypeError: + attrs = None + else: + if isinstance(obj, BaseException): + attrs['args'] = obj.args + has_slots = getattr(obj, '__slots__', not_there) is not not_there if has_slots: slots = set() for cls in type(obj).__mro__: slots.update(getattr(cls, '__slots__', ())) - attrs = {} + if slots and attrs is None: + attrs = {} for n in slots: value = getattr(obj, n, not_there) if value is not not_there: attrs[n] = value - else: - try: - attrs = vars(obj).copy() - except TypeError: - return None - else: - if isinstance(obj, BaseException): - attrs['args'] = obj.args + + if attrs is None: + return None + if ignore is not None: - if isinstance(ignore, dict): - ignore = ignore.get(type(obj), ()) for attr in ignore: attrs.pop(attr, None) return attrs -def compare_object(x, y, context): +def _attrs_to_ignore(context, ignore_attributes, obj): + ignore = context.get_option('ignore_attributes', ()) + if isinstance(ignore, dict): + ignore = ignore.get(type(obj), ()) + ignore = set(ignore) + ignore.update(ignore_attributes) + return ignore + + +def compare_object(x, y, context, ignore_attributes=()): """ Compare the two supplied objects based on their type and attributes. @@ -67,12 +87,17 @@ Either a sequence of strings containing attribute names to be ignored when comparing or a mapping of type to sequence of strings containing attribute names to be ignored when comparing that type. + + This may be specified as either a parameter to this function or in the + ``context``. If specified in both, they will both apply with precedence + given to whatever is specified is specified as a parameter. + If specified as a parameter to this fucntion, it may only be a list of + strings. """ - ignore_attributes = context.get_option('ignore_attributes', ()) - if type(x) is not type(y) or isinstance(x, ClassType): + if type(x) is not type(y) or isinstance(x, (ClassType, type)): return compare_simple(x, y, context) - x_attrs = _extract_attrs(x, ignore_attributes) - y_attrs = _extract_attrs(y, ignore_attributes) + x_attrs = _extract_attrs(x, _attrs_to_ignore(context, ignore_attributes, x)) + y_attrs = _extract_attrs(y, _attrs_to_ignore(context, ignore_attributes, y)) if x_attrs is None or y_attrs is None or not (x_attrs and y_attrs): return compare_simple(x, y, context) if x_attrs != y_attrs: @@ -341,8 +366,17 @@ def compare_call(x, y, context): if x == y: return - x_name, x_args, x_kw = x - y_name, y_args, y_kw = y + + def extract(call): + try: + name, args, kwargs = call + except ValueError: + name = None + args, kwargs = call + return name, args, kwargs + + x_name, x_args, x_kw = extract(x) + y_name, y_args, y_kw = extract(y) if x_name == y_name and x_args == y_args and x_kw == y_kw: return compare_call(getattr(x, parent_name), getattr(y, parent_name), context) return compare_text(repr(x), repr(y), context) @@ -372,6 +406,7 @@ Unicode: compare_text, int: compare_simple, float: compare_simple, + Decimal: compare_simple, GeneratorType: compare_generator, mock_call.__class__: compare_call, unittest_mock_call.__class__: compare_call, @@ -450,6 +485,13 @@ if actual is not not_there: possible.append(actual) + x = self.options.pop('x', not_there) + if x is not not_there: + possible.append(x) + y = self.options.pop('y', not_there) + if y is not not_there: + possible.append(y) + if len(possible) != 2: message = 'Exactly two objects needed, you supplied:' if possible: @@ -496,6 +538,9 @@ return '\n\nWhile comparing %s: ' % ''.join(self.breadcrumbs[1:]) def seen(self, x, y): + # don't get confused by string interning: + if isinstance(x, basestring) and isinstance(y, basestring): + return False key = id(x), id(y) if key in self._seen: return True @@ -547,14 +592,17 @@ def compare(*args, **kw): """ - Compare the two arguments passed either positionally or using - explicit ``expected`` and ``actual`` keyword paramaters. An - :class:`AssertionError` will be raised if they are not the same. - The :class:`AssertionError` raised will attempt to provide + Compare two objects, raising an :class:`AssertionError` if they are not + the same. The :class:`AssertionError` raised will attempt to provide descriptions of the differences found. + The two objects to compare can be passed either positionally or using + explicit keyword arguments named ``x`` and ``y``, or ``expected`` and + ``actual``. + Any other keyword parameters supplied will be passed to the functions - that end up doing the comparison. See the API documentation below + that end up doing the comparison. See the + :mod:`API documentation below <testfixtures.comparison>` for details of these. :param prefix: If provided, in the event of an :class:`AssertionError` @@ -565,6 +613,18 @@ being raised, the suffix supplied will be appended to the message in the :class:`AssertionError`. + :param x_label: If provided, in the event of an :class:`AssertionError` + being raised, the object passed as the first positional + argument, or ``x`` keyword argument, will be labelled + with this string in the message in the + :class:`AssertionError`. + + :param x_label: If provided, in the event of an :class:`AssertionError` + being raised, the object passed as the second positional + argument, or ``y`` keyword argument, will be labelled + with this string in the message in the + :class:`AssertionError`. + :param raises: If ``False``, the message that would be raised in the :class:`AssertionError` will be returned instead of the exception being raised. @@ -666,15 +726,19 @@ if self.v is None: return True + remaining_keys = set(self.v.keys()) if self.strict: v = _extract_attrs(other) + remaining_keys -= set(v.keys()) else: v = {} - for k in self.v.keys(): - try: - v[k] = getattr(other, k) - except AttributeError: - pass + + while remaining_keys: + k = remaining_keys.pop() + try: + v[k] = getattr(other, k) + except AttributeError: + pass kw = {'x_label': 'Comparison', 'y_label': 'actual'} context = CompareContext(kw) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/testfixtures/compat.py new/testfixtures-6.14.0/testfixtures/compat.py --- old/testfixtures-6.10.0/testfixtures/compat.py 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/testfixtures/compat.py 2020-02-24 16:21:42.000000000 +0100 @@ -28,6 +28,7 @@ from itertools import zip_longest from functools import reduce from collections.abc import Iterable + from abc import ABC else: @@ -50,3 +51,5 @@ from itertools import izip_longest as zip_longest reduce = reduce from collections import Iterable + from abc import ABCMeta + ABC = ABCMeta('ABC', (object,), {}) # compatible with Python 2 *and* 3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/testfixtures/django.py new/testfixtures-6.14.0/testfixtures/django.py --- old/testfixtures-6.10.0/testfixtures/django.py 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/testfixtures/django.py 2020-02-24 16:21:42.000000000 +0100 @@ -1,3 +1,7 @@ +""" +testfixtures.django +------------------- +""" from __future__ import absolute_import from functools import partial diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/testfixtures/popen.py new/testfixtures-6.14.0/testfixtures/popen.py --- old/testfixtures-6.10.0/testfixtures/popen.py 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/testfixtures/popen.py 2020-02-24 16:21:42.000000000 +0100 @@ -1,9 +1,15 @@ +""" +testfixtures.popen +------------------ +""" + import pipes from functools import wraps, partial +from io import TextIOWrapper from itertools import chain from subprocess import STDOUT, PIPE from tempfile import TemporaryFile -from testfixtures.compat import basestring, PY3, zip_longest, reduce +from testfixtures.compat import basestring, PY3, zip_longest, reduce, PY2 from testfixtures.utils import extend_docstring from .mock import Mock, call @@ -61,7 +67,7 @@ env=None, universal_newlines=False, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), - encoding=None, errors=None): + encoding=None, errors=None, text=None): self.mock = Mock() self.class_instance_mock = mock_class.mock.Popen_instance #: A :func:`unittest.mock.call` representing the call made to instantiate @@ -105,6 +111,8 @@ value.write(mock_value) value.flush() value.seek(0) + if PY3 and (universal_newlines or text or encoding): + value = TextIOWrapper(value, encoding=encoding, errors=errors) setattr(self, name, value) if stdin == PIPE: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/testfixtures/tests/test_compare.py new/testfixtures-6.14.0/testfixtures/tests/test_compare.py --- old/testfixtures-6.10.0/testfixtures/tests/test_compare.py 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/testfixtures/tests/test_compare.py 2020-02-24 16:21:42.000000000 +0100 @@ -1,4 +1,5 @@ from datetime import date, datetime +from decimal import Decimal from functools import partial @@ -19,9 +20,9 @@ from testfixtures.compat import ( class_type_name, exception_module, PY3, xrange, BytesLiteral, UnicodeLiteral, - PY2, PY_37_PLUS + PY2, PY_37_PLUS, ABC ) -from testfixtures.comparison import compare_sequence +from testfixtures.comparison import compare_sequence, compare_object from unittest import TestCase hexaddr = compile('0x[0-9A-Fa-f]+') @@ -36,31 +37,41 @@ _compare = compare -class CompareHelper(object): - def check_raises(self, x=marker, y=marker, message=None, regex=None, - compare=compare, **kw): - args = [] - for value in x, y: - if value is not marker: - args.append(value) - try: - compare(*args, **kw) - except Exception as e: - if not isinstance(e, AssertionError): # pragma: no cover - raise - actual = hexsub(e.args[0]) - if message is not None: - # handy for debugging, but can't be relied on for tests! - _compare(actual, expected=message, show_whitespace=True) - assert actual == message - else: - if not regex.match(actual): # pragma: no cover - raise AssertionError( - '%r did not match %r' % (actual, regex.pattern) - ) +def check_raises(x=marker, y=marker, message=None, regex=None, + compare=compare, **kw): + args = [] + for value in x, y: + if value is not marker: + args.append(value) + for value in 'x', 'y': + explicit = 'explicit_{}'.format(value) + if explicit in kw: + kw[value] = kw[explicit] + del kw[explicit] + try: + compare(*args, **kw) + except Exception as e: + if not isinstance(e, AssertionError): # pragma: no cover + raise + actual = hexsub(e.args[0]) + if message is not None: + # handy for debugging, but can't be relied on for tests! + _compare(actual, expected=message, show_whitespace=True) + assert actual == message else: - raise AssertionError('No exception raised!') + if not regex.match(actual): # pragma: no cover + raise AssertionError( + '%r did not match %r' % (actual, regex.pattern) + ) + else: + raise AssertionError('No exception raised!') + + +class CompareHelper(object): + + def check_raises(self, *args, **kw): + check_raises(*args, **kw) class TestCompare(CompareHelper, TestCase): @@ -84,6 +95,10 @@ def test_number_different(self): self.check_raises(1, 2, '1 != 2') + def test_decimal_different(self): + self.check_raises(Decimal(1), Decimal(2), + "Decimal('1') != Decimal('2')") + def test_different_with_labels(self): self.check_raises(1, 2, '1 (expected) != 2 (actual)', x_label='expected', y_label='actual') @@ -291,6 +306,30 @@ "[4]" ) + def test_list_different_float(self): + self.check_raises( + [1, 2, 3.0], [1, 2, 4.0], + "sequence not as expected:\n\n" + "same:\n" + "[1, 2]\n\n" + "first:\n" + "[3.0]\n\n" + "second:\n" + "[4.0]" + ) + + def test_list_different_decimal(self): + self.check_raises( + [1, 2, Decimal(3)], [1, 2, Decimal(4)], + "sequence not as expected:\n\n" + "same:\n" + "[1, 2]\n\n" + "first:\n" + "[Decimal('3')]\n\n" + "second:\n" + "[Decimal('4')]" + ) + def test_list_totally_different(self): self.check_raises( [1], [2], @@ -748,6 +787,34 @@ pass self.check_raises(X, Y, expected) + def test_new_style_classes_same(self): + class X(object): + pass + compare(X, X) + + def test_new_style_classes_different(self): + if PY3: + expected = ( + "<class 'testfixtures.tests.test_compare.TestCompare." + "test_new_style_classes_different.<locals>.X'>" + " != " + "<class 'testfixtures.tests.test_compare.TestCompare." + "test_new_style_classes_different.<locals>.Y'>" + ) + else: + expected = ( + "<class 'testfixtures.tests.test_compare.X'>" + " != " + "<class 'testfixtures.tests.test_compare.Y'>" + ) + + class X(object): + pass + + class Y(object): + pass + self.check_raises(X, Y, expected) + def test_show_whitespace(self): # does nothing! ;-) self.check_raises( @@ -1325,13 +1392,18 @@ message="'x' (expected) != 'y' (actual)") def test_explicit_both(self): - self.check_raises(message="'x' (expected) != 'y' (actual)", - expected='x', actual='y') + self.check_raises(expected='x', actual='y', + message="'x' (expected) != 'y' (actual)") + + def test_implicit_and_labels(self): + self.check_raises('x', 'y', + x_label='x_label', y_label='y_label', + message="'x' (x_label) != 'y' (y_label)") def test_explicit_and_labels(self): - self.check_raises(message="'x' (x_label) != 'y' (y_label)", - expected='x', actual='y', - x_label='x_label', y_label='y_label') + self.check_raises(explicit_x='x', explicit_y='y', + x_label='x_label', y_label='y_label', + message="'x' (x_label) != 'y' (y_label)") def test_invalid_two_args_expected(self): with ShouldRaise(TypeError( @@ -1447,8 +1519,8 @@ compare(m.mock_calls, m.mock_calls, strict=True) def test_calls_different(self): - m1 =Mock() - m2 =Mock() + m1 = Mock() + m2 = Mock() m1.foo(1, 2, x=3, y=4) m2.bar(1, 3, x=7, y=4) @@ -1472,6 +1544,16 @@ "'call.bar(1, 3, x=7, y=4)'" ) + def test_call_args_different(self): + m = Mock() + m.foo(1) + + self.check_raises( + m.foo.call_args, + call(2), + "'call(1)' != 'call(2)'" + ) + def test_compare_arbitrary_nested_same(self): compare(SampleClassA([SampleClassB()]), SampleClassA([SampleClassB()])) @@ -1619,6 +1701,26 @@ compare(Child(1), Child(1)) + def test_slots_and_attrs(self): + + class Parent(object): + __slots__ = ('a',) + + class Child(Parent): + def __init__(self, a, b): + self.a = a + self.b = b + + self.check_raises(Child(1, 2), Child(1, 3), message=( + 'Child not as expected:\n' + '\n' + 'attributes same:\n' + "['a']\n" + '\n' + 'attributes differ:\n' + "'b': 2 != 3" + )) + def test_partial_callable_different(self): def foo(x): pass @@ -1719,6 +1821,36 @@ message="Both expected and actual appear as 'Wut', but are not equal!" ) + def test_string_with_slotted(self): + + class Slotted(object): + __slots__ = ['foo'] + def __init__(self, foo): + self.foo = foo + def __repr__(self): + return repr(self.foo) + + self.check_raises( + 'foo', + Slotted('foo'), + "'foo' (%s) != 'foo' (%s)" % (repr(str), repr(Slotted)) + ) + + def test_not_recursive(self): + self.check_raises( + {1: 'foo', 2: 'foo'}, + {1: 'bar', 2: 'bar'}, + "dict not as expected:\n" + "\n" + "values differ:\n" + "1: 'foo' != 'bar'\n" + "2: 'foo' != 'bar'\n" + "\n" + "While comparing [1]: 'foo' != 'bar'" + "\n\n" + "While comparing [2]: 'foo' != 'bar'" + ) + class TestIgnore(CompareHelper): @@ -1757,3 +1889,85 @@ "'id': 1 != 2", ignore_attributes=ignore ) + + +class TestCompareObject(object): + + class Thing(object): + def __init__(self, **kw): + for k, v in kw.items(): + setattr(self, k, v) + + def test_ignore(self): + def compare_thing(x, y, context): + return compare_object(x, y, context, ignore_attributes=['y']) + compare(self.Thing(x=1, y=2), self.Thing(x=1, y=3), + comparers={self.Thing: compare_thing}) + + def test_ignore_dict_context_list_param(self): + def compare_thing(x, y, context): + return compare_object(x, y, context, ignore_attributes=['y']) + compare(self.Thing(x=1, y=2, z=3), self.Thing(x=1, y=4, z=5), + comparers={self.Thing: compare_thing}, + ignore_attributes={self.Thing: ['z']}) + + def test_ignore_list_context_list_param(self): + def compare_thing(x, y, context): + return compare_object(x, y, context, ignore_attributes=['y']) + compare(self.Thing(x=1, y=2, z=3), self.Thing(x=1, y=4, z=5), + comparers={self.Thing: compare_thing}, + ignore_attributes=['z']) + + +class BaseClass(ABC): + pass + + +class MyDerivedClass(BaseClass): + + def __init__(self, thing): + self.thing = thing + + +class ConcreteBaseClass(object): pass + + +class ConcreteDerivedClass(ConcreteBaseClass): + + def __init__(self, thing): + self.thing = thing + + +class TestBaseClasses(CompareHelper): + + def test_abc_equal(self): + thing1 = MyDerivedClass(1) + thing2 = MyDerivedClass(1) + + compare(thing1, thing2) + + def test_abc_unequal(self): + thing1 = MyDerivedClass(1) + thing2 = MyDerivedClass(2) + + self.check_raises(thing1, thing2, message=( + "MyDerivedClass not as expected:\n\n" + "attributes differ:\n" + "'thing': 1 != 2" + )) + + def test_concrete_equal(self): + thing1 = ConcreteDerivedClass(1) + thing2 = ConcreteDerivedClass(1) + + compare(thing1, thing2) + + def test_concrete_unequal(self): + thing1 = ConcreteDerivedClass(1) + thing2 = ConcreteDerivedClass(2) + + self.check_raises(thing1, thing2, message=( + "ConcreteDerivedClass not as expected:\n\n" + "attributes differ:\n" + "'thing': 1 != 2" + )) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/testfixtures/tests/test_comparison.py new/testfixtures-6.14.0/testfixtures/tests/test_comparison.py --- old/testfixtures-6.10.0/testfixtures/tests/test_comparison.py 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/testfixtures/tests/test_comparison.py 2020-02-24 16:21:42.000000000 +0100 @@ -427,6 +427,85 @@ AClass(1, 2), ) + def run_property_equal_test(self, strict): + class SomeClass(object): + @property + def prop(self): + return 1 + + self.assertEqual( + C(SomeClass, prop=1, strict=strict), + SomeClass() + ) + + def test_property_equal_strict(self): + self.run_property_equal_test(strict=True) + + def test_property_equal_not_strict(self): + self.run_property_equal_test(strict=False) + + def run_property_not_equal_test(self, strict): + class SomeClass(object): + @property + def prop(self): + return 1 + + c = C(SomeClass, prop=2, strict=strict) + self.assertNotEqual(c, SomeClass()) + compare_repr( + c, + "\n" + "<C(failed):testfixtures.tests.test_comparison.SomeClass>\n" + "attributes differ:\n" + "'prop': 2 (Comparison) != 1 (actual)\n" + "</C>") + + def test_property_not_equal_strict(self): + self.run_property_not_equal_test(strict=True) + + def test_property_not_equal_not_strict(self): + self.run_property_not_equal_test(strict=False) + + def run_method_equal_test(self, strict): + class SomeClass(object): + def method(self): + pass # pragma: no cover + + instance = SomeClass() + self.assertEqual( + C(SomeClass, method=instance.method, strict=strict), + instance + ) + + def test_method_equal_strict(self): + self.run_method_equal_test(strict=True) + + def test_method_equal_not_strict(self): + self.run_method_equal_test(strict=False) + + def run_method_not_equal_test(self, strict): + class SomeClass(object): pass + instance = SomeClass() + instance.method = min + + c = C(SomeClass, method=max, strict=strict) + self.assertNotEqual(c, instance) + compare_repr( + c, + "\n" + "<C(failed):testfixtures.tests.test_comparison.SomeClass>\n" + "attributes differ:\n" + "'method': <built-in function max> (Comparison)" + " != <built-in function min> (actual)\n" + "</C>" + ) + + def test_method_not_equal_strict(self): + self.run_method_not_equal_test(strict=True) + + def test_method_not_equal_not_strict(self): + self.run_method_not_equal_test(strict=False) + def test_exception(self): self.assertEqual( ValueError('foo'), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/testfixtures/tests/test_popen.py new/testfixtures-6.14.0/testfixtures/tests/test_popen.py --- old/testfixtures-6.10.0/testfixtures/tests/test_popen.py 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/testfixtures/tests/test_popen.py 2020-02-24 16:21:42.000000000 +0100 @@ -185,6 +185,54 @@ call.Popen('a command', shell=True, stderr=PIPE, stdout=PIPE), ], Popen.mock.method_calls) + def test_communicate_text_mode(self): + Popen = MockPopen() + Popen.set_command('a command', stdout=b'foo', stderr=b'bar') + # usage + process = Popen('a command', stdout=PIPE, stderr=PIPE, text=True) + actual = process.communicate() + # check + compare(actual, expected=(u'foo', u'bar')) + + def test_communicate_universal_newlines(self): + Popen = MockPopen() + Popen.set_command('a command', stdout=b'foo', stderr=b'bar') + # usage + process = Popen('a command', stdout=PIPE, stderr=PIPE, universal_newlines=True) + actual = process.communicate() + # check + compare(actual, expected=(u'foo', u'bar')) + + def test_communicate_encoding(self): + Popen = MockPopen() + Popen.set_command('a command', stdout=b'foo', stderr=b'bar') + # usage + process = Popen('a command', stdout=PIPE, stderr=PIPE, encoding='ascii') + actual = process.communicate() + # check + compare(actual, expected=(u'foo', u'bar')) + + def test_communicate_encoding_with_errors(self): + Popen = MockPopen() + Popen.set_command('a command', stdout=b'\xa3', stderr=b'\xa3') + # usage + process = Popen('a command', stdout=PIPE, stderr=PIPE, encoding='ascii', errors='ignore') + actual = process.communicate() + # check + if PY2: + compare(actual, expected=(b'\xa3', b'\xa3')) + else: + compare(actual, expected=(u'', u'')) + + def test_read_from_stdout_and_stderr_text_mode(self): + Popen = MockPopen() + Popen.set_command('a command', stdout=b'foo', stderr=b'bar') + # usage + process = Popen('a command', stdout=PIPE, stderr=PIPE, text=True) + actual = process.stdout.read(), process.stderr.read() + # check + compare(actual, expected=(u'foo', u'bar')) + def test_write_to_stdin(self): # setup Popen = MockPopen() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/testfixtures/tests/test_twisted.py new/testfixtures-6.14.0/testfixtures/tests/test_twisted.py --- old/testfixtures-6.10.0/testfixtures/tests/test_twisted.py 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/testfixtures/tests/test_twisted.py 2020-02-24 16:21:42.000000000 +0100 @@ -2,7 +2,8 @@ from twisted.python.failure import Failure from twisted.trial.unittest import TestCase -from testfixtures import compare, ShouldRaise +from testfixtures import compare, ShouldRaise, StringComparison as S, ShouldAssert +from testfixtures.compat import PY3 from testfixtures.twisted import LogCapture, INFO log = Logger() @@ -80,3 +81,87 @@ capture.raise_logged_failure(start_index=1) compare(s.raised.value, expected=TypeError('all gone wrong')) self.flushLoggedErrors() + + def test_order_doesnt_matter_ok(self): + capture = LogCapture.make(self) + log.info('Failed to send BAR') + log.info('Sent FOO, length 1234') + log.info('Sent 1 Messages') + capture.check( + (INFO, S('Sent FOO, length \d+')), + (INFO, 'Failed to send BAR'), + (INFO, 'Sent 1 Messages'), + order_matters=False + ) + + def test_order_doesnt_matter_failure(self): + capture = LogCapture.make(self) + log.info('Failed to send BAR') + log.info('Sent FOO, length 1234') + log.info('Sent 1 Messages') + with ShouldAssert( + "entries not as expected:\n" + "\n" + "expected and found:\n" + "[(<LogLevel=info>, 'Failed to send BAR'), (<LogLevel=info>, 'Sent 1 Messages')]\n" + "\n" + "expected but not found:\n" + "[(<LogLevel=info>, <S:Sent FOO, length abc>)]\n" + "\n" + "other entries:\n" + "[(<LogLevel=info>, {}'Sent FOO, length 1234')]".format('' if PY3 else 'u') + ): + capture.check( + (INFO, S('Sent FOO, length abc')), + (INFO, 'Failed to send BAR'), + (INFO, 'Sent 1 Messages'), + order_matters=False + ) + + def test_order_doesnt_matter_extra_in_expected(self): + capture = LogCapture.make(self) + log.info('Failed to send BAR') + log.info('Sent FOO, length 1234') + with ShouldAssert( + "entries not as expected:\n" + "\n" + "expected and found:\n" + "[(<LogLevel=info>, 'Failed to send BAR'),\n" + " (<LogLevel=info>, <S:Sent FOO, length 1234>)]\n" + "\n" + "expected but not found:\n" + "[(<LogLevel=info>, 'Sent 1 Messages')]\n" + "\n" + "other entries:\n" + "[]" + ): + capture.check( + (INFO, S('Sent FOO, length 1234')), + (INFO, 'Failed to send BAR'), + (INFO, 'Sent 1 Messages'), + order_matters=False + ) + + def test_order_doesnt_matter_extra_in_actual(self): + capture = LogCapture.make(self) + log.info('Failed to send BAR') + log.info('Sent FOO, length 1234') + log.info('Sent 1 Messages') + with ShouldAssert( + "entries not as expected:\n" + "\n" + "expected and found:\n" + "[(<LogLevel=info>, 'Failed to send BAR'), (<LogLevel=info>, 'Sent 1 Messages')]\n" + "\n" + "expected but not found:\n" + "[(<LogLevel=info>, <S:Sent FOO, length abc>)]\n" + "\n" + "other entries:\n" + "[(<LogLevel=info>, {}'Sent FOO, length 1234')]".format('' if PY3 else 'u') + ): + capture.check( + (INFO, S('Sent FOO, length abc')), + (INFO, 'Failed to send BAR'), + (INFO, 'Sent 1 Messages'), + order_matters=False + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/testfixtures/twisted.py new/testfixtures-6.14.0/testfixtures/twisted.py --- old/testfixtures-6.10.0/testfixtures/twisted.py 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/testfixtures/twisted.py 2020-02-24 16:21:42.000000000 +0100 @@ -6,6 +6,8 @@ """ from __future__ import absolute_import +from pprint import pformat + from . import compare from twisted.logger import globalLogPublisher, formatEvent, LogLevel @@ -40,19 +42,44 @@ "Stop capturing." globalLogPublisher._observers = self.original_observers - def check(self, *expected): + def check(self, *expected, **kw): """ Check captured events against those supplied. Please see the ``fields`` parameter to the constructor to see how "actual" events are built. + + :param order_matters: + This defaults to ``True``. If ``False``, the order of expected logging versus + actual logging will be ignored. """ + order_matters = kw.pop('order_matters', True) + assert not kw, 'order_matters is the only keyword parameter' actual = [] for event in self.events: - actual_event = [field(event) if callable(field) else event.get(field) - for field in self.fields] + actual_event = tuple(field(event) if callable(field) else event.get(field) + for field in self.fields) if len(actual_event) == 1: actual_event = actual_event[0] actual.append(actual_event) - compare(expected=expected, actual=actual) + if order_matters: + compare(expected=expected, actual=actual) + else: + expected = list(expected) + matched = [] + unmatched = [] + for entry in actual: + try: + index = expected.index(entry) + except ValueError: + unmatched.append(entry) + else: + matched.append(expected.pop(index)) + if expected: + raise AssertionError(( + 'entries not as expected:\n\n' + 'expected and found:\n%s\n\n' + 'expected but not found:\n%s\n\n' + 'other entries:\n%s' + ) % (pformat(matched), pformat(expected), pformat(unmatched))) def check_failure_text(self, expected, index=-1, attribute='value'): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/testfixtures/version.txt new/testfixtures-6.14.0/testfixtures/version.txt --- old/testfixtures-6.10.0/testfixtures/version.txt 2019-06-19 08:07:46.000000000 +0200 +++ new/testfixtures-6.14.0/testfixtures/version.txt 2020-02-24 16:21:42.000000000 +0100 @@ -1 +1 @@ -6.10.0 +6.14.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/testfixtures.egg-info/PKG-INFO new/testfixtures-6.14.0/testfixtures.egg-info/PKG-INFO --- old/testfixtures-6.10.0/testfixtures.egg-info/PKG-INFO 2019-06-19 08:07:50.000000000 +0200 +++ new/testfixtures-6.14.0/testfixtures.egg-info/PKG-INFO 2020-02-24 16:21:45.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: testfixtures -Version: 6.10.0 +Version: 6.14.0 Summary: A collection of helpers and mock objects for unit tests and doc tests. Home-page: https://github.com/Simplistix/testfixtures Author: Chris Withers @@ -75,6 +75,7 @@ Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Provides-Extra: test Provides-Extra: docs Provides-Extra: build diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testfixtures-6.10.0/testfixtures.egg-info/requires.txt new/testfixtures-6.14.0/testfixtures.egg-info/requires.txt --- old/testfixtures-6.10.0/testfixtures.egg-info/requires.txt 2019-06-19 08:07:50.000000000 +0200 +++ new/testfixtures-6.14.0/testfixtures.egg-info/requires.txt 2020-02-24 16:21:45.000000000 +0100 @@ -6,13 +6,23 @@ [docs] sphinx +zope.component +sybil +twisted + +[docs:python_version < "3"] +mock +django<2 + +[docs:python_version >= "3"] +django [test] pytest>=3.6 pytest-cov pytest-django -sybil zope.component +sybil twisted [test:python_version < "3"]