未验证 提交 5ae77d8b 编写于 作者: A Amador Pahim

Merge branch 'clebergnu-instrumented_loader_to_safeloader'

Signed-off-by: NAmador Pahim <amador@pahim.org>
......@@ -17,8 +17,6 @@
Test loader module.
"""
import ast
import collections
import imp
import inspect
import os
......@@ -634,203 +632,6 @@ class FileLoader(TestLoader):
subtests_filter))
return tests
def _find_avocado_tests(self, path, class_name=None):
"""
Attempts to find Avocado instrumented tests from Python source files
:param path: path to a Python source code file
:type path: str
:param class_name: the specific class to be found
:type path: str
:returns: tuple where first item is dict with class name and additional
info such as method names and tags; the second item is
set of class names which look like avocado tests but are
force-disabled.
:rtype: tuple
"""
# If only the Test class was imported from the avocado namespace
test_import = False
# The name used, in case of 'from avocado import Test as AvocadoTest'
test_import_name = None
# If the "avocado" module itself was imported
mod_import = False
# The name used, in case of 'import avocado as avocadolib'
mod_import_name = None
# The resulting test classes
result = collections.OrderedDict()
disabled = set()
if os.path.isdir(path):
path = os.path.join(path, "__init__.py")
with open(path) as source_file:
mod = ast.parse(source_file.read(), path)
for statement in mod.body:
# Looking for a 'from avocado import Test'
if (isinstance(statement, ast.ImportFrom) and
statement.module == 'avocado'):
for name in statement.names:
if name.name == 'Test':
test_import = True
if name.asname is not None:
test_import_name = name.asname
else:
test_import_name = name.name
break
# Looking for a 'import avocado'
elif isinstance(statement, ast.Import):
for name in statement.names:
if name.name == 'avocado':
mod_import = True
if name.asname is not None:
mod_import_name = name.nasname
else:
mod_import_name = name.name
# Looking for a 'class Anything(anything):'
elif isinstance(statement, ast.ClassDef):
# class_name will exist only under recursion. In that
# case, we will only process the class if it has the
# expected class_name.
if class_name is not None and class_name != statement.name:
continue
docstring = ast.get_docstring(statement)
# Looking for a class that has in the docstring either
# ":avocado: enable" or ":avocado: disable
has_disable = safeloader.check_docstring_directive(docstring,
'disable')
if (has_disable and class_name is None):
disabled.add(statement.name)
continue
cl_tags = safeloader.get_docstring_directives_tags(docstring)
has_enable = safeloader.check_docstring_directive(docstring,
'enable')
if (has_enable and class_name is None):
info = self._get_methods_info(statement.body, cl_tags)
result[statement.name] = info
continue
# Looking for the 'recursive' docstring or a 'class_name'
# (meaning we are under recursion)
has_recurse = safeloader.check_docstring_directive(docstring,
'recursive')
if (has_recurse or class_name is not None):
info = self._get_methods_info(statement.body, cl_tags)
result[statement.name] = info
# Getting the list of parents of the current class
parents = statement.bases
# Searching the parents in the same module
for parent in parents[:]:
# Looking for a 'class FooTest(module.Parent)'
if isinstance(parent, ast.Attribute):
parent_class = parent.attr
# Looking for a 'class FooTest(Parent)'
else:
parent_class = parent.id
res, dis = self._find_avocado_tests(path, parent_class)
if res:
parents.remove(parent)
for cls in res:
info.extend(res[cls])
disabled.update(dis)
# If there are parents left to be discovered, they
# might be in a different module.
for parent in parents:
if isinstance(parent, ast.Attribute):
# Looking for a 'class FooTest(module.Parent)'
parent_module = parent.value.id
parent_class = parent.attr
else:
# Looking for a 'class FooTest(Parent)'
parent_module = None
parent_class = parent.id
for node in mod.body:
reference = None
# Looking for 'from parent import class'
if isinstance(node, ast.ImportFrom):
reference = parent_class
# Looking for 'import parent'
elif isinstance(node, ast.Import):
reference = parent_module
if reference is None:
continue
for artifact in node.names:
# Looking for a class alias
# ('from parent import class as alias')
if artifact.asname is not None:
parent_class = reference = artifact.name
# If the parent class or the parent module
# is found in the imports, discover the
# parent module path and find the parent
# class there
if artifact.name == reference:
modules_paths = [os.path.dirname(path)]
modules_paths.extend(sys.path)
if parent_module is None:
parent_module = node.module
_, ppath, _ = imp.find_module(parent_module,
modules_paths)
res, dis = self._find_avocado_tests(ppath,
parent_class)
if res:
for cls in res:
info.extend(res[cls])
disabled.update(dis)
continue
if test_import:
base_ids = [base.id for base in statement.bases
if hasattr(base, 'id')]
# Looking for a 'class FooTest(Test):'
if test_import_name in base_ids:
info = self._get_methods_info(statement.body,
cl_tags)
result[statement.name] = info
continue
# Looking for a 'class FooTest(avocado.Test):'
if mod_import:
for base in statement.bases:
module = base.value.id
klass = base.attr
if module == mod_import_name and klass == 'Test':
info = self._get_methods_info(statement.body,
cl_tags)
result[statement.name] = info
continue
return result, disabled
@staticmethod
def _get_methods_info(statement_body, class_tags):
methods_info = []
for st in statement_body:
if (isinstance(st, ast.FunctionDef) and
st.name.startswith('test')):
docstring = ast.get_docstring(st)
mt_tags = safeloader.get_docstring_directives_tags(docstring)
mt_tags.update(class_tags)
methods = [method for method, _ in methods_info]
if st.name not in methods:
methods_info.append((st.name, mt_tags))
return methods_info
def _find_python_unittests(self, test_path, disabled, subtests_filter):
result = []
class_methods = safeloader.find_class_and_methods(test_path,
......@@ -856,7 +657,7 @@ class FileLoader(TestLoader):
test_name = test_path
try:
# Avocado tests
avocado_tests, disabled = self._find_avocado_tests(test_path)
avocado_tests, disabled = safeloader.find_avocado_tests(test_path)
if avocado_tests:
test_factories = []
for test_class, info in avocado_tests.items():
......
......@@ -17,7 +17,11 @@ Safe (AST based) test loader module utilities
"""
import ast
import collections
import imp
import os
import re
import sys
from ..utils import data_structures
......@@ -154,3 +158,201 @@ def find_class_and_methods(path, method_pattern=None, base_class=None):
methods = data_structures.ordered_list_unique(methods)
result[statement.name] = methods
return result
def get_methods_info(statement_body, class_tags):
"""
Returns information on an Avocado instrumented test method
"""
methods_info = []
for st in statement_body:
if (isinstance(st, ast.FunctionDef) and
st.name.startswith('test')):
docstring = ast.get_docstring(st)
mt_tags = get_docstring_directives_tags(docstring)
mt_tags.update(class_tags)
methods = [method for method, _ in methods_info]
if st.name not in methods:
methods_info.append((st.name, mt_tags))
return methods_info
def find_avocado_tests(path, class_name=None):
"""
Attempts to find Avocado instrumented tests from Python source files
:param path: path to a Python source code file
:type path: str
:param class_name: the specific class to be found
:type path: str
:returns: tuple where first item is dict with class name and additional
info such as method names and tags; the second item is
set of class names which look like avocado tests but are
force-disabled.
:rtype: tuple
"""
# If only the Test class was imported from the avocado namespace
test_import = False
# The name used, in case of 'from avocado import Test as AvocadoTest'
test_import_name = None
# If the "avocado" module itself was imported
mod_import = False
# The name used, in case of 'import avocado as avocadolib'
mod_import_name = None
# The resulting test classes
result = collections.OrderedDict()
disabled = set()
if os.path.isdir(path):
path = os.path.join(path, "__init__.py")
with open(path) as source_file:
mod = ast.parse(source_file.read(), path)
for statement in mod.body:
# Looking for a 'from avocado import Test'
if (isinstance(statement, ast.ImportFrom) and
statement.module == 'avocado'):
for name in statement.names:
if name.name == 'Test':
test_import = True
if name.asname is not None:
test_import_name = name.asname
else:
test_import_name = name.name
break
# Looking for a 'import avocado'
elif isinstance(statement, ast.Import):
for name in statement.names:
if name.name == 'avocado':
mod_import = True
if name.asname is not None:
mod_import_name = name.nasname
else:
mod_import_name = name.name
# Looking for a 'class Anything(anything):'
elif isinstance(statement, ast.ClassDef):
# class_name will exist only under recursion. In that
# case, we will only process the class if it has the
# expected class_name.
if class_name is not None and class_name != statement.name:
continue
docstring = ast.get_docstring(statement)
# Looking for a class that has in the docstring either
# ":avocado: enable" or ":avocado: disable
has_disable = check_docstring_directive(docstring, 'disable')
if (has_disable and class_name is None):
disabled.add(statement.name)
continue
cl_tags = get_docstring_directives_tags(docstring)
has_enable = check_docstring_directive(docstring, 'enable')
if (has_enable and class_name is None):
info = get_methods_info(statement.body, cl_tags)
result[statement.name] = info
continue
# Looking for the 'recursive' docstring or a 'class_name'
# (meaning we are under recursion)
has_recurse = check_docstring_directive(docstring, 'recursive')
if (has_recurse or class_name is not None):
info = get_methods_info(statement.body, cl_tags)
result[statement.name] = info
# Getting the list of parents of the current class
parents = statement.bases
# Searching the parents in the same module
for parent in parents[:]:
# Looking for a 'class FooTest(module.Parent)'
if isinstance(parent, ast.Attribute):
parent_class = parent.attr
# Looking for a 'class FooTest(Parent)'
else:
parent_class = parent.id
res, dis = find_avocado_tests(path, parent_class)
if res:
parents.remove(parent)
for cls in res:
info.extend(res[cls])
disabled.update(dis)
# If there are parents left to be discovered, they
# might be in a different module.
for parent in parents:
if isinstance(parent, ast.Attribute):
# Looking for a 'class FooTest(module.Parent)'
parent_module = parent.value.id
parent_class = parent.attr
else:
# Looking for a 'class FooTest(Parent)'
parent_module = None
parent_class = parent.id
for node in mod.body:
reference = None
# Looking for 'from parent import class'
if isinstance(node, ast.ImportFrom):
reference = parent_class
# Looking for 'import parent'
elif isinstance(node, ast.Import):
reference = parent_module
if reference is None:
continue
for artifact in node.names:
# Looking for a class alias
# ('from parent import class as alias')
if artifact.asname is not None:
parent_class = reference = artifact.name
# If the parent class or the parent module
# is found in the imports, discover the
# parent module path and find the parent
# class there
if artifact.name == reference:
modules_paths = [os.path.dirname(path)]
modules_paths.extend(sys.path)
if parent_module is None:
parent_module = node.module
_, ppath, _ = imp.find_module(parent_module,
modules_paths)
res, dis = find_avocado_tests(ppath,
parent_class)
if res:
for cls in res:
info.extend(res[cls])
disabled.update(dis)
continue
if test_import:
base_ids = [base.id for base in statement.bases
if hasattr(base, 'id')]
# Looking for a 'class FooTest(Test):'
if test_import_name in base_ids:
info = get_methods_info(statement.body,
cl_tags)
result[statement.name] = info
continue
# Looking for a 'class FooTest(avocado.Test):'
if mod_import:
for base in statement.bases:
module = base.value.id
klass = base.attr
if module == mod_import_name and klass == 'Test':
info = get_methods_info(statement.body,
cl_tags)
result[statement.name] = info
continue
return result, disabled
import os
import shutil
import stat
import sys
import multiprocessing
import tempfile
import unittest
......@@ -187,57 +186,6 @@ class Second(avocado.Test):
pass
"""
KEEP_METHODS_ORDER = '''
from avocado import Test
class MyClass(Test):
def test2(self):
pass
def testA(self):
pass
def test1(self):
pass
def testZZZ(self):
pass
def test(self):
pass
'''
RECURSIVE_DISCOVERY_TEST1 = """
from avocado import Test
class BaseClass(Test):
def test_basic(self):
pass
class FirstChild(BaseClass):
def test_first_child(self):
pass
class SecondChild(FirstChild):
'''
:avocado: disable
'''
def test_second_child(self):
pass
"""
RECURSIVE_DISCOVERY_TEST2 = """
from avocado import Test
from recursive_discovery_test1 import SecondChild
class ThirdChild(Test, SecondChild):
'''
:avocado: recursive
'''
def test_third_child(self):
pass
"""
PYTHON_UNITTEST = """#!/usr/bin/env python
from unittest import TestCase
......@@ -515,35 +463,6 @@ class LoaderTest(unittest.TestCase):
'does,not,exist'])
self.assertEqual(len(filtered), 0)
def test_methods_order(self):
avocado_keep_methods_order = script.TemporaryScript(
'keepmethodsorder.py',
KEEP_METHODS_ORDER)
avocado_keep_methods_order.save()
expected_order = ['test2', 'testA', 'test1', 'testZZZ', 'test']
tests = self.loader._find_avocado_tests(avocado_keep_methods_order.path)[0]
methods = [method[0] for method in tests['MyClass']]
self.assertEqual(expected_order, methods)
avocado_keep_methods_order.remove()
def test_recursive_discovery(self):
avocado_recursive_discovery_test1 = script.TemporaryScript(
'recursive_discovery_test1.py',
RECURSIVE_DISCOVERY_TEST1)
avocado_recursive_discovery_test1.save()
avocado_recursive_discovery_test2 = script.TemporaryScript(
'recursive_discovery_test2.py',
RECURSIVE_DISCOVERY_TEST2)
avocado_recursive_discovery_test2.save()
sys.path.append(os.path.dirname(avocado_recursive_discovery_test1.path))
tests = self.loader._find_avocado_tests(avocado_recursive_discovery_test2.path)[0]
expected = {'ThirdChild': [('test_third_child', set([])),
('test_second_child', set([])),
('test_first_child', set([])),
('test_basic', set([]))]}
self.assertEqual(expected, tests)
def test_python_unittest(self):
disabled_test = script.TemporaryScript("disabled.py",
AVOCADO_TEST_OK_DISABLED,
......
import ast
import sys
import os
import re
import unittest
......@@ -6,6 +8,58 @@ from avocado.core import safeloader
from avocado.utils import script
KEEP_METHODS_ORDER = '''
from avocado import Test
class MyClass(Test):
def test2(self):
pass
def testA(self):
pass
def test1(self):
pass
def testZZZ(self):
pass
def test(self):
pass
'''
RECURSIVE_DISCOVERY_TEST1 = """
from avocado import Test
class BaseClass(Test):
def test_basic(self):
pass
class FirstChild(BaseClass):
def test_first_child(self):
pass
class SecondChild(FirstChild):
'''
:avocado: disable
'''
def test_second_child(self):
pass
"""
RECURSIVE_DISCOVERY_TEST2 = """
from avocado import Test
from recursive_discovery_test1 import SecondChild
class ThirdChild(Test, SecondChild):
'''
:avocado: recursive
'''
def test_third_child(self):
pass
"""
def get_this_file():
this_file = __file__
if this_file.endswith('.py'):
......@@ -165,7 +219,9 @@ class FindClassAndMethods(UnlimitedDiff):
'FindClassAndMethods': ['test_self',
'test_with_pattern',
'test_with_base_class',
'test_with_pattern_and_base_class'],
'test_with_pattern_and_base_class',
'test_methods_order',
'test_recursive_discovery'],
'UnlimitedDiff': ['setUp']
}
found = safeloader.find_class_and_methods(get_this_file())
......@@ -187,7 +243,9 @@ class FindClassAndMethods(UnlimitedDiff):
'FindClassAndMethods': ['test_self',
'test_with_pattern',
'test_with_base_class',
'test_with_pattern_and_base_class'],
'test_with_pattern_and_base_class',
'test_methods_order',
'test_recursive_discovery'],
'UnlimitedDiff': []
}
found = safeloader.find_class_and_methods(get_this_file(),
......@@ -199,7 +257,9 @@ class FindClassAndMethods(UnlimitedDiff):
'FindClassAndMethods': ['test_self',
'test_with_pattern',
'test_with_base_class',
'test_with_pattern_and_base_class'],
'test_with_pattern_and_base_class',
'test_methods_order',
'test_recursive_discovery'],
}
found = safeloader.find_class_and_methods(get_this_file(),
base_class='UnlimitedDiff')
......@@ -209,13 +269,42 @@ class FindClassAndMethods(UnlimitedDiff):
reference = {
'FindClassAndMethods': ['test_with_pattern',
'test_with_base_class',
'test_with_pattern_and_base_class'],
'test_with_pattern_and_base_class']
}
found = safeloader.find_class_and_methods(get_this_file(),
re.compile(r'test_with.*'),
'UnlimitedDiff')
self.assertEqual(reference, found)
def test_methods_order(self):
avocado_keep_methods_order = script.TemporaryScript(
'keepmethodsorder.py',
KEEP_METHODS_ORDER)
avocado_keep_methods_order.save()
expected_order = ['test2', 'testA', 'test1', 'testZZZ', 'test']
tests = safeloader.find_avocado_tests(avocado_keep_methods_order.path)[0]
methods = [method[0] for method in tests['MyClass']]
self.assertEqual(expected_order, methods)
avocado_keep_methods_order.remove()
def test_recursive_discovery(self):
avocado_recursive_discovery_test1 = script.TemporaryScript(
'recursive_discovery_test1.py',
RECURSIVE_DISCOVERY_TEST1)
avocado_recursive_discovery_test1.save()
avocado_recursive_discovery_test2 = script.TemporaryScript(
'recursive_discovery_test2.py',
RECURSIVE_DISCOVERY_TEST2)
avocado_recursive_discovery_test2.save()
sys.path.append(os.path.dirname(avocado_recursive_discovery_test1.path))
tests = safeloader.find_avocado_tests(avocado_recursive_discovery_test2.path)[0]
expected = {'ThirdChild': [('test_third_child', set([])),
('test_second_child', set([])),
('test_first_child', set([])),
('test_basic', set([]))]}
self.assertEqual(expected, tests)
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册