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
3 changes: 3 additions & 0 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ jobs:
- name: Lint with ruff
run: |
ruff check .
- name: Format with ruff
run: |
ruff format --check .
- name: Test with testtools
run: |
python -m testtools.run testresources.tests.test_suite
8 changes: 3 additions & 5 deletions doc/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
# of resources by test cases.
#
# Copyright (c) 2005-2010 Testresources Contributors
#
#
# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
# license at the users choice. A copy of both licenses are available in the
# project source as Apache-2.0 and BSD. You may not use this file except in
# compliance with one of these two licences.
#
#
# Unless required by applicable law or agreed to in writing, software distributed
# under these licenses is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the license you chose
Expand All @@ -21,7 +21,6 @@


class SampleTestResource(TestResourceManager):

setUpCost = 2
tearDownCost = 2

Expand All @@ -34,8 +33,7 @@ class MyResource(object):


class SampleWithDependencies(TestResourceManager):

resources = [('foo', SampleTestResource()), ('bar', SampleTestResource())]
resources = [("foo", SampleTestResource()), ("bar", SampleTestResource())]

def make(self, dependency_resources):
# dependency_resources will be {'foo': result_of_make_in_foo, 'bar':
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python
#!/usr/bin/env python3

import setuptools

setuptools.setup(setup_requires=['pbr>=1.3'], pbr=True)
setuptools.setup(setup_requires=["pbr>=1.3"], pbr=True)
86 changes: 46 additions & 40 deletions testresources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import heapq
import inspect
import unittest

try:
from collections.abc import MutableSet
except ImportError:
Expand All @@ -42,13 +43,15 @@
# Otherwise it is major.minor.micro~$(revno).

from pbr.version import VersionInfo
_version = VersionInfo('testresources')

_version = VersionInfo("testresources")
__version__ = _version.semantic_version().version_tuple()
version = _version.release_string()


def test_suite():
import testresources.tests

return testresources.tests.test_suite()


Expand Down Expand Up @@ -111,7 +114,7 @@ def _kruskals_graph_MST(graph):
# combine g1 and g2 into g1
graphs -= 1
for from_node, to_nodes in g2.items():
#remember its symmetric, don't need to do 'to'.
# remember its symmetric, don't need to do 'to'.
forest[from_node] = g1
g1.setdefault(from_node, {}).update(to_nodes)
# add edge
Expand Down Expand Up @@ -161,8 +164,7 @@ def split_by_resources(tests):
resource_set_tests = {no_resources: []}
for test in tests:
resources = getattr(test, "resources", ())
all_resources = list(resource.neededResources()
for _, resource in resources)
all_resources = list(resource.neededResources() for _, resource in resources)
resource_set = set()
for resource_list in all_resources:
resource_set.update(resource_list)
Expand Down Expand Up @@ -206,8 +208,8 @@ class _OrderedSet(MutableSet):

def __init__(self, iterable=None):
self.end = end = []
end += [None, end, end] # sentinel node for doubly linked list
self.map = {} # key --> [key, prev, next]
end += [None, end, end] # sentinel node for doubly linked list
self.map = {} # key --> [key, prev, next]
if iterable is not None:
self |= iterable

Expand Down Expand Up @@ -274,8 +276,7 @@ def addTest(self, test_case_or_suite):
self.adsorbSuite(test)
else:
for test in tests:
unittest.TestSuite.addTest(
self, test_case_or_suite.__class__([test]))
unittest.TestSuite.addTest(self, test_case_or_suite.__class__([test]))

def cost_of_switching(self, old_resource_set, new_resource_set):
"""Cost of switching from 'old_resource_set' to 'new_resource_set'.
Expand All @@ -290,8 +291,9 @@ def cost_of_switching(self, old_resource_set, new_resource_set):
"""
new_resources = new_resource_set - old_resource_set
gone_resources = old_resource_set - new_resource_set
return (sum(resource.setUpCost for resource in new_resources) +
sum(resource.tearDownCost for resource in gone_resources))
return sum(resource.setUpCost for resource in new_resources) + sum(
resource.tearDownCost for resource in gone_resources
)

def switch(self, old_resource_set, new_resource_set, result):
"""Switch from 'old_resource_set' to 'new_resource_set'.
Expand Down Expand Up @@ -321,7 +323,7 @@ def run(self, result):
for test in self._tests:
if result.shouldStop:
break
resources = getattr(test, 'resources', [])
resources = getattr(test, "resources", [])
new_resources = _OrderedSet()
for name, resource in resources:
new_resources.update(resource.neededResources())
Expand Down Expand Up @@ -357,8 +359,7 @@ def sortTests(self):
resource_set_graph = _resource_graph(resource_set_tests)
no_resources = frozenset()
# A list of resource_set_tests, all fully internally connected.
partitions = _strongly_connected_components(resource_set_graph,
no_resources)
partitions = _strongly_connected_components(resource_set_graph, no_resources)
result = []
for partition in partitions:
# we process these at the end for no particularly good reason (it
Expand All @@ -384,30 +385,31 @@ def _getGraph(self, resource_sets):
"""
no_resources = frozenset()
graph = {}
root = set(['root'])
root = set(["root"])
# bottom = set(['bottom'])
for from_set in resource_sets:
graph[from_set] = {}
if from_set == root:
from_resources = no_resources
#elif from_set == bottom:
# elif from_set == bottom:
# continue # no links from bottom
else:
from_resources = from_set
for to_set in resource_sets:
if from_set is to_set:
continue # no self-edges
#if to_set == bottom:
# if to_set == bottom:
# if from_set == root:
# continue # no short cuts!
# to_resources = no_resources
#el
# el
if to_set == root:
continue # no links to root
else:
to_resources = to_set
graph[from_set][to_set] = self.cost_of_switching(
from_resources, to_resources)
from_resources, to_resources
)
return graph

def _makeOrder(self, partition):
Expand All @@ -419,7 +421,7 @@ def _makeOrder(self, partition):
# http://en.wikipedia.org/wiki/Travelling_salesman_problem#Metric_TSP

# We need a root
root = frozenset(['root'])
root = frozenset(["root"])
partition.add(root)
# and an end
# partition.add(frozenset(['bottom']))
Expand All @@ -428,7 +430,7 @@ def _makeOrder(self, partition):
digraph = self._getGraph(partition)
# build a prime map
primes = {}
prime = frozenset(['prime'])
prime = frozenset(["prime"])
for node in digraph:
primes[node] = node.union(prime)
graph = _digraph_to_graph(digraph, primes)
Expand Down Expand Up @@ -480,14 +482,14 @@ def _makeOrder(self, partition):
return order[1:]


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


class TestLoader(unittest.TestLoader):
"""Custom TestLoader to set the right TestSuite class."""

suiteClass = OptimisingTestSuite


Expand Down Expand Up @@ -624,8 +626,7 @@ def make(self, dependency_resources):
for the resources specified as dependencies.
:return: The made resource.
"""
raise NotImplementedError(
"Override make to construct resources.")
raise NotImplementedError("Override make to construct resources.")

def neededResources(self):
"""Return the resources needed for this resource, including self.
Expand All @@ -647,7 +648,7 @@ def reset(self, old_resource, result=None):
is part of the public interface, but _make_all and _clean_all is not.

Note that if a resource A holds a lock or other blocking thing on
a dependency D, reset will result in this call sequence over a
a dependency D, reset will result in this call sequence over a
getResource(), dirty(), getResource(), finishedWith(), finishedWith()
sequence:
B.make(), A.make(), B.reset(), A.reset(), A.clean(), B.clean()
Expand Down Expand Up @@ -679,8 +680,7 @@ def reset(self, old_resource, result=None):
self._call_result_method_if_exists(result, "startResetResource", self)
dependency_resources = {}
for name, mgr in self.resources:
dependency_resources[name] = mgr.reset(
getattr(old_resource, name), result)
dependency_resources[name] = mgr.reset(getattr(old_resource, name), result)
resource = self._reset(old_resource, dependency_resources)
for name, value in dependency_resources.items():
setattr(resource, name, value)
Expand Down Expand Up @@ -723,6 +723,8 @@ def _setResource(self, new_resource):
"""Set the current resource to a new value."""
self._currentResource = new_resource
self._dirty = False


TestResource = TestResourceManager


Expand All @@ -741,9 +743,13 @@ class GenericResource(TestResourceManager):
method.
"""

def __init__(self, resource_factory, setup_method_name='setUp',
teardown_method_name='tearDown',
id_attribute_name="__name__"):
def __init__(
self,
resource_factory,
setup_method_name="setUp",
teardown_method_name="tearDown",
id_attribute_name="__name__",
):
"""Create a GenericResource

:param resource_factory: A factory to create a new resource.
Expand Down Expand Up @@ -779,7 +785,8 @@ def id(self):
"""
return "%s[%s]" % (
super(GenericResource, self).id(),
getattr(self.resource_factory, self.id_attribute_name))
getattr(self.resource_factory, self.id_attribute_name),
)


class FixtureResource(TestResourceManager):
Expand Down Expand Up @@ -823,8 +830,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 "%s[%s]" % (super(FixtureResource, self).id(), str(self.fixture))

def _reset(self, resource, dependency_resources):
self.fixture.reset()
Expand All @@ -833,7 +839,7 @@ def _reset(self, resource, dependency_resources):
def isDirty(self):
return True

_dirty = property(lambda _:True, lambda _, _1:None)
_dirty = property(lambda _: True, lambda _, _1: None)


class ResourcedTestCase(unittest.TestCase):
Expand Down Expand Up @@ -901,8 +907,9 @@ def neededResources(resources):
result = []

for resource in resources:
dependencies = neededResources([
dependency for name, dependency in resource.resources])
dependencies = neededResources(
[dependency for name, dependency in resource.resources]
)
for resource in dependencies + [resource]:
if resource in seen:
continue
Expand All @@ -924,10 +931,9 @@ def _get_result():
"""
stack = inspect.stack()
for frame in stack[2:]:
if frame[3] in ('run', '__call__'):
if frame[3] in ("run", "__call__"):
# Not all frames called 'run' will be unittest. It could be a
# reactor in trial, for instance.
result = frame[0].f_locals.get('result')
if (result is not None and
getattr(result, 'startTest', None) is not None):
result = frame[0].f_locals.get("result")
if result is not None and getattr(result, "startTest", None) is not None:
return result
18 changes: 13 additions & 5 deletions testresources/tests/TestUtil.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,17 @@
class LogCollector(logging.Handler):
def __init__(self):
logging.Handler.__init__(self)
self.records=[]
self.records = []

def emit(self, record):
self.records.append(record.getMessage())


def makeCollectingLogger():
"""I make a logger instance that collects its logs for programmatic analysis
-> (logger, collector)"""
logger=logging.Logger("collector")
handler=LogCollector()
logger = logging.Logger("collector")
handler = LogCollector()
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(handler)
return logger, handler
Expand All @@ -44,7 +45,7 @@ def visitTests(suite, visitor):
visitor.visitCase(suite)
return
for test in suite._tests:
#Abusing types to avoid monkey patching unittest.TestCase.
# Abusing types to avoid monkey patching unittest.TestCase.
# Maybe that would be better?
try:
test.visit(visitor)
Expand All @@ -55,7 +56,10 @@ def visitTests(suite, visitor):
visitor.visitSuite(test)
visitTests(test, visitor)
else:
print("unvisitable non-unittest.TestCase element %r (%r)" % (test, test.__class__))
print(
"unvisitable non-unittest.TestCase element %r (%r)"
% (test, test.__class__)
)


class TestSuite(unittest.TestSuite):
Expand All @@ -72,11 +76,15 @@ def visit(self, visitor):

class TestLoader(unittest.TestLoader):
"""Custome TestLoader to set the right TestSuite class."""

suiteClass = TestSuite


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

def visitSuite(self, aTestSuite):
pass

def visitCase(self, aTestCase):
pass
Loading