@@ -4,7 +4,6 @@
# SPDX-License-Identifier: MIT
#
-from functools import wraps
from abc import ABCMeta
decoratorClasses = set()
@@ -29,11 +28,10 @@ class OETestDecorator(object, metaclass=ABCMeta):
setattr(self, attr, value)
def __call__(self, func):
- @wraps(func)
- def wrapped_f(*args, **kwargs):
- self.attrs = self.attrs # XXX: Enables OETestLoader discover
- return func(*args, **kwargs)
- return wrapped_f
+ # Record the decorator on the test function for OETestLoader to find.
+ # They are set up in the order they are applied, innermost first.
+ func._oeqa_decorators = getattr(func, '_oeqa_decorators', []) + [self]
+ return func
# OETestLoader call it when is loading test cases.
# XXX: Most methods would change the registry for later
@@ -14,8 +14,7 @@ from oeqa.core.utils.test import getSuiteModules, getCaseID
from oeqa.core.exception import OEQATestNotFound
from oeqa.core.case import OETestCase
-from oeqa.core.decorator import decoratorClasses, OETestDecorator, \
- OETestDiscover
+from oeqa.core.decorator import decoratorClasses, OETestDiscover
# When loading tests, the unittest framework stores any exceptions and
# displays them only when the run method is called.
@@ -103,23 +102,14 @@ class OETestLoader(unittest.TestLoader):
self.tc._registry['cases'][case_id] = case
def _handleTestCaseDecorators(self, case):
- def _handle(obj):
- if isinstance(obj, OETestDecorator):
- if not obj.__class__ in decoratorClasses:
- raise Exception("Decorator %s isn't registered" \
- " in decoratorClasses." % obj.__class__.__name__)
- # The decorator instance is shared by every class that has
- # the method so bind a copy for this test case
- copy.copy(obj).bind(self.tc._registry, case)
-
- def _walk_closure(obj):
- if hasattr(obj, '__closure__') and obj.__closure__:
- for f in obj.__closure__:
- obj = f.cell_contents
- _handle(obj)
- _walk_closure(obj)
method = getattr(case, case._testMethodName, None)
- _walk_closure(method)
+ for obj in getattr(method, '_oeqa_decorators', []):
+ if not obj.__class__ in decoratorClasses:
+ raise Exception("Decorator %s isn't registered" \
+ " in decoratorClasses." % obj.__class__.__name__)
+ # The decorator instance is shared by every class that has
+ # the method so bind a copy for this test case
+ copy.copy(obj).bind(self.tc._registry, case)
def _filterTest(self, case):
"""
@@ -126,6 +126,14 @@ class TestDependsDecorator(TestBase):
self.assertEqual(result.skipped, [])
self.assertEqual(len(result.successes), len(tests))
+class TestDecoratorOrder(TestBase):
+ def test_decorator_order(self):
+ # Decorators are set up in the order they are applied, innermost first
+ tc = self._testLoader(modules=['timeout'])
+ case = tc._registry['cases']['timeout.TimeoutTest.testTimeoutDepends']
+ self.assertEqual([d.__class__.__name__ for d in case.decorators],
+ ['OETimeout', 'OETestDepends'])
+
class TestInheritedDecorators(TestBase):
modules = ['inherit']
OETestDecorator wrapped each test method in a function which only called it, with a dummy assignment to self so the decorator would be captured in the wrapper's closure. OETestLoader then walked the closures to find the decorators. Instead, record the decorators in an attribute on the test function and return it unchanged. The order they are set up in is the same as before, innermost first, and a test now checks this. AI-Generated: Uses Claude Opus 5.5 Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org> --- meta/lib/oeqa/core/decorator/__init__.py | 10 ++++---- meta/lib/oeqa/core/loader.py | 26 +++++++-------------- meta/lib/oeqa/core/tests/test_decorators.py | 8 +++++++ 3 files changed, 20 insertions(+), 24 deletions(-)