oeqa/core: Rework OETestTag and remove unused OETestFilter

Rework OETestTag so that it does not rely on the existing decorator code
base and instead inserts the tags into an attribute on the decorated
target (e.g. class/type or method). This allows the use of OETestTag on
classes and method.

In order to filter tagged tests rework the loaders filtering code,
removing the generic-ness (with validation and attributes/etc.) and
replace it with a "tags_filter" parameter which is a function that
filters a test based on the tags it has. This allows the loader user to
filter on tags in more specific ways (e.g. include all untagged tests
and any tests tagged with foo). Plumb all this through the context code
and testing code.

Update the associated tests to pass correctly with the changes.

(From OE-Core rev: b8a4a4c2de68110d74607cb9807c9e741ca9441c)

Signed-off-by: Nathan Rossi <nathan@nathanrossi.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Nathan Rossi
2019-09-03 16:56:41 +00:00
committed by Richard Purdie
parent 1220faf665
commit c3625e141d
9 changed files with 98 additions and 133 deletions

View File

@@ -64,12 +64,12 @@ class OETestContext(object):
setattr(tclass, 'setUpHooker', skipfuncgen('Skip by the command line argument "%s"' % skip))
def loadTests(self, module_paths, modules=[], tests=[],
modules_manifest="", modules_required=[], filters={}):
modules_manifest="", modules_required=[], **kwargs):
if modules_manifest:
modules = self._read_modules_from_manifest(modules_manifest)
self.loader = self.loaderClass(self, module_paths, modules, tests,
modules_required, filters)
modules_required, **kwargs)
self.suites = self.loader.discover()
def runTests(self, processes=None, skips=[]):

View File

@@ -6,6 +6,7 @@
from functools import wraps
from abc import abstractmethod, ABCMeta
from oeqa.core.utils.misc import strToList
decoratorClasses = set()
@@ -63,12 +64,15 @@ class OETestDiscover(OETestDecorator):
def discover(registry):
return registry['cases']
class OETestFilter(OETestDecorator):
def OETestTag(*tags):
expandedtags = []
for tag in tags:
expandedtags += strToList(tag)
def decorator(item):
if hasattr(item, "__oeqa_testtags"):
item.__oeqa_testtags += expandedtags
else:
item.__oeqa_testtags = expandedtags
return item
return decorator
# OETestLoader call it while loading the tests
# in loadTestsFromTestCase method, it needs to
# return a bool, True if needs to be filtered.
# This method must consume the filter used.
@abstractmethod
def filtrate(self, filters):
return False

View File

@@ -1,27 +0,0 @@
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: MIT
#
from . import OETestFilter, registerDecorator
from oeqa.core.utils.misc import strToList
def _tagFilter(tags, filters):
return False if set(tags) & set(filters) else True
@registerDecorator
class OETestTag(OETestFilter):
attrs = ('oetag',)
def bind(self, registry, case):
super(OETestTag, self).bind(registry, case)
self.oetag = strToList(self.oetag, 'oetag')
def filtrate(self, filters):
if filters.get('oetag'):
filterx = strToList(filters['oetag'], 'oetag')
del filters['oetag']
if _tagFilter(self.oetag, filterx):
return True
return False

View File

@@ -16,7 +16,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, \
OETestFilter, OETestDiscover
OETestDiscover
# When loading tests, the unittest framework stores any exceptions and
# displays them only when the run method is called.
@@ -68,7 +68,7 @@ class OETestLoader(unittest.TestLoader):
'_top_level_dir']
def __init__(self, tc, module_paths, modules, tests, modules_required,
filters, *args, **kwargs):
*args, **kwargs):
self.tc = tc
self.modules = _built_modules_dict(modules)
@@ -76,13 +76,7 @@ class OETestLoader(unittest.TestLoader):
self.tests = tests
self.modules_required = modules_required
self.filters = filters
self.decorator_filters = [d for d in decoratorClasses if \
issubclass(d, OETestFilter)]
self._validateFilters(self.filters, self.decorator_filters)
self.used_filters = [d for d in self.decorator_filters
for f in self.filters
if f in d.attrs]
self.tags_filter = kwargs.get("tags_filter", None)
if isinstance(module_paths, str):
module_paths = [module_paths]
@@ -104,28 +98,6 @@ class OETestLoader(unittest.TestLoader):
setattr(testCaseClass, 'td', self.tc.td)
setattr(testCaseClass, 'logger', self.tc.logger)
def _validateFilters(self, filters, decorator_filters):
# Validate if filter isn't empty
for key,value in filters.items():
if not value:
raise TypeError("Filter %s specified is empty" % key)
# Validate unique attributes
attr_filters = [attr for clss in decorator_filters \
for attr in clss.attrs]
dup_attr = [attr for attr in attr_filters
if attr_filters.count(attr) > 1]
if dup_attr:
raise TypeError('Detected duplicated attribute(s) %s in filter'
' decorators' % ' ,'.join(dup_attr))
# Validate if filter is supported
for f in filters:
if f not in attr_filters:
classes = ', '.join([d.__name__ for d in decorator_filters])
raise TypeError('Found "%s" filter but not declared in any of '
'%s decorators' % (f, classes))
def _registerTestCase(self, case):
case_id = case.id()
self.tc._registry['cases'][case_id] = case
@@ -188,19 +160,20 @@ class OETestLoader(unittest.TestLoader):
return True
# Decorator filters
if self.filters and isinstance(case, OETestCase):
filters = self.filters.copy()
case_decorators = [cd for cd in case.decorators
if cd.__class__ in self.used_filters]
if self.tags_filter is not None and callable(self.tags_filter):
alltags = set()
# pull tags from the case class
if hasattr(case, "__oeqa_testtags"):
for t in getattr(case, "__oeqa_testtags"):
alltags.add(t)
# pull tags from the method itself
if hasattr(case, test_name):
method = getattr(case, test_name)
if hasattr(method, "__oeqa_testtags"):
for t in getattr(method, "__oeqa_testtags"):
alltags.add(t)
# Iterate over case decorators to check if needs to be filtered.
for cd in case_decorators:
if cd.filtrate(filters):
return True
# Case is missing one or more decorators for all the filters
# being used, so filter test case.
if filters:
if self.tags_filter(alltags):
return True
return False

View File

@@ -5,7 +5,7 @@
#
from oeqa.core.case import OETestCase
from oeqa.core.decorator.oetag import OETestTag
from oeqa.core.decorator import OETestTag
from oeqa.core.decorator.data import OETestDataDepends
class DataTest(OETestCase):

View File

@@ -5,10 +5,9 @@
#
from oeqa.core.case import OETestCase
from oeqa.core.decorator.oetag import OETestTag
from oeqa.core.decorator import OETestTag
class TagTest(OETestCase):
@OETestTag('goodTag')
def testTagGood(self):
self.assertTrue(True, msg='How is this possible?')
@@ -17,5 +16,23 @@ class TagTest(OETestCase):
def testTagOther(self):
self.assertTrue(True, msg='How is this possible?')
@OETestTag('otherTag', 'multiTag')
def testTagOtherMulti(self):
self.assertTrue(True, msg='How is this possible?')
def testTagNone(self):
self.assertTrue(True, msg='How is this possible?')
@OETestTag('classTag')
class TagClassTest(OETestCase):
@OETestTag('otherTag')
def testTagOther(self):
self.assertTrue(True, msg='How is this possible?')
@OETestTag('otherTag', 'multiTag')
def testTagOtherMulti(self):
self.assertTrue(True, msg='How is this possible?')
def testTagNone(self):
self.assertTrue(True, msg='How is this possible?')

View File

@@ -30,9 +30,9 @@ class TestBase(unittest.TestCase):
directory = os.path.dirname(os.path.abspath(__file__))
self.cases_path = os.path.join(directory, 'cases')
def _testLoader(self, d={}, modules=[], tests=[], filters={}):
def _testLoader(self, d={}, modules=[], tests=[], **kwargs):
from oeqa.core.context import OETestContext
tc = OETestContext(d, self.logger)
tc.loadTests(self.cases_path, modules=modules, tests=tests,
filters=filters)
**kwargs)
return tc

View File

@@ -14,35 +14,58 @@ setup_sys_path()
from oeqa.core.exception import OEQADependency
from oeqa.core.utils.test import getCaseMethod, getSuiteCasesNames, getSuiteCasesIDs
class TestFilterDecorator(TestBase):
def _runFilterTest(self, modules, filters, expect, msg):
tc = self._testLoader(modules=modules, filters=filters)
test_loaded = set(getSuiteCasesNames(tc.suites))
self.assertEqual(expect, test_loaded, msg=msg)
class TestTagDecorator(TestBase):
def _runTest(self, modules, filterfn, expect):
tc = self._testLoader(modules = modules, tags_filter = filterfn)
test_loaded = set(getSuiteCasesIDs(tc.suites))
self.assertEqual(expect, test_loaded)
def test_oetag(self):
# Get all cases without filtering.
filter_all = {}
test_all = {'testTagGood', 'testTagOther', 'testTagNone'}
msg_all = 'Failed to get all oetag cases without filtering.'
# get all cases without any filtering
self._runTest(['oetag'], None, {
'oetag.TagTest.testTagGood',
'oetag.TagTest.testTagOther',
'oetag.TagTest.testTagOtherMulti',
'oetag.TagTest.testTagNone',
'oetag.TagClassTest.testTagOther',
'oetag.TagClassTest.testTagOtherMulti',
'oetag.TagClassTest.testTagNone',
})
# Get cases with 'goodTag'.
filter_good = {'oetag':'goodTag'}
test_good = {'testTagGood'}
msg_good = 'Failed to get just one test filtering with "goodTag" oetag.'
# exclude any case with tags
self._runTest(['oetag'], lambda tags: tags, {
'oetag.TagTest.testTagNone',
})
# Get cases with an invalid tag.
filter_invalid = {'oetag':'invalidTag'}
test_invalid = set()
msg_invalid = 'Failed to filter all test using an invalid oetag.'
# exclude any case with otherTag
self._runTest(['oetag'], lambda tags: "otherTag" in tags, {
'oetag.TagTest.testTagGood',
'oetag.TagTest.testTagNone',
'oetag.TagClassTest.testTagNone',
})
tests = ((filter_all, test_all, msg_all),
(filter_good, test_good, msg_good),
(filter_invalid, test_invalid, msg_invalid))
# exclude any case with classTag
self._runTest(['oetag'], lambda tags: "classTag" in tags, {
'oetag.TagTest.testTagGood',
'oetag.TagTest.testTagOther',
'oetag.TagTest.testTagOtherMulti',
'oetag.TagTest.testTagNone',
})
for test in tests:
self._runFilterTest(['oetag'], test[0], test[1], test[2])
# include any case with classTag
self._runTest(['oetag'], lambda tags: "classTag" not in tags, {
'oetag.TagClassTest.testTagOther',
'oetag.TagClassTest.testTagOtherMulti',
'oetag.TagClassTest.testTagNone',
})
# include any case with classTag or no tags
self._runTest(['oetag'], lambda tags: tags and "classTag" not in tags, {
'oetag.TagTest.testTagNone',
'oetag.TagClassTest.testTagOther',
'oetag.TagClassTest.testTagOtherMulti',
'oetag.TagClassTest.testTagNone',
})
class TestDependsDecorator(TestBase):
modules = ['depends']

View File

@@ -15,31 +15,6 @@ from oeqa.core.exception import OEQADependency
from oeqa.core.utils.test import getSuiteModules, getSuiteCasesIDs
class TestLoader(TestBase):
def test_fail_empty_filter(self):
filters = {'oetag' : ''}
expect = 'Filter oetag specified is empty'
msg = 'Expected TypeError exception for having invalid filter'
try:
# Must throw TypeError because empty filter
tc = self._testLoader(filters=filters)
self.fail(msg)
except TypeError as e:
result = True if expect in str(e) else False
self.assertTrue(result, msg=msg)
def test_fail_invalid_filter(self):
filters = {'invalid' : 'good'}
expect = 'filter but not declared in any of'
msg = 'Expected TypeError exception for having invalid filter'
try:
# Must throw TypeError because invalid filter
tc = self._testLoader(filters=filters)
self.fail(msg)
except TypeError as e:
result = True if expect in str(e) else False
self.assertTrue(result, msg=msg)
@unittest.skip("invalid directory is missing oetag.py")
def test_fail_duplicated_module(self):
cases_path = self.cases_path