From de2a7ce6f517cb437ffba0de79f3e0b9df8ad721 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 25 Mar 2026 14:17:32 +0100 Subject: [PATCH 1/2] Vendor LoggingFixture This was removed from fixtures recently [1]. While it has been re-added [2], the fixture is small enough that we can just vendor it here. A future change will make fixtures a hard requirement of our test suite, since that's trivially doable now that we no longer package our tests. [1] https://github.com/testing-cabal/fixtures/pull/114 [2] https://github.com/testing-cabal/fixtures/pull/122 Signed-off-by: Stephen Finucane --- tests/test_fixturesupport.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/test_fixturesupport.py b/tests/test_fixturesupport.py index 31300f02..72c49483 100644 --- a/tests/test_fixturesupport.py +++ b/tests/test_fixturesupport.py @@ -20,10 +20,23 @@ except ImportError: fixtures = None # type: ignore -try: - from fixtures.tests.helpers import LoggingFixture -except ImportError: - LoggingFixture = None +if fixtures: + + class LoggingFixture(fixtures.Fixture): + def __init__(self, suffix: str = "", calls: list[str] | None = None) -> None: + super().__init__() + if calls is None: + calls = [] + self.calls = calls + self.suffix = suffix + + def setUp(self) -> None: + super().setUp() + self.calls.append("setUp" + self.suffix) + self.addCleanup(self.calls.append, "cleanUp" + self.suffix) + + def reset(self) -> None: + self.calls.append("reset" + self.suffix) class TestFixtureSupport(TestCase): From f9040f7863e396b20f3a3554f2a13048934ee852 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 25 Mar 2026 14:44:33 +0100 Subject: [PATCH 2/2] Make fixtures, testresources required for test suite The tests are no longer packaged, so circular imports are less of a concern than previously. Note that this does not affect testtools itself, where both packages remain optional. Signed-off-by: Stephen Finucane --- pyproject.toml | 2 +- tests/test_fixturesupport.py | 39 ++++------ tests/test_run.py | 133 +++++++++++++++-------------------- tests/test_testresult.py | 12 +--- tests/test_testsuite.py | 12 +--- 5 files changed, 76 insertions(+), 122 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c84dab57..939c8a57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ requires-python = ">=3.10" Homepage = "https://github.com/testing-cabal/testtools" [project.optional-dependencies] -test = ["testscenarios", "testresources"] +test = ["fixtures", "testscenarios", "testresources"] twisted = ["Twisted", "fixtures"] dev = [ "ruff==0.15.7", diff --git a/tests/test_fixturesupport.py b/tests/test_fixturesupport.py index 72c49483..2e8e121f 100644 --- a/tests/test_fixturesupport.py +++ b/tests/test_fixturesupport.py @@ -2,6 +2,8 @@ import unittest +import fixtures + from testtools import ( TestCase, content, @@ -15,36 +17,25 @@ ExtendedTestResult, ) -try: - import fixtures -except ImportError: - fixtures = None # type: ignore - -if fixtures: - class LoggingFixture(fixtures.Fixture): - def __init__(self, suffix: str = "", calls: list[str] | None = None) -> None: - super().__init__() - if calls is None: - calls = [] - self.calls = calls - self.suffix = suffix +class LoggingFixture(fixtures.Fixture): + def __init__(self, suffix: str = "", calls: list[str] | None = None) -> None: + super().__init__() + if calls is None: + calls = [] + self.calls = calls + self.suffix = suffix - def setUp(self) -> None: - super().setUp() - self.calls.append("setUp" + self.suffix) - self.addCleanup(self.calls.append, "cleanUp" + self.suffix) + def setUp(self) -> None: + super().setUp() + self.calls.append("setUp" + self.suffix) + self.addCleanup(self.calls.append, "cleanUp" + self.suffix) - def reset(self) -> None: - self.calls.append("reset" + self.suffix) + def reset(self) -> None: + self.calls.append("reset" + self.suffix) class TestFixtureSupport(TestCase): - def setUp(self): - super().setUp() - if fixtures is None or LoggingFixture is None: - self.skipTest("Need fixtures") - def test_useFixture(self): fixture = LoggingFixture() diff --git a/tests/test_run.py b/tests/test_run.py index a5e18cdb..5650ea50 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -9,6 +9,8 @@ from textwrap import dedent from unittest import TestSuite +import fixtures + import testtools from testtools import TestCase, run, skipUnless from testtools.matchers import ( @@ -17,29 +19,17 @@ MatchesRegex, ) -try: - import fixtures -except ImportError: - fixtures = None # type: ignore - -try: - import testresources -except ImportError: - testresources = None - - -if fixtures: - class SampleTestFixture(fixtures.Fixture): - """Creates testtools.runexample temporarily.""" +class SampleTestFixture(fixtures.Fixture): + """Creates testtools.runexample temporarily.""" - def __init__(self, broken=False): - """Create a SampleTestFixture. + def __init__(self, broken=False): + """Create a SampleTestFixture. - :param broken: If True, the sample file will not be importable. - """ - if not broken: - init_contents = b"""\ + :param broken: If True, the sample file will not be importable. + """ + if not broken: + init_contents = b"""\ from testtools import TestCase class TestFoo(TestCase): @@ -51,33 +41,31 @@ def test_suite(): from unittest import TestLoader return TestLoader().loadTestsFromName(__name__) """ - else: - init_contents = b"class not in\n" - self.package = fixtures.PythonPackage( - "runexample", [("__init__.py", init_contents)] - ) - - def setUp(self): - super().setUp() - self.useFixture(self.package) - testtools.__path__.append(self.package.base) - self.addCleanup(testtools.__path__.remove, self.package.base) - self.addCleanup(sys.modules.pop, "testtools.runexample", None) + else: + init_contents = b"class not in\n" + self.package = fixtures.PythonPackage( + "runexample", [("__init__.py", init_contents)] + ) + def setUp(self): + super().setUp() + self.useFixture(self.package) + testtools.__path__.append(self.package.base) + self.addCleanup(testtools.__path__.remove, self.package.base) + self.addCleanup(sys.modules.pop, "testtools.runexample", None) -if fixtures and testresources: - class SampleResourcedFixture(fixtures.Fixture): - """Creates a test suite that uses testresources.""" +class SampleResourcedFixture(fixtures.Fixture): + """Creates a test suite that uses testresources.""" - def __init__(self): - super().__init__() - self.package = fixtures.PythonPackage( - "resourceexample", - [ - ( - "__init__.py", - b""" + def __init__(self): + super().__init__() + self.package = fixtures.PythonPackage( + "resourceexample", + [ + ( + "__init__.py", + b""" from fixtures import Fixture from testresources import ( FixtureResource, @@ -109,30 +97,28 @@ def test_suite(): from unittest import TestLoader return OptimisingTestSuite(TestLoader().loadTestsFromName(__name__)) """, - ) - ], - ) - - def setUp(self): - super().setUp() - self.useFixture(self.package) - self.addCleanup(testtools.__path__.remove, self.package.base) - testtools.__path__.append(self.package.base) + ) + ], + ) + def setUp(self): + super().setUp() + self.useFixture(self.package) + self.addCleanup(testtools.__path__.remove, self.package.base) + testtools.__path__.append(self.package.base) -if fixtures: - class SampleLoadTestsPackage(fixtures.Fixture): - """Creates a test suite package using load_tests.""" +class SampleLoadTestsPackage(fixtures.Fixture): + """Creates a test suite package using load_tests.""" - def __init__(self): - super().__init__() - self.package = fixtures.PythonPackage( - "discoverexample", - [ - ( - "__init__.py", - b""" + def __init__(self): + super().__init__() + self.package = fixtures.PythonPackage( + "discoverexample", + [ + ( + "__init__.py", + b""" from testtools import TestCase, clone_test_with_new_id class TestExample(TestCase): @@ -143,22 +129,17 @@ def load_tests(loader, tests, pattern): tests.addTest(clone_test_with_new_id(tests._tests[1]._tests[0], "fred")) return tests """, - ) - ], - ) - - def setUp(self): - super().setUp() - self.useFixture(self.package) - self.addCleanup(sys.path.remove, self.package.base) - + ) + ], + ) -class TestRun(TestCase): def setUp(self): super().setUp() - if fixtures is None: - self.skipTest("Need fixtures") + self.useFixture(self.package) + self.addCleanup(sys.path.remove, self.package.base) + +class TestRun(TestCase): def test_run_custom_list(self): self.useFixture(SampleTestFixture()) tests = [] @@ -347,8 +328,6 @@ def test_run_load_list(self): ) def test_load_list_preserves_custom_suites(self): - if testresources is None: - self.skipTest("Need testresources") self.useFixture(SampleResourcedFixture()) # We load two tests, not loading one. Both share a resource, so we # should see just one resource setup occur. diff --git a/tests/test_testresult.py b/tests/test_testresult.py index df5e0f82..67f4ebc2 100644 --- a/tests/test_testresult.py +++ b/tests/test_testresult.py @@ -18,6 +18,8 @@ from typing import Any from unittest import TestSuite +import testresources + from testtools import ( CopyStreamResult, ExtendedToOriginalDecorator, @@ -84,11 +86,6 @@ run_with_stack_hidden, ) -try: - import testresources -except ImportError: - testresources = None - def _utcfromtimestamp(t): return datetime.datetime.fromtimestamp(t, tz=datetime.timezone.utc).replace( @@ -1477,11 +1474,6 @@ def _subDescription(self): class TestResourcedToStreamDecorator(TestCase): - def setUp(self): - super().setUp() - if testresources is None: - self.skipTest("Need testresources") - def test_startMakeResource(self): log = LoggingStreamResult() result = ResourcedToStreamDecorator(log) diff --git a/tests/test_testsuite.py b/tests/test_testsuite.py index b7671a1d..3f5eae09 100644 --- a/tests/test_testsuite.py +++ b/tests/test_testsuite.py @@ -6,6 +6,8 @@ import unittest from pprint import pformat +from fixtures import FunctionFixture + from testtools import ( ConcurrentStreamTestSuite, ConcurrentTestSuite, @@ -20,11 +22,6 @@ from .helpers import LoggingResult -try: - from fixtures import FunctionFixture -except ImportError: - FunctionFixture = None # type: ignore - class Sample(TestCase): def __hash__(self): @@ -348,11 +345,6 @@ def test_simple(self): class TestFixtureSuite(TestCase): - def setUp(self): - super().setUp() - if FunctionFixture is None: - self.skipTest("Need fixtures") - def test_fixture_suite(self): log: list[int | str] = []