diff mbox series

[38/41] oeqa/core/loader: Always return a flat test suite

Message ID 20261115084845.701136-38-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
OETestLoader.discover() returned a flat suite of the loaded tests only
when a discovery decorator class was registered, which happens as a side
effect of some loaded module importing oeqa.core.decorator.depends.
Otherwise it returned unittest's nested per module suites. skipTests()
expects a flat suite, so with no module importing OETestDepends, as for
the oe-core selftests on their own, --skip-tests failed with:

  AttributeError: 'TestSuite' object has no attribute 'id'

Always return a flat suite, using the registered test cases in the order
they were loaded when no discovery decorator reorders them, and add a
test which runs without OETestDepends registered.

AI-Generated: Uses Claude Opus 5.5
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
---
 meta/lib/oeqa/core/loader.py            |  4 ++--
 meta/lib/oeqa/core/tests/test_loader.py | 23 +++++++++++++++++++++++
 2 files changed, 25 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/meta/lib/oeqa/core/loader.py b/meta/lib/oeqa/core/loader.py
index d4f330dafc3..9ff91e792aa 100644
--- a/meta/lib/oeqa/core/loader.py
+++ b/meta/lib/oeqa/core/loader.py
@@ -237,7 +237,7 @@  class OETestLoader(unittest.TestLoader):
                     pattern='*.py', top_level_dir=path)
             big_suite.addTests(suite)
 
-        cases = None
+        cases = list(self.tc._registry['cases'].values())
         discover_classes = [clss for clss in decoratorClasses
                             if issubclass(clss, OETestDiscover)]
         for clss in discover_classes:
@@ -246,7 +246,7 @@  class OETestLoader(unittest.TestLoader):
         if self.modules_required:
             self._required_modules_validation()
 
-        return self.suiteClass(cases) if cases else big_suite
+        return self.suiteClass(cases)
 
     def _filterModule(self, module):
         if module.__name__ in sys.builtin_module_names:
diff --git a/meta/lib/oeqa/core/tests/test_loader.py b/meta/lib/oeqa/core/tests/test_loader.py
index cb38ac845e3..f4ab4c5b414 100755
--- a/meta/lib/oeqa/core/tests/test_loader.py
+++ b/meta/lib/oeqa/core/tests/test_loader.py
@@ -6,6 +6,9 @@ 
 #
 
 import os
+import subprocess
+import sys
+import textwrap
 import unittest
 
 from common import setup_sys_path, TestBase
@@ -59,5 +62,25 @@  class TestLoader(TestBase):
         msg = 'Expected modules from two different paths'
         self.assertEqual(modules, expected_modules, msg=msg)
 
+    def test_flat_suite(self):
+        # The loaded suite must be a flat list of tests, which skipTests()
+        # relies on, even if no module registers a discovery decorator such as
+        # OETestDepends. Registering one is global so use a new process.
+        oeqa_lib = os.path.realpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../../'))
+        cases_path = os.path.join(self.cases_path, 'loader', 'valid')
+        script = textwrap.dedent("""
+            import logging, sys, unittest
+            sys.path.insert(0, %r)
+            from oeqa.core.context import OETestContext
+            from oeqa.core.decorator import decoratorClasses
+            tc = OETestContext({}, logging.getLogger('flat'))
+            tc.loadTests(%r)
+            assert not [c for c in decoratorClasses if c.__name__ == 'OETestDepends']
+            tc.skipTests(['another.AnotherTest.testAnother'])
+            print(' '.join(type(t).__name__ for t in tc.suites))
+        """) % (oeqa_lib, cases_path)
+        output = subprocess.check_output([sys.executable, '-c', script], text=True)
+        self.assertEqual(output.split(), ['AnotherTest'])
+
 if __name__ == '__main__':
     unittest.main()