Repository: zopefoundation/Products.ZCatalog (language: python)

Issue:
Tests in GHA are broken on MacOS
See https://github.com/zopefoundation/Products.ZCatalog/actions/runs/10339538608

It seems not to be able to install the `zope.interface` wheel, but the version of that package has not changed since the last successful run.


Discussion / hints:
Note: I do not plan to work on this issue myself.

Fix patch:
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644
index 00000000..eb4f0c64
--- /dev/null
+++ b/.github/workflows/pre-commit.yml
@@ -0,0 +1,33 @@
+# Generated from:
+# https://github.com/zopefoundation/meta/tree/master/config/zope-product
+name: pre-commit
+
+on:
+  pull_request:
+  push:
+    branches:
+    - master
+  # Allow to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+env:
+  FORCE_COLOR: 1
+
+jobs:
+  pre-commit:
+    name: linting
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v4
+    - uses: actions/setup-python@v5
+      with:
+        python-version: 3.x
+    - uses: pre-commit/action@v3.0.1
+      with:
+        extra_args: --all-files --show-diff-on-failure
+      env:
+        PRE_COMMIT_COLOR: always
+    - uses: pre-commit-ci/lite-action@v1.0.2
+      if: always()
+      with:
+        msg: Apply pre-commit code formatting
diff --git a/.meta.toml b/.meta.toml
index 220b6118..07702e90 100644
--- a/.meta.toml
+++ b/.meta.toml
@@ -2,7 +2,7 @@
 # https://github.com/zopefoundation/meta/tree/master/config/zope-product
 [meta]
 template = "zope-product"
-commit-id = "c412f00f"
+commit-id = "baf6089f"
 
 [python]
 with-pypy = false
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..8d0156c9
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,28 @@
+# Generated from:
+# https://github.com/zopefoundation/meta/tree/master/config/zope-product
+minimum_pre_commit_version: '3.6'
+repos:
+  - repo: https://github.com/pycqa/isort
+    rev: "5.13.2"
+    hooks:
+    - id: isort
+  - repo: https://github.com/hhatto/autopep8
+    rev: "v2.3.1"
+    hooks:
+    - id: autopep8
+      args: [--in-place, --aggressive, --aggressive]
+  - repo: https://github.com/asottile/pyupgrade
+    rev: v3.17.0
+    hooks:
+    - id: pyupgrade
+      args: [--py38-plus]
+  - repo: https://github.com/isidentical/teyit
+    rev: 0.4.3
+    hooks:
+    - id: teyit
+  - repo: https://github.com/PyCQA/flake8
+    rev: "7.1.1"
+    hooks:
+    - id: flake8
+      additional_dependencies:
+        - flake8-debugger == 4.1.2
diff --git a/CHANGES.rst b/CHANGES.rst
index 79329fe2..a89880a0 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,6 +4,8 @@ Changelog
 7.2 (unreleased)
 ----------------
 
+- Add support for Python 3.13.
+
 - Drop support for Python 3.7.
 
 
diff --git a/MANIFEST.in b/MANIFEST.in
index ba206149..b029e347 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -5,6 +5,7 @@ include *.rst
 include *.txt
 include buildout.cfg
 include tox.ini
+include .pre-commit-config.yaml
 
 recursive-include src *.py
 recursive-include src *.dtml
diff --git a/buildout.cfg b/buildout.cfg
index 00e7510c..a4d2cbda 100644
--- a/buildout.cfg
+++ b/buildout.cfg
@@ -8,7 +8,7 @@ parts =
 
 [versions]
 Products.ZCatalog =
-RestrictedPython = >= 5.1
+
 
 [interpreter]
 recipe = zc.recipe.egg
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..3687d376
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,21 @@
+# 
+# Generated from:
+# https://github.com/zopefoundation/meta/tree/master/config/zope-product
+
+[build-system]
+requires = ["setuptools < 74"]
+build-backend = "setuptools.build_meta"
+
+[tool.coverage.run]
+branch = true
+source = ["Products.ZCatalog"]
+
+[tool.coverage.report]
+fail_under = 85
+precision = 2
+ignore_errors = true
+show_missing = true
+exclude_lines = ["pragma: no cover", "pragma: nocover", "except ImportError:", "raise NotImplementedError", "if __name__ == '__main__':", "self.fail", "raise AssertionError", "raise unittest.Skip"]
+
+[tool.coverage.html]
+directory = "parts/htmlcov"
diff --git a/setup.py b/setup.py
index 1eaa4dc9..f50d321b 100644
--- a/setup.py
+++ b/setup.py
@@ -49,6 +49,7 @@
         "Programming Language :: Python :: 3.10",
         "Programming Language :: Python :: 3.11",
         "Programming Language :: Python :: 3.12",
+        "Programming Language :: Python :: 3.13",
         "Programming Language :: Python :: Implementation :: CPython",
         "Topic :: Internet :: WWW/HTTP :: Indexing/Search",
     ],
diff --git a/tox.ini b/tox.ini
index a533a013..8f811ae6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,13 +10,14 @@ envlist =
     py310
     py311
     py312
+    py313
     coverage
 
 [testenv]
 skip_install = true
 deps =
-    setuptools < 69
-    zc.buildout >= 3.0.1
+    setuptools < 74
+    zc.buildout >= 3.1
     wheel > 0.37
     cffi >= 1.17.0rc1
     # The universal2 environment is for Python on macOS built with
@@ -34,18 +35,40 @@ deps =
     universal2: MarkupSafe
     universal2: zope.testrunner
 setenv =
-    py312: VIRTUALENV_PIP=23.1.2
-    py312: PIP_REQUIRE_VIRTUALENV=0
 commands_pre =
     {envbindir}/buildout -nc {toxinidir}/buildout.cfg buildout:directory={envdir} buildout:develop={toxinidir} install test
 commands =
     {envbindir}/test {posargs:-cv}
 
+[testenv:setuptools-latest]
+basepython = python3
+deps =
+    git+https://github.com/pypa/setuptools.git\#egg=setuptools
+    zc.buildout >= 3.1
+    wheel > 0.37
+    cffi >= 1.17.0rc1
+    # The universal2 environment is for Python on macOS built with
+    # universal2 mode instead of architecture-specific. These dependencies
+    # must be installed separately, zc.buildout is incompatible with the
+    # universal2 mode and cannot install them itself.
+    universal2: zope.interface
+    universal2: zope.security
+    universal2: zope.container
+    universal2: Persistence
+    universal2: Acquisition
+    universal2: AccessControl
+    universal2: zodbpickle
+    universal2: charset_normalizer
+    universal2: MarkupSafe
+    universal2: zope.testrunner
+
+
 [testenv:release-check]
 description = ensure that the distribution is ready to release
 basepython = python3
 skip_install = true
 deps =
+    setuptools < 74
     twine
     build
     check-manifest
@@ -59,29 +82,14 @@ commands =
     twine check dist/*
 
 [testenv:lint]
+description = This env runs all linters configured in .pre-commit-config.yaml
 basepython = python3
-commands_pre =
-    mkdir -p {toxinidir}/parts/flake8
-allowlist_externals =
-    mkdir
-commands =
-    isort --check-only --diff {toxinidir}/src {toxinidir}/setup.py
-    flake8 {toxinidir}/src {toxinidir}/setup.py
+skip_install = true
 deps =
-    flake8
-    isort
-    # Useful flake8 plugins that are Python and Plone specific:
-    flake8-coding
-    flake8-debugger
-    mccabe
-
-[testenv:isort-apply]
-basepython = python3
+    pre-commit
 commands_pre =
-deps =
-    isort
 commands =
-    isort {toxinidir}/src {toxinidir}/setup.py []
+    pre-commit run --all-files --show-diff-on-failure
 
 [testenv:coverage]
 basepython = python3
@@ -91,28 +99,9 @@ allowlist_externals =
     mkdir
 deps =
     {[testenv]deps}
-    coverage
+    coverage[toml]
 commands =
     mkdir -p {toxinidir}/parts/htmlcov
     coverage run {envbindir}/test {posargs:-cv}
     coverage html
-    coverage report -m --fail-under=85
-
-[coverage:run]
-branch = True
-source = Products.ZCatalog
-
-[coverage:report]
-precision = 2
-ignore_errors = True
-exclude_lines =
-    pragma: no cover
-    pragma: nocover
-    except ImportError:
-    raise NotImplementedError
-    if __name__ == '__main__':
-    self.fail
-    raise AssertionError
-
-[coverage:html]
-directory = parts/htmlcov
+    coverage report


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 7aaeeb6b..7865fcc6 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -21,18 +21,17 @@ jobs:
         - ["macos", "macos-latest"]
         config:
         # [Python version, tox env]
-        - ["3.11",  "release-check"]
-        - ["3.11",  "lint"]
-        - ["3.8",   "py38"]
-        - ["3.9",   "py39"]
-        - ["3.10",  "py310"]
-        - ["3.11",  "py311"]
-        - ["3.12",  "py312"]
-        - ["3.11",   "coverage"]
+        - ["3.11", "release-check"]
+        - ["3.8", "py38"]
+        - ["3.9", "py39"]
+        - ["3.10", "py310"]
+        - ["3.11", "py311"]
+        - ["3.12", "py312"]
+        - ["3.13", "py313"]
+        - ["3.11", "coverage"]
         exclude:
-          - { os: ["macos", "macos-latest"], config: ["3.11",  "release-check"] }
-          - { os: ["macos", "macos-latest"], config: ["3.11",  "lint"] }
-          - { os: ["macos", "macos-latest"], config: ["3.11",  "coverage"] }
+          - { os: ["macos", "macos-latest"], config: ["3.11", "release-check"] }
+          - { os: ["macos", "macos-latest"], config: ["3.11", "coverage"] }
 
     runs-on: ${{ matrix.os[1] }}
     if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
@@ -43,6 +42,7 @@ jobs:
       uses: actions/setup-python@v5
       with:
         python-version: ${{ matrix.config[0] }}
+        allow-prereleases: true
     - name: Pip cache
       uses: actions/cache@v4
       with:
diff --git a/src/Products/PluginIndexes/BooleanIndex/tests.py b/src/Products/PluginIndexes/BooleanIndex/tests.py
index 3ac8a16d..49d12b7a 100644
--- a/src/Products/PluginIndexes/BooleanIndex/tests.py
+++ b/src/Products/PluginIndexes/BooleanIndex/tests.py
@@ -36,22 +36,22 @@ def test_index_true(self):
         index = self._makeOne()
         obj = Dummy(1, True)
         index._index_object(obj.id, obj, attr='truth')
-        self.assertTrue(1 in index._unindex)
-        self.assertFalse(1 in index._index)
+        self.assertIn(1, index._unindex)
+        self.assertNotIn(1, index._index)
 
     def test_index_false(self):
         index = self._makeOne()
         obj = Dummy(1, False)
         index._index_object(obj.id, obj, attr='truth')
-        self.assertTrue(1 in index._unindex)
-        self.assertFalse(1 in index._index)
+        self.assertIn(1, index._unindex)
+        self.assertNotIn(1, index._index)
 
     def test_index_missing_attribute(self):
         index = self._makeOne()
         obj = Dummy(1, True)
         index._index_object(obj.id, obj, attr='missing')
-        self.assertFalse(1 in index._unindex)
-        self.assertFalse(1 in index._index)
+        self.assertNotIn(1, index._unindex)
+        self.assertNotIn(1, index._index)
 
     def test_search_true(self):
         index = self._makeOne()
diff --git a/src/Products/PluginIndexes/DateIndex/tests.py b/src/Products/PluginIndexes/DateIndex/tests.py
index c09b0f22..3419cf73 100644
--- a/src/Products/PluginIndexes/DateIndex/tests.py
+++ b/src/Products/PluginIndexes/DateIndex/tests.py
@@ -161,7 +161,7 @@ def checkApply():
             self.assertEqual(len(result), len(expectedValues),
                              f'{req}: {result} | {expectedValues}')
             for k, v in expectedValues:
-                self.assertTrue(k in result)
+                self.assertIn(k, result)
 
         cache = index.getRequestCache()
         cache.clear()
@@ -221,16 +221,16 @@ def test_empty(self):
         self.assertEqual(len(index), 0)
         self.assertEqual(len(index.referencedObjects()), 0)
 
-        self.assertTrue(index.getEntryForObject(1234) is None)
+        self.assertIsNone(index.getEntryForObject(1234))
         marker = []
-        self.assertTrue(index.getEntryForObject(1234, marker) is marker)
+        self.assertIs(index.getEntryForObject(1234, marker), marker)
         index.unindex_object(1234)  # shouldn't throw
 
         self.assertTrue(index.hasUniqueValuesFor('date'))
         self.assertFalse(index.hasUniqueValuesFor('foo'))
         self.assertEqual(len(list(index.uniqueValues('date'))), 0)
 
-        self.assertTrue(index._apply_index({'zed': 12345}) is None)
+        self.assertIsNone(index._apply_index({'zed': 12345}))
 
         self._checkApply(index,
                          {'date': DateTime(0)}, [])
@@ -260,9 +260,9 @@ def test_retrieval(self):
         # One empty
         self.assertEqual(len(index.referencedObjects()), len(values) - 1)
 
-        self.assertTrue(index.getEntryForObject(1234) is None)
+        self.assertIsNone(index.getEntryForObject(1234))
         marker = []
-        self.assertTrue(index.getEntryForObject(1234, marker) is marker)
+        self.assertIs(index.getEntryForObject(1234, marker), marker)
         index.unindex_object(1234)  # shouldn't throw
 
         for k, v in values:
@@ -272,7 +272,7 @@ def test_retrieval(self):
 
         self.assertEqual(
             len(list(index.uniqueValues('date'))), len(values) - 2)
-        self.assertTrue(index._apply_index({'bar': 123}) is None)
+        self.assertIsNone(index._apply_index({'bar': 123}))
 
         self._checkApply(index,
                          {'date': DateTime(0)}, values[1:2])
@@ -334,7 +334,7 @@ def test_removal(self):
         self._checkApply(index,
                          {'date': 1072742900}, [values[7]])
         index.index_object(7, None)
-        self.assertFalse(7 in index.documentToKeyMap().keys())
+        self.assertNotIn(7, index.documentToKeyMap().keys())
 
     def test_getCounter(self):
         from DateTime import DateTime
diff --git a/src/Products/PluginIndexes/DateRangeIndex/tests.py b/src/Products/PluginIndexes/DateRangeIndex/tests.py
index 9f4ccd9e..29cf3c3f 100644
--- a/src/Products/PluginIndexes/DateRangeIndex/tests.py
+++ b/src/Products/PluginIndexes/DateRangeIndex/tests.py
@@ -140,12 +140,12 @@ def test_interfaces(self):
     def test_empty(self):
         empty = self._makeOne('empty')
 
-        self.assertTrue(empty.getEntryForObject(1234) is None)
+        self.assertIsNone(empty.getEntryForObject(1234))
         empty.unindex_object(1234)  # shouldn't throw
 
         self.assertFalse(list(empty.uniqueValues('foo')))
         self.assertFalse(list(empty.uniqueValues('foo', withLengths=True)))
-        self.assertTrue(empty._apply_index({'zed': 12345}) is None)
+        self.assertIsNone(empty._apply_index({'zed': 12345}))
 
         self._checkApply(empty, {'empty': 12345}, [])
 
@@ -193,14 +193,14 @@ def test_floor_date(self):
         floor = index.floor_value - 1
         bad = Dummy('bad', floor, None)
         index.index_object(0, bad)
-        self.assertTrue(0 in index._always.keys())
+        self.assertIn(0, index._always.keys())
 
     def test_ceiling_date(self):
         index = self._makeOne('work', 'start', 'stop')
         ceiling = index.ceiling_value + 1
         bad = Dummy('bad', None, ceiling)
         index.index_object(1, bad)
-        self.assertTrue(1 in index._always.keys())
+        self.assertIn(1, index._always.keys())
 
     def test_datetime(self):
         from Products.PluginIndexes.DateIndex.tests import _getEastern
diff --git a/src/Products/PluginIndexes/FieldIndex/tests.py b/src/Products/PluginIndexes/FieldIndex/tests.py
index 4919fe45..e9620515 100644
--- a/src/Products/PluginIndexes/FieldIndex/tests.py
+++ b/src/Products/PluginIndexes/FieldIndex/tests.py
@@ -116,7 +116,7 @@ def checkApply():
             assert len(result) == len(expectedValues), \
                 f'{list(result)} | {expectedValues}'
             for k, v in expectedValues:
-                self.assertTrue(k in result)
+                self.assertIn(k, result)
 
         index = self._index
 
@@ -214,7 +214,7 @@ def testPopulated(self):
     def testNone(self):
         # Make sure None is ignored.
         self._index.index_object(10, Dummy(None))
-        self.assertFalse(None in self._index.uniqueValues('foo'))
+        self.assertNotIn(None, self._index.uniqueValues('foo'))
         self._checkApply({'foo': None}, [])
 
     def testReindexNone(self):
diff --git a/src/Products/PluginIndexes/PathIndex/tests.py b/src/Products/PluginIndexes/PathIndex/tests.py
index 962256fe..e7bb6e7e 100644
--- a/src/Products/PluginIndexes/PathIndex/tests.py
+++ b/src/Products/PluginIndexes/PathIndex/tests.py
@@ -132,7 +132,7 @@ def test_getEntryForObject_miss_no_default(self):
     def test_getEntryForObject_miss_w_default(self):
         index = self._makeOne()
         default = object()
-        self.assertTrue(index.getEntryForObject(1234, default) is default)
+        self.assertIs(index.getEntryForObject(1234, default), default)
 
     def test_getEntryForObject_hit(self):
         index = self._makeOne()
diff --git a/src/Products/PluginIndexes/UUIDIndex/tests.py b/src/Products/PluginIndexes/UUIDIndex/tests.py
index 90784f46..dad41871 100644
--- a/src/Products/PluginIndexes/UUIDIndex/tests.py
+++ b/src/Products/PluginIndexes/UUIDIndex/tests.py
@@ -91,7 +91,7 @@ def checkApply():
             self.assertEqual(used, ('foo', ))
             self.assertEqual(len(result), len(expectedValues))
             for k, v in expectedValues:
-                self.assertTrue(k in result)
+                self.assertIn(k, result)
 
         index = self._index
 
@@ -134,7 +134,7 @@ def test_populated(self):
         values = self._values
         self.assertEqual(len(self._index), len(values))
         self.assertEqual(self._index.indexSize(), len(values))
-        self.assertTrue(self._index.getEntryForObject(10) is None)
+        self.assertIsNone(self._index.getEntryForObject(10))
         self._checkApply({'foo': 'not'}, [])
 
         self._index.unindex_object(10)  # nothrow
@@ -156,7 +156,7 @@ def test_populated(self):
     def test_none(self):
         # Make sure None is ignored.
         self._index.index_object(10, Dummy(None))
-        self.assertFalse(None in self._index.uniqueValues('foo'))
+        self.assertNotIn(None, self._index.uniqueValues('foo'))
         self._checkApply({'foo': None}, [])
 
     def test_reindex(self):
diff --git a/src/Products/ZCTextIndex/tests/testLexicon.py b/src/Products/ZCTextIndex/tests/testLexicon.py
index 9695ed8c..c00eaa26 100644
--- a/src/Products/ZCTextIndex/tests/testLexicon.py
+++ b/src/Products/ZCTextIndex/tests/testLexicon.py
@@ -133,7 +133,7 @@ def process_post_glob(self, lst):
         wids = lexicon.sourceToWordIds('cats and dogs')
         wids = lexicon.termToWordIds('dogs')
         self.assertEqual(len(wids), 1)
-        self.assertTrue(wids[0] > 0)
+        self.assertGreater(wids[0], 0)
 
     def testMissingTermToWordIdsWithProcess_post_glob(self):
         """This test is for added process_post_glob"""
@@ -156,7 +156,7 @@ def testOnePipelineElement(self):
         wids = lexicon.sourceToWordIds('cats and dogs')
         wids = lexicon.termToWordIds('fish')
         self.assertEqual(len(wids), 1)
-        self.assertTrue(wids[0] > 0)
+        self.assertGreater(wids[0], 0)
 
     def testSplitterAdaptorFold(self):
         from Products.ZCTextIndex.Lexicon import CaseNormalizer
@@ -189,7 +189,7 @@ def testTwoElementPipeline(self):
         wids = lexicon.sourceToWordIds('cats and dogs')
         wids = lexicon.termToWordIds('hsif')
         self.assertEqual(len(wids), 1)
-        self.assertTrue(wids[0] > 0)
+        self.assertGreater(wids[0], 0)
 
     def testThreeElementPipeline(self):
         from Products.ZCTextIndex.Lexicon import Splitter
@@ -202,7 +202,7 @@ def testThreeElementPipeline(self):
         wids = lexicon.sourceToWordIds('cats and dogs')
         wids = lexicon.termToWordIds('hsif')
         self.assertEqual(len(wids), 1)
-        self.assertTrue(wids[0] > 0)
+        self.assertGreater(wids[0], 0)
 
     def testSplitterLocaleAwareness(self):
         import locale
diff --git a/src/Products/ZCatalog/tests/test_brains.py b/src/Products/ZCatalog/tests/test_brains.py
index 8c6e653b..1635c568 100644
--- a/src/Products/ZCatalog/tests/test_brains.py
+++ b/src/Products/ZCatalog/tests/test_brains.py
@@ -162,8 +162,10 @@ def testGetObjectHappy(self):
         self.root.REQUEST = request
         self.assertEqual(b.getPath(), '/happy')
         self.assertEqual(b.getObject().REQUEST, request)
-        self.assertTrue(aq_base(b.getObject()) is
-                        aq_base(self.cat.getobject(1)))
+        self.assertIs(
+            aq_base(b.getObject()),
+            aq_base(self.cat.getobject(1))
+        )
 
     def testGetObjectHappy_catalog_as_utility(self):
         request = DummyRequest()
@@ -172,8 +174,10 @@ def testGetObjectHappy_catalog_as_utility(self):
         setRequest(request)
         self.assertEqual(b.getPath(), '/happy')
         self.assertEqual(b.getObject().REQUEST, request)
-        self.assertTrue(aq_base(b.getObject()) is
-                        aq_base(self.cat.getobject(1)))
+        self.assertIs(
+            aq_base(b.getObject()),
+            aq_base(self.cat.getobject(1))
+        )
         clearRequest()
 
     def testGetObjectPropagatesConflictErrors(self):
diff --git a/src/Products/ZCatalog/tests/test_catalog.py b/src/Products/ZCatalog/tests/test_catalog.py
index 8b7c2a0c..4c19f31a 100644
--- a/src/Products/ZCatalog/tests/test_catalog.py
+++ b/src/Products/ZCatalog/tests/test_catalog.py
@@ -234,7 +234,7 @@ def _make_one(self, extra=None):
 
     def test_clear(self):
         catalog = self._make_one()
-        self.assertTrue(len(catalog) > 0)
+        self.assertGreater(len(catalog), 0)
         catalog.clear()
         self.assertEqual(catalog._length(), 0)
         self.assertEqual(len(catalog), 0)
@@ -351,7 +351,7 @@ def extra(catalog):
             col1 = FieldIndex('col1')
             catalog.addIndex('col1', col1)
         catalog = self._make_one(extra=extra)
-        self.assertTrue(len(catalog) > 0)
+        self.assertGreater(len(catalog), 0)
         all_data = catalog({})
         self.assertEqual(len(all_data), 0)
 
@@ -737,7 +737,7 @@ def test_sort_on_two_reverse(self):
                     sort_order='reverse')
         self.assertEqual(len(a), upper)
         for x in range(upper - 1):
-            self.assertTrue(a[x].num > a[x + 1].num)
+            self.assertGreater(a[x].num, a[x + 1].num)
 
     def test_sort_on_two_reverse_with_limit(self):
         catalog = self._make_one()
@@ -768,7 +768,7 @@ def test_sort_on_two_reverse_neither(self):
                     sort_order=('', ''))
         self.assertEqual(len(a), upper)
         for x in range(upper - 1):
-            self.assertTrue(a[x].num < a[x + 1].num)
+            self.assertLess(a[x].num, a[x + 1].num)
 
     def test_sort_on_two_reverse_first(self):
         catalog = self._make_one()
@@ -777,7 +777,7 @@ def test_sort_on_two_reverse_first(self):
                     sort_order=('reverse', ''))
         self.assertEqual(len(a), upper)
         for x in range(upper - 1):
-            self.assertTrue(a[x].num < a[x + 1].num)
+            self.assertLess(a[x].num, a[x + 1].num)
 
     def test_sort_on_two_reverse_second(self):
         catalog = self._make_one()
@@ -786,7 +786,7 @@ def test_sort_on_two_reverse_second(self):
                     sort_order=('', 'reverse'))
         self.assertEqual(len(a), upper)
         for x in range(upper - 1):
-            self.assertTrue(a[x].num > a[x + 1].num)
+            self.assertGreater(a[x].num, a[x + 1].num)
 
     def test_sort_on_two_reverse_both(self):
         catalog = self._make_one()
@@ -795,7 +795,7 @@ def test_sort_on_two_reverse_both(self):
                     sort_order=('reverse', 'reverse'))
         self.assertEqual(len(a), upper)
         for x in range(upper - 1):
-            self.assertTrue(a[x].num > a[x + 1].num)
+            self.assertGreater(a[x].num, a[x + 1].num)
 
     def test_sort_on_two_reverse_too_many(self):
         catalog = self._make_one()
@@ -804,14 +804,14 @@ def test_sort_on_two_reverse_too_many(self):
                     sort_order=('', '', 'reverse', ''))
         self.assertEqual(len(a), upper)
         for x in range(upper - 1):
-            self.assertTrue(a[x].num < a[x + 1].num)
+            self.assertLess(a[x].num, a[x + 1].num)
 
     def test_sort_on_two_small_limit(self):
         catalog = self._make_one()
         a = catalog(sort_on=('att1', 'num'), att1='att1', sort_limit=10)
         self.assertEqual(len(a), 10)
         for x in range(9):
-            self.assertTrue(a[x].num < a[x + 1].num)
+            self.assertLess(a[x].num, a[x + 1].num)
 
     def test_sort_on_two_small_limit_reverse(self):
         catalog = self._make_one()
@@ -819,7 +819,7 @@ def test_sort_on_two_small_limit_reverse(self):
                     sort_limit=10, sort_order='reverse')
         self.assertEqual(len(a), 10)
         for x in range(9):
-            self.assertTrue(a[x].num > a[x + 1].num)
+            self.assertGreater(a[x].num, a[x + 1].num)
 
     def test_sort_on_two_big_limit(self):
         catalog = self._make_one()
@@ -827,7 +827,7 @@ def test_sort_on_two_big_limit(self):
                     sort_limit=self.upper * 3)
         self.assertEqual(len(a), 100)
         for x in range(99):
-            self.assertTrue(a[x].num < a[x + 1].num)
+            self.assertLess(a[x].num, a[x + 1].num)
 
     def test_sort_on_two_big_limit_reverse(self):
         catalog = self._make_one()
@@ -835,7 +835,7 @@ def test_sort_on_two_big_limit_reverse(self):
                     sort_limit=self.upper * 3, sort_order='reverse')
         self.assertEqual(len(a), 100)
         for x in range(99):
-            self.assertTrue(a[x].num > a[x + 1].num)
+            self.assertGreater(a[x].num, a[x + 1].num)
 
     def test_sort_on_three(self):
         def extra(catalog):
@@ -856,7 +856,7 @@ def extra(catalog):
                     sort_order='reverse')
         self.assertEqual(len(a), self.upper)
         for x in range(self.upper - 1):
-            self.assertTrue(a[x].num > a[x + 1].num)
+            self.assertGreater(a[x].num, a[x + 1].num)
 
     def test_sort_on_three_reverse_last(self):
         def extra(catalog):
@@ -867,7 +867,7 @@ def extra(catalog):
                     sort_order=('', '', 'reverse'))
         self.assertEqual(len(a), self.upper)
         for x in range(self.upper - 1):
-            self.assertTrue(a[x].num > a[x + 1].num)
+            self.assertGreater(a[x].num, a[x + 1].num)
 
     def test_sort_on_three_small_limit(self):
         def extra(catalog):
@@ -878,7 +878,7 @@ def extra(catalog):
                     sort_limit=10)
         self.assertEqual(len(a), 10)
         for x in range(9):
-            self.assertTrue(a[x].num < a[x + 1].num)
+            self.assertLess(a[x].num, a[x + 1].num)
 
     def test_sort_on_three_big_limit(self):
         def extra(catalog):
@@ -889,7 +889,7 @@ def extra(catalog):
                     sort_limit=self.upper * 3)
         self.assertEqual(len(a), 100)
         for x in range(99):
-            self.assertTrue(a[x].num < a[x + 1].num)
+            self.assertLess(a[x].num, a[x + 1].num)
 
 
 class TestUnCatalog(unittest.TestCase):
diff --git a/src/Products/ZCatalog/tests/test_zcatalog.py b/src/Products/ZCatalog/tests/test_zcatalog.py
index 34ce16e9..b783b46d 100644
--- a/src/Products/ZCatalog/tests/test_zcatalog.py
+++ b/src/Products/ZCatalog/tests/test_zcatalog.py
@@ -322,8 +322,11 @@ def test_catalogView(self):
         # provide `ZopeVersion`
         catalog.ZopeVersion = lambda *arg, **kw: 4
         vr = catalog.manage_catalogView()
-        self.assertTrue("There are no objects in the Catalog." not in vr,
-                        "catalogView wrongly reports `no objects`")
+        self.assertNotIn(
+            "There are no objects in the Catalog.",
+            vr,
+            "catalogView wrongly reports `no objects`"
+        )
 
 
 class TestAddDelColumnIndex(ZCatalogBase, unittest.TestCase):
@@ -416,7 +419,7 @@ def test_getObject_restricted_raises_Unauthorized(self):
         pickySecurityManager = PickySecurityManager(['fold'])
         setSecurityManager(pickySecurityManager)
         ob = brain.getObject()
-        self.assertFalse(ob is None)
+        self.assertIsNotNone(ob)
         self.assertEqual(ob.getId(), 'ob')
 
     # Now test _unrestrictedGetObject
diff --git a/src/Products/ZCatalog/tests/test_zodb.py b/src/Products/ZCatalog/tests/test_zodb.py
index 12d257cc..d47afe86 100644
--- a/src/Products/ZCatalog/tests/test_zodb.py
+++ b/src/Products/ZCatalog/tests/test_zodb.py
@@ -116,7 +116,7 @@ def test_unmaintained_search(self):
         ignore, cache_size_limit = self._get_zodb_info(catalog)
         # ZODB connection cache grows out of size limit and eats memory
         actual_size = self._actual_cache_size(catalog)
-        self.assertTrue(actual_size >= cache_size_limit * 1.1)
+        self.assertGreaterEqual(actual_size, cache_size_limit * 1.1)
 
     def test_maintained_search(self):
         # run big query with cache maintenance
@@ -125,4 +125,4 @@ def test_maintained_search(self):
         ignore, cache_size_limit = self._get_zodb_info(catalog)
         # ZODB connection cache stays within its size limit
         actual_size = self._actual_cache_size(catalog)
-        self.assertTrue(actual_size <= cache_size_limit + threshold)
+        self.assertLessEqual(actual_size, cache_size_limit + threshold)