Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
---
name: Python package

on:
push:
pull_request:

jobs:
build:

runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]

python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install ruff testtools pbr
python -m pip install ruff hatchling hatch-vcs
python -m pip install ".[test]"
- name: Lint with ruff
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
TAGS
tags
lib/pyunit3k
lib/testtools
__pycache__
./dist
Expand All @@ -14,3 +13,4 @@ dist/
testresources.egg-info/
.tox
*.pyc
testresources/_version.py
2 changes: 1 addition & 1 deletion doc/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def make(self, dependency_resources):
return "You need to implement your own getResource."


class MyResource(object):
class MyResource:
"""My pet resource."""


Expand Down
83 changes: 83 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[project]
name = "testresources"
description = "Testresources, a pyunit extension for managing expensive test resources"
readme = "README.rst"
classifiers = [
"Development Status :: 6 - Mature",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Quality Assurance",
"Topic :: Software Development :: Testing",
]
authors = [{name = "Robert Collins", email = "robertc@robertcollins.net"}]
license = {text = "Apache-2.0 or BSD"}
requires-python = ">=3.10"
dynamic = ["version"]

[project.urls]
Homepage = "https://github.com/testing-cabal/testresources"
"Bug Tracker" = "https://github.com/testing-cabal/testresources/issues"
"Source Code" = "https://github.com/testing-cabal/testresources"

[project.optional-dependencies]
test = [
"docutils",
"fixtures",
"testtools",
]

[tool.hatch.version]
source = "vcs"

[tool.hatch.build.hooks.vcs]
version-file = "testresources/_version.py"
template = """\
# This file is automatically generated by hatch.
version = {version!r}
__version__ = {version_tuple!r}
"""

[tool.hatch.build.targets.sdist]
include = [
"testresources*",
"doc*",
"Apache-2.0",
"BSD",
"COPYING",
"GOALS",
"Makefile",
"NEWS",
"TODO",
"tox.ini",
]

[tool.hatch.build.targets.wheel]
packages = ["testresources"]

[tool.ruff]
exclude = ["testresources/_version.py"]

[tool.ruff.lint]
select = ["E", "F", "I", "PIE", "UP", "RSE", "RUF"]
ignore = [
# we make use of resources as a class var to initialize things
"RUF012"
]

[tool.ruff.lint.per-file-ignores]
"testresources/tests/*" = ["S"]
1 change: 0 additions & 1 deletion requirements.txt

This file was deleted.

31 changes: 0 additions & 31 deletions setup.cfg

This file was deleted.

5 changes: 0 additions & 5 deletions setup.py

This file was deleted.

86 changes: 60 additions & 26 deletions testresources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,7 @@
import heapq
import inspect
import unittest

try:
from collections.abc import MutableSet
except ImportError:
from collections import MutableSet
try:
import unittest2
except ImportError:
unittest2 = None
from collections.abc import MutableSet

# same format as sys.version_info: "A tuple containing the five components of
# the version number: major, minor, micro, releaselevel, and serial. All
Expand All @@ -42,11 +34,56 @@
# If the releaselevel is 'final', then the tarball will be major.minor.micro.
# Otherwise it is major.minor.micro~$(revno).

from pbr.version import VersionInfo

_version = VersionInfo("testresources")
__version__ = _version.semantic_version().version_tuple()
version = _version.release_string()
def __get_git_version() -> str | None:
import os
import subprocess

cwd = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)

try:
out = subprocess.check_output(
["git", "describe"], stderr=subprocess.STDOUT, cwd=cwd
)
except (OSError, subprocess.CalledProcessError):
return None

try:
version = out.strip().decode("utf-8")
except UnicodeDecodeError:
return None

if "-" in version: # after tag
# convert version-N-githash to version.postN+githash
return version.replace("-", ".post", 1).replace("-g", "+git", 1)
else:
return version


# same format as sys.version_info: "A tuple containing the five components of
# the version number: major, minor, micro, releaselevel, and serial. All
# values except releaselevel are integers; the release level is 'alpha',
# 'beta', 'candidate', or 'final'. The version_info value corresponding to the
# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a
# releaselevel of 'dev' for unreleased under-development code.
#
# If the releaselevel is 'alpha' then the major/minor/micro components are not
# established at this point, and setup.py will use a version of next-$(revno).
# If the releaselevel is 'final', then the tarball will be major.minor.micro.
# Otherwise it is major.minor.micro~$(revno).

try:
from ._version import __version__, version
except ModuleNotFoundError:
# package is not installed
if v := __get_git_version():
version = v
# we're in a git repo
__version__ = tuple([int(x) if x.isdigit() else x for x in version.split(".")])
else:
# we're working with a tarball or similar
version = "0.0.0"
__version__ = (0, 0, 0)


def test_suite():
Expand Down Expand Up @@ -100,7 +137,7 @@ def _kruskals_graph_MST(graph):
edges = set()
for from_node, to_nodes in graph.items():
for to_node, value in to_nodes.items():
edge = (value,) + tuple(sorted([from_node, to_node]))
edge = (value, *tuple(sorted([from_node, to_node])))
edges.add(edge)
edges = list(edges)
heapq.heapify(edges)
Expand Down Expand Up @@ -483,8 +520,6 @@ def _makeOrder(self, partition):


OptimisingTestSuite.known_suite_classes = (unittest.TestSuite, OptimisingTestSuite)
if unittest2 is not None:
OptimisingTestSuite.known_suite_classes += (unittest2.TestSuite,)


class TestLoader(unittest.TestLoader):
Expand All @@ -493,7 +528,7 @@ class TestLoader(unittest.TestLoader):
suiteClass = OptimisingTestSuite


class TestResourceManager(object):
class TestResourceManager:
"""A manager for resources that can be shared across tests.

ResourceManagers can report activity to a TestResult. The methods
Expand Down Expand Up @@ -761,7 +796,7 @@ def __init__(
be embedded in the string returned by the id() method, to identify
the generic resource. Defaults to '__name__'.
"""
super(GenericResource, self).__init__()
super().__init__()
self.resource_factory = resource_factory
self.setup_method_name = setup_method_name
self.teardown_method_name = teardown_method_name
Expand All @@ -783,9 +818,8 @@ def id(self):

:see: The `id_attribute_name` parameter.
"""
return "%s[%s]" % (
super(GenericResource, self).id(),
getattr(self.resource_factory, self.id_attribute_name),
return (
f"{super().id()}[{getattr(self.resource_factory, self.id_attribute_name)}]"
)


Expand Down Expand Up @@ -815,7 +849,7 @@ def __init__(self, fixture):

:param fixture: The fixture to wrap.
"""
super(FixtureResource, self).__init__()
super().__init__()
self.fixture = fixture

def clean(self, resource):
Expand All @@ -830,7 +864,7 @@ def id(self):

The default is to call str(fixture) to get such information.
"""
return "%s[%s]" % (super(FixtureResource, self).id(), str(self.fixture))
return f"{super().id()}[{self.fixture!s}]"

def _reset(self, resource, dependency_resources):
self.fixture.reset()
Expand Down Expand Up @@ -860,15 +894,15 @@ class is neded, or you can use multiple inheritance and call into
resources = []

def setUp(self):
super(ResourcedTestCase, self).setUp()
super().setUp()
self.setUpResources()

def setUpResources(self):
setUpResources(self, self.resources, _get_result())

def tearDown(self):
self.tearDownResources()
super(ResourcedTestCase, self).tearDown()
super().tearDown()

def tearDownResources(self):
tearDownResources(self, self.resources, _get_result())
Expand Down Expand Up @@ -910,7 +944,7 @@ def neededResources(resources):
dependencies = neededResources(
[dependency for name, dependency in resource.resources]
)
for resource in dependencies + [resource]:
for resource in [*dependencies, resource]:
if resource in seen:
continue
seen.add(resource)
Expand Down
5 changes: 5 additions & 0 deletions testresources/_version.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Type stub for auto-generated _version module
from typing import Union

__version__: tuple[Union[int, str], ...] # noqa: UP007
version: str
6 changes: 3 additions & 3 deletions testresources/tests/TestUtil.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ def visitTests(suite, visitor):
visitTests(test, visitor)
else:
print(
"unvisitable non-unittest.TestCase element %r (%r)"
% (test, test.__class__)
f"unvisitable non-unittest.TestCase element {test!r} "
f"({test.__class__!r})"
)


Expand All @@ -80,7 +80,7 @@ class TestLoader(unittest.TestLoader):
suiteClass = TestSuite


class TestVisitor(object):
class TestVisitor:
"""A visitor for Tests"""

def visitSuite(self, aTestSuite):
Expand Down
4 changes: 2 additions & 2 deletions testresources/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@

def test_suite():
import testresources.tests.test_optimising_test_suite
import testresources.tests.test_resource_graph
import testresources.tests.test_resourced_test_case
import testresources.tests.test_test_loader
import testresources.tests.test_test_resource
import testresources.tests.test_resource_graph

result = TestUtil.TestSuite()
result.addTest(testresources.tests.test_test_loader.test_suite())
Expand All @@ -36,7 +36,7 @@ def test_suite():
return result


class ResultWithoutResourceExtensions(object):
class ResultWithoutResourceExtensions:
"""A test fake which does not have resource extensions."""


Expand Down
Loading