diff mbox series

[37/41] oeqa/core: Use unittest's setup hook instead of patching test classes

Message ID 20261115084845.701136-37-richard.purdie@linuxfoundation.org
State New
Headers show
Series [01/41] oeqa/loader: Drop code for pre python 3.5 | expand

Commit Message

Richard Purdie Nov. 15, 2026, 8:48 a.m. UTC
To run the decorators and the td_vars check, OETestLoader renamed the
setUp()/tearDown()/setUpClass()/tearDownClass() methods of every test
class it loaded and replaced them with OETestCase wrappers, marking the
class as patched. Since the marker was inherited, a subclass which
overrode setUp() and was loaded after its parent class was left
unpatched and silently lost its decorators.

Override TestCase._callSetUp(), which unittest uses to run setUp(), to
check td_vars and set up the decorators, and register the decorator
teardowns as cleanups so they run after tearDown() and when setUp()
fails. The td_vars check now runs per test rather than in setUpClass(),
none of the classes using td_vars need the variables in setUpClass().

The class level hook was otherwise only used to skip classes given on
the command line, so use unittest.skip() for those and for skipped
tests. Tests in a skipped class are now each reported as skipped rather
than as a single class level skip.

This does mean we use python internal API but we can adapt if python
changes that.

AI-Generated: Uses Claude Opus 5.5
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
---
 meta/lib/oeqa/core/case.py    | 37 +++++++++++------------------------
 meta/lib/oeqa/core/context.py | 25 +++++++++--------------
 meta/lib/oeqa/core/loader.py  | 30 +---------------------------
 3 files changed, 21 insertions(+), 71 deletions(-)
diff mbox series

Patch

diff --git a/meta/lib/oeqa/core/case.py b/meta/lib/oeqa/core/case.py
index e71cab5028c..86944cddde0 100644
--- a/meta/lib/oeqa/core/case.py
+++ b/meta/lib/oeqa/core/case.py
@@ -32,33 +32,18 @@  class OETestCase(unittest.TestCase):
     # OEQAMissingVariable exception is raised
     td_vars = None
 
-    @classmethod
-    def _oeSetUpClass(clss):
-        _validate_td_vars(clss.td, clss.td_vars, "class")
-        if hasattr(clss, 'setUpHooker') and callable(getattr(clss, 'setUpHooker')):
-            clss.setUpHooker()
-        clss.setUpClassMethod()
-
-    @classmethod
-    def _oeTearDownClass(clss):
-        clss.tearDownClassMethod()
-
-    def _oeSetUp(self):
-        # unittest doesn't call tearDown() if setUp() fails so the decorators
-        # need tearing down here in that case too
-        try:
-            for d in self.decorators:
-                d.setUpDecorator()
-            self.setUpMethod()
-        except:
-            for d in self.decorators:
-                d.tearDownDecorator()
-            raise
-
-    def _oeTearDown(self):
+    # Decorators bound to this test case by OETestLoader
+    decorators = ()
+
+    def _callSetUp(self):
+        # unittest.TestCase.run() calls this to run setUp(). Check the test
+        # data and set up the decorators first. Decorators are torn down as
+        # cleanups, which run after tearDown() and also if setUp() fails.
+        _validate_td_vars(self.td, self.td_vars, "class")
         for d in self.decorators:
-            d.tearDownDecorator()
-        self.tearDownMethod()
+            d.setUpDecorator()
+            self.addCleanup(d.tearDownDecorator)
+        super()._callSetUp()
 
     def assertFileExists(self, filename, msg=None):
         """
diff --git a/meta/lib/oeqa/core/context.py b/meta/lib/oeqa/core/context.py
index 3bd65ba35f6..601a8014d9b 100644
--- a/meta/lib/oeqa/core/context.py
+++ b/meta/lib/oeqa/core/context.py
@@ -47,24 +47,17 @@  class OETestContext:
         return modules
 
     def skipTests(self, skips):
-        if not skips:
-            return
-        def skipfuncgen(skipmsg):
-            def func():
-                raise unittest.SkipTest(skipmsg)
-            return func
-        class_ids = {}
+        # Use unittest's skip support so that skipped tests, and the class
+        # setup of skipped classes, don't run
         for test in self.suites:
-            if test.__class__ not in class_ids:
-                class_ids[test.__class__] = '.'.join(test.id().split('.')[:-1])
+            class_id = test.id().rsplit('.', 1)[0]
             for skip in skips:
-                if (test.id()+'.').startswith(skip+'.'):
-                    setattr(test, 'setUp', skipfuncgen('Skip by the command line argument "%s"' % skip))
-        for tclass in class_ids:
-            cid = class_ids[tclass]
-            for skip in skips:
-                if (cid + '.').startswith(skip + '.'):
-                    setattr(tclass, 'setUpHooker', skipfuncgen('Skip by the command line argument "%s"' % skip))
+                msg = 'Skip by the command line argument "%s"' % skip
+                if (class_id + '.').startswith(skip + '.'):
+                    unittest.skip(msg)(test.__class__)
+                elif (test.id() + '.').startswith(skip + '.'):
+                    method = getattr(test, test._testMethodName)
+                    setattr(test, test._testMethodName, unittest.skip(msg)(method))
 
     def loadTests(self, module_paths, modules=[], tests=[],
             modules_manifest="", modules_required=[], **kwargs):
diff --git a/meta/lib/oeqa/core/loader.py b/meta/lib/oeqa/core/loader.py
index 94ade54506d..d4f330dafc3 100644
--- a/meta/lib/oeqa/core/loader.py
+++ b/meta/lib/oeqa/core/loader.py
@@ -171,37 +171,9 @@  class OETestLoader(unittest.TestLoader):
         return False
 
     def _getTestCase(self, testCaseClass, tcName):
-        if not hasattr(testCaseClass, '__oeqa_loader') and \
-                issubclass(testCaseClass, OETestCase):
-            # In order to support data_vars validation
-            # monkey patch the default setUp/tearDown{Class} to use
-            # the ones provided by OETestCase
-            setattr(testCaseClass, 'setUpClassMethod',
-                    getattr(testCaseClass, 'setUpClass'))
-            setattr(testCaseClass, 'tearDownClassMethod',
-                    getattr(testCaseClass, 'tearDownClass'))
-            setattr(testCaseClass, 'setUpClass',
-                    testCaseClass._oeSetUpClass)
-            setattr(testCaseClass, 'tearDownClass',
-                    testCaseClass._oeTearDownClass)
-
-            # In order to support decorators initialization
-            # monkey patch the default setUp/tearDown to use
-            # a setUpDecorators/tearDownDecorators that methods
-            # will call setUp/tearDown original methods.
-            setattr(testCaseClass, 'setUpMethod',
-                    getattr(testCaseClass, 'setUp')) 
-            setattr(testCaseClass, 'tearDownMethod',
-                    getattr(testCaseClass, 'tearDown'))
-            setattr(testCaseClass, 'setUp', testCaseClass._oeSetUp)
-            setattr(testCaseClass, 'tearDown', testCaseClass._oeTearDown)
-
-            setattr(testCaseClass, '__oeqa_loader', True)
-
         case = testCaseClass(tcName)
         if isinstance(case, OETestCase):
-            setattr(case, 'decorators', [])
-
+            case.decorators = []
         return case
 
     def loadTestsFromTestCase(self, testCaseClass):