def get_methods_info(statement_body, class_tags, class_requirements): """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_requirements: the requirements at the class level, to be combined with the requirements 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_requirements = get_docstring_directives_requirements(docstring) mt_requirements.extend(class_requirements) methods = [method for method, _, _ in methods_info] if st.name not in methods: methods_info.append((st.name, mt_tags, mt_requirements)) return methods_info
def test_tag_keyval_duplicate(self): raw = ":avocado: tags=fast,arch:x86_64,arch:ppc64,arch:x86_64" exp = {"fast": None, "arch": set(["x86_64", "ppc64"])} self.assertEqual(get_docstring_directives_tags(raw), exp)
def test_tag_newline_after(self): raw = ":avocado: tags=fast,slow\n:avocado: enable" exp = {"fast": None, "slow": None} self.assertEqual(get_docstring_directives_tags(raw), exp)
def test_tag_newline_before(self): raw = ":avocado: enable\n:avocado: tags=fast" exp = {"fast": None} self.assertEqual(get_docstring_directives_tags(raw), exp)
def test_tag_empty(self): raw = ":avocado: tags=" exp = {} self.assertEqual(get_docstring_directives_tags(raw), exp)
def test_tag_tab_separator(self): raw = ":avocado:\ttags=FAST" exp = {"FAST": None} self.assertEqual(get_docstring_directives_tags(raw), exp)
def test_tag_duplicate(self): raw = ":avocado: tags=SLOW,disk,disk" exp = {"SLOW": None, "disk": None} self.assertEqual(get_docstring_directives_tags(raw), exp)
def test_tag_lowercase_uppercase(self): raw = ":avocado: tags=slow,DISK" exp = {"slow": None, "DISK": None} self.assertEqual(get_docstring_directives_tags(raw), exp)
def test_tag_double_with_empty(self): raw = ":avocado: tags=fast,,network" exp = {"fast": None, "network": None} self.assertEqual(get_docstring_directives_tags(raw), exp)
def test_tag_single(self): raw = ":avocado: tags=fast" exp = {"fast": None} self.assertEqual(get_docstring_directives_tags(raw), exp)
def test_get_tags_empty(self): for tag in self.NO_TAGS: self.assertEqual({}, get_docstring_directives_tags(tag))
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_requirements( 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_requirements( 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_requirements( 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