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"]


Reply via email to