diff options
author | Nathan Rossi <nathan@nathanrossi.com> | 2019-09-03 16:56:41 +0000 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2019-09-07 21:56:43 +0100 |
commit | c3625e141dfbb6c8b8bfa3ff05e0e87f215bf606 (patch) | |
tree | 5c2e6dbfea3d050d7929234c4a880e923795a066 | |
parent | 1220faf6659e404f6aa2c3155eb8840ac361c2b2 (diff) | |
download | poky-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>
-rw-r--r-- | meta/lib/oeqa/core/context.py | 4 | ||||
-rw-r--r-- | meta/lib/oeqa/core/decorator/__init__.py | 20 | ||||
-rw-r--r-- | meta/lib/oeqa/core/decorator/oetag.py | 27 | ||||
-rw-r--r-- | meta/lib/oeqa/core/loader.py | 61 | ||||
-rw-r--r-- | meta/lib/oeqa/core/tests/cases/data.py | 2 | ||||
-rw-r--r-- | meta/lib/oeqa/core/tests/cases/oetag.py | 21 | ||||
-rw-r--r-- | meta/lib/oeqa/core/tests/common.py | 4 | ||||
-rwxr-xr-x | meta/lib/oeqa/core/tests/test_decorators.py | 77 | ||||
-rwxr-xr-x | meta/lib/oeqa/core/tests/test_loader.py | 25 |
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 | ||
7 | from functools import wraps | 7 | from functools import wraps |
8 | from abc import abstractmethod, ABCMeta | 8 | from abc import abstractmethod, ABCMeta |
9 | from oeqa.core.utils.misc import strToList | ||
9 | 10 | ||
10 | decoratorClasses = set() | 11 | decoratorClasses = 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 | ||
66 | class OETestFilter(OETestDecorator): | 67 | def 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 | |||
7 | from . import OETestFilter, registerDecorator | ||
8 | from oeqa.core.utils.misc import strToList | ||
9 | |||
10 | def _tagFilter(tags, filters): | ||
11 | return False if set(tags) & set(filters) else True | ||
12 | |||
13 | @registerDecorator | ||
14 | class 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 | |||
16 | from oeqa.core.exception import OEQATestNotFound | 16 | from oeqa.core.exception import OEQATestNotFound |
17 | from oeqa.core.case import OETestCase | 17 | from oeqa.core.case import OETestCase |
18 | from oeqa.core.decorator import decoratorClasses, OETestDecorator, \ | 18 | from 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 | ||
7 | from oeqa.core.case import OETestCase | 7 | from oeqa.core.case import OETestCase |
8 | from oeqa.core.decorator.oetag import OETestTag | 8 | from oeqa.core.decorator import OETestTag |
9 | from oeqa.core.decorator.data import OETestDataDepends | 9 | from oeqa.core.decorator.data import OETestDataDepends |
10 | 10 | ||
11 | class DataTest(OETestCase): | 11 | class 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 | ||
7 | from oeqa.core.case import OETestCase | 7 | from oeqa.core.case import OETestCase |
8 | from oeqa.core.decorator.oetag import OETestTag | 8 | from oeqa.core.decorator import OETestTag |
9 | 9 | ||
10 | class TagTest(OETestCase): | 10 | class 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') | ||
27 | class 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() | |||
14 | from oeqa.core.exception import OEQADependency | 14 | from oeqa.core.exception import OEQADependency |
15 | from oeqa.core.utils.test import getCaseMethod, getSuiteCasesNames, getSuiteCasesIDs | 15 | from oeqa.core.utils.test import getCaseMethod, getSuiteCasesNames, getSuiteCasesIDs |
16 | 16 | ||
17 | class TestFilterDecorator(TestBase): | 17 | class 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 | ||
47 | class TestDependsDecorator(TestBase): | 70 | class 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 | |||
15 | from oeqa.core.utils.test import getSuiteModules, getSuiteCasesIDs | 15 | from oeqa.core.utils.test import getSuiteModules, getSuiteCasesIDs |
16 | 16 | ||
17 | class TestLoader(TestBase): | 17 | class 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 |