def get_methods_info(statement_body, class_tags, class_dependencies): """Returns information on test methods. :param statement_body: the body of a "class" statement :param class_tags: the tags at the class level, to be combined with the tags at the method level. :param class_dependencies: the dependencies at the class level, to be combined with the dependencies at the method level. """ methods_info = [] for st in statement_body: if not (isinstance(st, (ast.FunctionDef, ast.AsyncFunctionDef))): continue decorators = getattr(st, "decorator_list", []) decorator_names = [getattr(_, "id", None) for _ in decorators] if "property" in decorator_names: continue if not st.name.startswith("test"): continue docstring = ast.get_docstring(st) mt_tags = get_docstring_directives_tags(docstring) mt_tags.update(class_tags) mt_dependencies = get_docstring_directives_dependencies(docstring) mt_dependencies.extend(class_dependencies) methods = [method for method, _, _ in methods_info] if st.name not in methods: methods_info.append((st.name, mt_tags, mt_dependencies)) return methods_info
def test_dependency_double(self): raw = ':avocado: dependency={"foo":"bar"}\n:avocado: dependency={"uri":"http://foo.bar"}' exp = [{"foo": "bar"}, {"uri": "http://foo.bar"}] self.assertEqual(get_docstring_directives_dependencies(raw), exp)
def test_dependency_single(self): raw = ':avocado: dependency={"foo":"bar"}' exp = [{"foo": "bar"}] self.assertEqual(get_docstring_directives_dependencies(raw), exp)
def test_get_dependency_empty(self): for dep in self.NO_REQS: self.assertEqual([], get_docstring_directives_dependencies(dep))
def find_python_tests(target_module, target_class, determine_match, path): """ Attempts to find Python tests from source files A Python test in this context is a method within a specific type of class (or that inherits from a specific class). :param target_module: the name of the module from which a class should have come from. When attempting to find a Python unittest, the target_module will most probably be "unittest", as per the standard library module name. When attempting to find Avocado tests, the target_module will most probably be "avocado". :type target_module: str :param target_class: the name of the class that is considered to contain test methods. When attempting to find Python unittests, the target_class will most probably be "TestCase". When attempting to find Avocado tests, the target_class will most probably be "Test". :type target_class: str :type determine_match: a callable that will determine if a given module and class is contains valid Python tests :type determine_match: function :param path: path to a Python source code file :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 Python tests but have been forcefully disabled. :rtype: tuple """ module = PythonModule(path, target_module, target_class) # The resulting test classes result = collections.OrderedDict() disabled = set() for klass in module.iter_classes(): docstring = ast.get_docstring(klass) # Looking for a class that has in the docstring either # ":avocado: enable" or ":avocado: disable if check_docstring_directive(docstring, "disable"): disabled.add(klass.name) continue if check_docstring_directive(docstring, "enable"): info = get_methods_info( klass.body, get_docstring_directives_tags(docstring), get_docstring_directives_dependencies(docstring), ) result[klass.name] = info continue # From this point onwards we want to do recursive discovery, but # for now we don't know whether it is avocado.Test inherited # (Ifs are optimized for readability, not speed) # If "recursive" tag is specified, it is forced as test if check_docstring_directive(docstring, "recursive"): match = True else: match = module.is_matching_klass(klass) info = get_methods_info( klass.body, get_docstring_directives_tags(docstring), get_docstring_directives_dependencies(docstring), ) # Getting the list of parents of the current class parents = klass.bases match = _examine_same_module( parents, info, disabled, match, module, target_module, target_class, determine_match, ) # If there are parents left to be discovered, they # might be in a different module. for parent in parents: try: ( parent_class, imported_symbol, symbol_is_module, ) = _get_attributes_for_further_examination(parent, module) found_spec = imported_symbol.get_importable_spec( symbol_is_module) if found_spec is None: continue except ClassNotSuitable: continue _info, _dis, _match = _examine_class( target_module, target_class, determine_match, found_spec.origin, parent_class, match, ) if _info: info.extend(_info) disabled.update(_dis) if _match is not match: match = _match # Only update the results if this was detected as 'avocado.Test' if match: result[klass.name] = info return result, disabled
def _examine_class(target_module, target_class, determine_match, path, class_name, match): """ Examine a class from a given path :param target_module: the name of the module from which a class should have come from. When attempting to find a Python unittest, the target_module will most probably be "unittest", as per the standard library module name. When attempting to find Avocado tests, the target_module will most probably be "avocado". :type target_module: str :param target_class: the name of the class that is considered to contain test methods. When attempting to find Python unittests, the target_class will most probably be "TestCase". When attempting to find Avocado tests, the target_class will most probably be "Test". :type target_class: str :param determine_match: a callable that will determine if a match has occurred or not :type determine_match: function :param path: path to a Python source code file :type path: str :param class_name: the specific class to be found :type path: str :param match: whether the inheritance from <target_module.target_class> has been determined or not :type match: bool :returns: tuple where first item is a list of test methods detected for given class; second item is set of class names which look like avocado tests but are force-disabled. :rtype: tuple """ module = PythonModule(path, target_module, target_class) info = [] disabled = set() for klass in module.iter_classes(class_name): if class_name != klass.name: continue docstring = ast.get_docstring(klass) if match is False: match = module.is_matching_klass(klass) info = get_methods_info( klass.body, get_docstring_directives_tags(docstring), get_docstring_directives_dependencies(docstring), ) # Getting the list of parents of the current class parents = klass.bases match = _examine_same_module( parents, info, disabled, match, module, target_module, target_class, determine_match, ) # If there are parents left to be discovered, they # might be in a different module. for parent in parents: try: ( parent_class, imported_symbol, symbol_is_module, ) = _get_attributes_for_further_examination(parent, module) found_spec = imported_symbol.get_importable_spec( symbol_is_module) if found_spec is None: continue except ClassNotSuitable: continue _info, _disabled, _match = _examine_class( target_module, target_class, determine_match, found_spec.origin, parent_class, match, ) if _info: _extend_test_list(info, _info) disabled.update(_disabled) if _match is not match: match = _match if not match and module.interesting_klass_found: imported_symbol = module.imported_symbols[class_name] if imported_symbol: found_spec = imported_symbol.get_importable_spec() if found_spec: _info, _disabled, _match = _examine_class( target_module, target_class, determine_match, found_spec.origin, class_name, match, ) if _info: _extend_test_list(info, _info) disabled.update(_disabled) if _match is not match: match = _match return info, disabled, match