summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa/core
diff options
context:
space:
mode:
authorNathan Rossi <nathan@nathanrossi.com>2019-09-03 16:56:41 +0000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2019-09-07 21:56:43 +0100
commitc3625e141dfbb6c8b8bfa3ff05e0e87f215bf606 (patch)
tree5c2e6dbfea3d050d7929234c4a880e923795a066 /meta/lib/oeqa/core
parent1220faf6659e404f6aa2c3155eb8840ac361c2b2 (diff)
downloadpoky-c3625e141dfbb6c8b8bfa3ff05e0e87f215bf606.tar.gz
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>
Diffstat (limited to 'meta/lib/oeqa/core')
-rw-r--r--meta/lib/oeqa/core/context.py4
-rw-r--r--meta/lib/oeqa/core/decorator/__init__.py20
-rw-r--r--meta/lib/oeqa/core/decorator/oetag.py27
-rw-r--r--meta/lib/oeqa/core/loader.py61
-rw-r--r--meta/lib/oeqa/core/tests/cases/data.py2
-rw-r--r--meta/lib/oeqa/core/tests/cases/oetag.py21
-rw-r--r--meta/lib/oeqa/core/tests/common.py4
-rwxr-xr-xmeta/lib/oeqa/core/tests/test_decorators.py77
-rwxr-xr-xmeta/lib/oeqa/core/tests/test_loader.py25
9 files changed, 103 insertions, 138 deletions
diff --git a/meta/lib/oeqa/core/context.py b/meta/lib/oeqa/core/context.py
index 68819cc338..14fc6a54f4 100644
--- a/meta/lib/oeqa/core/context.py
+++ b/meta/lib/oeqa/core/context.py
@@ -64,12 +64,12 @@ class OETestContext(object):
64 setattr(tclass, 'setUpHooker', skipfuncgen('Skip by the command line argument "%s"' % skip)) 64 setattr(tclass, 'setUpHooker', skipfuncgen('Skip by the command line argument "%s"' % skip))
65 65
66 def loadTests(self, module_paths, modules=[], tests=[], 66 def loadTests(self, module_paths, modules=[], tests=[],
67 modules_manifest="", modules_required=[], filters={}): 67 modules_manifest="", modules_required=[], **kwargs):
68 if modules_manifest: 68 if modules_manifest:
69 modules = self._read_modules_from_manifest(modules_manifest) 69 modules = self._read_modules_from_manifest(modules_manifest)
70 70
71 self.loader = self.loaderClass(self, module_paths, modules, tests, 71 self.loader = self.loaderClass(self, module_paths, modules, tests,
72 modules_required, filters) 72 modules_required, **kwargs)
73 self.suites = self.loader.discover() 73 self.suites = self.loader.discover()
74 74
75 def runTests(self, processes=None, skips=[]): 75 def runTests(self, processes=None, skips=[]):
diff --git a/meta/lib/oeqa/core/decorator/__init__.py b/meta/lib/oeqa/core/decorator/__init__.py
index 923b218266..1a5ac40134 100644
--- a/meta/lib/oeqa/core/decorator/__init__.py
+++ b/meta/lib/oeqa/core/decorator/__init__.py
@@ -6,6 +6,7 @@
6 6
7from functools import wraps 7from functools import wraps
8from abc import abstractmethod, ABCMeta 8from abc import abstractmethod, ABCMeta
9from oeqa.core.utils.misc import strToList
9 10
10decoratorClasses = set() 11decoratorClasses = set()
11 12
@@ -63,12 +64,15 @@ class OETestDiscover(OETestDecorator):
63 def discover(registry): 64 def discover(registry):
64 return registry['cases'] 65 return registry['cases']
65 66
66class OETestFilter(OETestDecorator): 67def OETestTag(*tags):
68 expandedtags = []
69 for tag in tags:
70 expandedtags += strToList(tag)
71 def decorator(item):
72 if hasattr(item, "__oeqa_testtags"):
73 item.__oeqa_testtags += expandedtags
74 else:
75 item.__oeqa_testtags = expandedtags
76 return item
77 return decorator
67 78
68 # OETestLoader call it while loading the tests
69 # in loadTestsFromTestCase method, it needs to
70 # return a bool, True if needs to be filtered.
71 # This method must consume the filter used.
72 @abstractmethod
73 def filtrate(self, filters):
74 return False
diff --git a/meta/lib/oeqa/core/decorator/oetag.py b/meta/lib/oeqa/core/decorator/oetag.py
deleted file mode 100644
index 8c31138dac..0000000000
--- a/meta/lib/oeqa/core/decorator/oetag.py
+++ /dev/null
@@ -1,27 +0,0 @@
1#
2# Copyright (C) 2016 Intel Corporation
3#
4# SPDX-License-Identifier: MIT
5#
6
7from . import OETestFilter, registerDecorator
8from oeqa.core.utils.misc import strToList
9
10def _tagFilter(tags, filters):
11 return False if set(tags) & set(filters) else True
12
13@registerDecorator
14class OETestTag(OETestFilter):
15 attrs = ('oetag',)
16
17 def bind(self, registry, case):
18 super(OETestTag, self).bind(registry, case)
19 self.oetag = strToList(self.oetag, 'oetag')
20
21 def filtrate(self, filters):
22 if filters.get('oetag'):
23 filterx = strToList(filters['oetag'], 'oetag')
24 del filters['oetag']
25 if _tagFilter(self.oetag, filterx):
26 return True
27 return False
diff --git a/meta/lib/oeqa/core/loader.py b/meta/lib/oeqa/core/loader.py
index 7fea0585c7..0d7970d49e 100644
--- a/meta/lib/oeqa/core/loader.py
+++ b/meta/lib/oeqa/core/loader.py
@@ -16,7 +16,7 @@ from oeqa.core.utils.test import getSuiteModules, getCaseID
16from oeqa.core.exception import OEQATestNotFound 16from oeqa.core.exception import OEQATestNotFound
17from oeqa.core.case import OETestCase 17from oeqa.core.case import OETestCase
18from oeqa.core.decorator import decoratorClasses, OETestDecorator, \ 18from oeqa.core.decorator import decoratorClasses, OETestDecorator, \
19 OETestFilter, OETestDiscover 19 OETestDiscover
20 20
21# When loading tests, the unittest framework stores any exceptions and 21# When loading tests, the unittest framework stores any exceptions and
22# displays them only when the run method is called. 22# displays them only when the run method is called.
@@ -68,7 +68,7 @@ class OETestLoader(unittest.TestLoader):
68 '_top_level_dir'] 68 '_top_level_dir']
69 69
70 def __init__(self, tc, module_paths, modules, tests, modules_required, 70 def __init__(self, tc, module_paths, modules, tests, modules_required,
71 filters, *args, **kwargs): 71 *args, **kwargs):
72 self.tc = tc 72 self.tc = tc
73 73
74 self.modules = _built_modules_dict(modules) 74 self.modules = _built_modules_dict(modules)
@@ -76,13 +76,7 @@ class OETestLoader(unittest.TestLoader):
76 self.tests = tests 76 self.tests = tests
77 self.modules_required = modules_required 77 self.modules_required = modules_required
78 78
79 self.filters = filters 79 self.tags_filter = kwargs.get("tags_filter", None)
80 self.decorator_filters = [d for d in decoratorClasses if \
81 issubclass(d, OETestFilter)]
82 self._validateFilters(self.filters, self.decorator_filters)
83 self.used_filters = [d for d in self.decorator_filters
84 for f in self.filters
85 if f in d.attrs]
86 80
87 if isinstance(module_paths, str): 81 if isinstance(module_paths, str):
88 module_paths = [module_paths] 82 module_paths = [module_paths]
@@ -104,28 +98,6 @@ class OETestLoader(unittest.TestLoader):
104 setattr(testCaseClass, 'td', self.tc.td) 98 setattr(testCaseClass, 'td', self.tc.td)
105 setattr(testCaseClass, 'logger', self.tc.logger) 99 setattr(testCaseClass, 'logger', self.tc.logger)
106 100
107 def _validateFilters(self, filters, decorator_filters):
108 # Validate if filter isn't empty
109 for key,value in filters.items():
110 if not value:
111 raise TypeError("Filter %s specified is empty" % key)
112
113 # Validate unique attributes
114 attr_filters = [attr for clss in decorator_filters \
115 for attr in clss.attrs]
116 dup_attr = [attr for attr in attr_filters
117 if attr_filters.count(attr) > 1]
118 if dup_attr:
119 raise TypeError('Detected duplicated attribute(s) %s in filter'
120 ' decorators' % ' ,'.join(dup_attr))
121
122 # Validate if filter is supported
123 for f in filters:
124 if f not in attr_filters:
125 classes = ', '.join([d.__name__ for d in decorator_filters])
126 raise TypeError('Found "%s" filter but not declared in any of '
127 '%s decorators' % (f, classes))
128
129 def _registerTestCase(self, case): 101 def _registerTestCase(self, case):
130 case_id = case.id() 102 case_id = case.id()
131 self.tc._registry['cases'][case_id] = case 103 self.tc._registry['cases'][case_id] = case
@@ -188,19 +160,20 @@ class OETestLoader(unittest.TestLoader):
188 return True 160 return True
189 161
190 # Decorator filters 162 # Decorator filters
191 if self.filters and isinstance(case, OETestCase): 163 if self.tags_filter is not None and callable(self.tags_filter):
192 filters = self.filters.copy() 164 alltags = set()
193 case_decorators = [cd for cd in case.decorators 165 # pull tags from the case class
194 if cd.__class__ in self.used_filters] 166 if hasattr(case, "__oeqa_testtags"):
195 167 for t in getattr(case, "__oeqa_testtags"):
196 # Iterate over case decorators to check if needs to be filtered. 168 alltags.add(t)
197 for cd in case_decorators: 169 # pull tags from the method itself
198 if cd.filtrate(filters): 170 if hasattr(case, test_name):
199 return True 171 method = getattr(case, test_name)
200 172 if hasattr(method, "__oeqa_testtags"):
201 # Case is missing one or more decorators for all the filters 173 for t in getattr(method, "__oeqa_testtags"):
202 # being used, so filter test case. 174 alltags.add(t)
203 if filters: 175
176 if self.tags_filter(alltags):
204 return True 177 return True
205 178
206 return False 179 return False
diff --git a/meta/lib/oeqa/core/tests/cases/data.py b/meta/lib/oeqa/core/tests/cases/data.py
index 0d8de87ae7..61f88547f7 100644
--- a/meta/lib/oeqa/core/tests/cases/data.py
+++ b/meta/lib/oeqa/core/tests/cases/data.py
@@ -5,7 +5,7 @@
5# 5#
6 6
7from oeqa.core.case import OETestCase 7from oeqa.core.case import OETestCase
8from oeqa.core.decorator.oetag import OETestTag 8from oeqa.core.decorator import OETestTag
9from oeqa.core.decorator.data import OETestDataDepends 9from oeqa.core.decorator.data import OETestDataDepends
10 10
11class DataTest(OETestCase): 11class DataTest(OETestCase):
diff --git a/meta/lib/oeqa/core/tests/cases/oetag.py b/meta/lib/oeqa/core/tests/cases/oetag.py
index 4e1d080985..52f97dfda6 100644
--- a/meta/lib/oeqa/core/tests/cases/oetag.py
+++ b/meta/lib/oeqa/core/tests/cases/oetag.py
@@ -5,10 +5,9 @@
5# 5#
6 6
7from oeqa.core.case import OETestCase 7from oeqa.core.case import OETestCase
8from oeqa.core.decorator.oetag import OETestTag 8from oeqa.core.decorator import OETestTag
9 9
10class TagTest(OETestCase): 10class TagTest(OETestCase):
11
12 @OETestTag('goodTag') 11 @OETestTag('goodTag')
13 def testTagGood(self): 12 def testTagGood(self):
14 self.assertTrue(True, msg='How is this possible?') 13 self.assertTrue(True, msg='How is this possible?')
@@ -17,5 +16,23 @@ class TagTest(OETestCase):
17 def testTagOther(self): 16 def testTagOther(self):
18 self.assertTrue(True, msg='How is this possible?') 17 self.assertTrue(True, msg='How is this possible?')
19 18
19 @OETestTag('otherTag', 'multiTag')
20 def testTagOtherMulti(self):
21 self.assertTrue(True, msg='How is this possible?')
22
20 def testTagNone(self): 23 def testTagNone(self):
21 self.assertTrue(True, msg='How is this possible?') 24 self.assertTrue(True, msg='How is this possible?')
25
26@OETestTag('classTag')
27class TagClassTest(OETestCase):
28 @OETestTag('otherTag')
29 def testTagOther(self):
30 self.assertTrue(True, msg='How is this possible?')
31
32 @OETestTag('otherTag', 'multiTag')
33 def testTagOtherMulti(self):
34 self.assertTrue(True, msg='How is this possible?')
35
36 def testTagNone(self):
37 self.assertTrue(True, msg='How is this possible?')
38
diff --git a/meta/lib/oeqa/core/tests/common.py b/meta/lib/oeqa/core/tests/common.py
index 39efd504c0..88cc758ad3 100644
--- a/meta/lib/oeqa/core/tests/common.py
+++ b/meta/lib/oeqa/core/tests/common.py
@@ -30,9 +30,9 @@ class TestBase(unittest.TestCase):
30 directory = os.path.dirname(os.path.abspath(__file__)) 30 directory = os.path.dirname(os.path.abspath(__file__))
31 self.cases_path = os.path.join(directory, 'cases') 31 self.cases_path = os.path.join(directory, 'cases')
32 32
33 def _testLoader(self, d={}, modules=[], tests=[], filters={}): 33 def _testLoader(self, d={}, modules=[], tests=[], **kwargs):
34 from oeqa.core.context import OETestContext 34 from oeqa.core.context import OETestContext
35 tc = OETestContext(d, self.logger) 35 tc = OETestContext(d, self.logger)
36 tc.loadTests(self.cases_path, modules=modules, tests=tests, 36 tc.loadTests(self.cases_path, modules=modules, tests=tests,
37 filters=filters) 37 **kwargs)
38 return tc 38 return tc
diff --git a/meta/lib/oeqa/core/tests/test_decorators.py b/meta/lib/oeqa/core/tests/test_decorators.py
index 499cd66ff3..b798bf7d33 100755
--- a/meta/lib/oeqa/core/tests/test_decorators.py
+++ b/meta/lib/oeqa/core/tests/test_decorators.py
@@ -14,35 +14,58 @@ setup_sys_path()
14from oeqa.core.exception import OEQADependency 14from oeqa.core.exception import OEQADependency
15from oeqa.core.utils.test import getCaseMethod, getSuiteCasesNames, getSuiteCasesIDs 15from oeqa.core.utils.test import getCaseMethod, getSuiteCasesNames, getSuiteCasesIDs
16 16
17class TestFilterDecorator(TestBase): 17class TestTagDecorator(TestBase):
18 18 def _runTest(self, modules, filterfn, expect):
19 def _runFilterTest(self, modules, filters, expect, msg): 19 tc = self._testLoader(modules = modules, tags_filter = filterfn)
20 tc = self._testLoader(modules=modules, filters=filters) 20 test_loaded = set(getSuiteCasesIDs(tc.suites))
21 test_loaded = set(getSuiteCasesNames(tc.suites)) 21 self.assertEqual(expect, test_loaded)
22 self.assertEqual(expect, test_loaded, msg=msg)
23 22
24 def test_oetag(self): 23 def test_oetag(self):
25 # Get all cases without filtering. 24 # get all cases without any filtering
26 filter_all = {} 25 self._runTest(['oetag'], None, {
27 test_all = {'testTagGood', 'testTagOther', 'testTagNone'} 26 'oetag.TagTest.testTagGood',
28 msg_all = 'Failed to get all oetag cases without filtering.' 27 'oetag.TagTest.testTagOther',
29 28 'oetag.TagTest.testTagOtherMulti',
30 # Get cases with 'goodTag'. 29 'oetag.TagTest.testTagNone',
31 filter_good = {'oetag':'goodTag'} 30 'oetag.TagClassTest.testTagOther',
32 test_good = {'testTagGood'} 31 'oetag.TagClassTest.testTagOtherMulti',
33 msg_good = 'Failed to get just one test filtering with "goodTag" oetag.' 32 'oetag.TagClassTest.testTagNone',
34 33 })
35 # Get cases with an invalid tag. 34
36 filter_invalid = {'oetag':'invalidTag'} 35 # exclude any case with tags
37 test_invalid = set() 36 self._runTest(['oetag'], lambda tags: tags, {
38 msg_invalid = 'Failed to filter all test using an invalid oetag.' 37 'oetag.TagTest.testTagNone',
39 38 })
40 tests = ((filter_all, test_all, msg_all), 39
41 (filter_good, test_good, msg_good), 40 # exclude any case with otherTag
42 (filter_invalid, test_invalid, msg_invalid)) 41 self._runTest(['oetag'], lambda tags: "otherTag" in tags, {
43 42 'oetag.TagTest.testTagGood',
44 for test in tests: 43 'oetag.TagTest.testTagNone',
45 self._runFilterTest(['oetag'], test[0], test[1], test[2]) 44 'oetag.TagClassTest.testTagNone',
45 })
46
47 # exclude any case with classTag
48 self._runTest(['oetag'], lambda tags: "classTag" in tags, {
49 'oetag.TagTest.testTagGood',
50 'oetag.TagTest.testTagOther',
51 'oetag.TagTest.testTagOtherMulti',
52 'oetag.TagTest.testTagNone',
53 })
54
55 # include any case with classTag
56 self._runTest(['oetag'], lambda tags: "classTag" not in tags, {
57 'oetag.TagClassTest.testTagOther',
58 'oetag.TagClassTest.testTagOtherMulti',
59 'oetag.TagClassTest.testTagNone',
60 })
61
62 # include any case with classTag or no tags
63 self._runTest(['oetag'], lambda tags: tags and "classTag" not in tags, {
64 'oetag.TagTest.testTagNone',
65 'oetag.TagClassTest.testTagOther',
66 'oetag.TagClassTest.testTagOtherMulti',
67 'oetag.TagClassTest.testTagNone',
68 })
46 69
47class TestDependsDecorator(TestBase): 70class TestDependsDecorator(TestBase):
48 modules = ['depends'] 71 modules = ['depends']
diff --git a/meta/lib/oeqa/core/tests/test_loader.py b/meta/lib/oeqa/core/tests/test_loader.py
index e73c91b141..cb38ac845e 100755
--- a/meta/lib/oeqa/core/tests/test_loader.py
+++ b/meta/lib/oeqa/core/tests/test_loader.py
@@ -15,31 +15,6 @@ from oeqa.core.exception import OEQADependency
15from oeqa.core.utils.test import getSuiteModules, getSuiteCasesIDs 15from oeqa.core.utils.test import getSuiteModules, getSuiteCasesIDs
16 16
17class TestLoader(TestBase): 17class TestLoader(TestBase):
18
19 def test_fail_empty_filter(self):
20 filters = {'oetag' : ''}
21 expect = 'Filter oetag specified is empty'
22 msg = 'Expected TypeError exception for having invalid filter'
23 try:
24 # Must throw TypeError because empty filter
25 tc = self._testLoader(filters=filters)
26 self.fail(msg)
27 except TypeError as e:
28 result = True if expect in str(e) else False
29 self.assertTrue(result, msg=msg)
30
31 def test_fail_invalid_filter(self):
32 filters = {'invalid' : 'good'}
33 expect = 'filter but not declared in any of'
34 msg = 'Expected TypeError exception for having invalid filter'
35 try:
36 # Must throw TypeError because invalid filter
37 tc = self._testLoader(filters=filters)
38 self.fail(msg)
39 except TypeError as e:
40 result = True if expect in str(e) else False
41 self.assertTrue(result, msg=msg)
42
43 @unittest.skip("invalid directory is missing oetag.py") 18 @unittest.skip("invalid directory is missing oetag.py")
44 def test_fail_duplicated_module(self): 19 def test_fail_duplicated_module(self):
45 cases_path = self.cases_path 20 cases_path = self.cases_path