Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Random fails - ImportError: module '' not in sys.modules #796

Open
mzebrak opened this issue Jul 14, 2022 · 1 comment
Open

Random fails - ImportError: module '' not in sys.modules #796

mzebrak opened this issue Jul 14, 2022 · 1 comment

Comments

@mzebrak
Copy link

mzebrak commented Jul 14, 2022

I ran into random failing tests when using the tavern plugin. It looks like there is a problem with its dependencies. Probably pykwalify

Unfortunately, I have not found any dependence on which to deduce why this is happening. Re-launching tests causes them to success in most cases, but during CI it is a problematic bug

The exception is raised from importlib/_bootstrap.py:588:

 with _ModuleLockManager(name):
        if sys.modules.get(name) is not module:
            msg = 'module {!r} not in sys.modules'.format(name)
            raise ImportError(msg, name=name)

here is the entire message:

cls = <class '_pytest.runner.CallInfo'>
func = <function call_runtest_hook.<locals>.<lambda> at 0x7fbab93a98b0>
when = 'call'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)
    @classmethod
    def from_call(
        cls,
        func: "Callable[[], TResult]",
        when: "Literal['collect', 'setup', 'call', 'teardown']",
        reraise: Optional[
            Union[Type[BaseException], Tuple[Type[BaseException], ...]]
        ] = None,
    ) -> "CallInfo[TResult]":
        """Call func, wrapping the result in a CallInfo.
    
        :param func:
            The function to call. Called without arguments.
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: Optional[TResult] = func()
../../../../.tox/tavern/lib/python3.8/site-packages/_pytest/runner.py:338: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
>       lambda: ihook(item=item, **kwds), when=when, reraise=reraise
    )
../../../../.tox/tavern/lib/python3.8/site-packages/_pytest/runner.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <_HookCaller 'pytest_runtest_call'>, args = ()
kwargs = {'item': <YamlItem XYZ>}, argname = 'item', firstresult = False
    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError("hook calling supports only keyword arguments")
        assert not self.is_historic()
    
        # This is written to avoid expensive operations when not needed.
        if self.spec:
            for argname in self.spec.argnames:
                if argname not in kwargs:
                    notincall = tuple(set(self.spec.argnames) - kwargs.keys())
                    warnings.warn(
                        "Argument(s) {} which are declared in the hookspec "
                        "can not be found in this hook call".format(notincall),
                        stacklevel=2,
                    )
                    break
    
            firstresult = self.spec.opts.get("firstresult")
        else:
            firstresult = False
    
>       return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
../../../../.tox/tavern/lib/python3.8/site-packages/pluggy/_hooks.py:265: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <_pytest.config.PytestPluginManager object at 0x7fbad5789d90>
hook_name = 'pytest_runtest_call'
methods = [<HookImpl plugin_name='runner', plugin=<module '_pytest.runner' from '/builds/XYZ/.tox/tavern/lib/python3.8...est.threadexception' from '/builds/XYZ/.tox/tavern/lib/python3.8/site-packages/_pytest/threadexception.py'>>]
kwargs = {'item': <YamlItem XYZ>}, firstresult = False
    def _hookexec(self, hook_name, methods, kwargs, firstresult):
        # called from all hookcaller instances.
        # enable_tracing will set its own wrapping function at self._inner_hookexec
>       return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
../../../../.tox/tavern/lib/python3.8/site-packages/pluggy/_manager.py:80: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
hook_name = 'pytest_runtest_call'
hook_impls = [<HookImpl plugin_name='runner', plugin=<module '_pytest.runner' from '/builds/XYZ/.tox/tavern/lib/python3.8...est.threadexception' from '/builds/XYZ/.tox/tavern/lib/python3.8/site-packages/_pytest/threadexception.py'>>]
caller_kwargs = {'item': <YamlItem XYZ>}, firstresult = False
    def _multicall(hook_name, hook_impls, caller_kwargs, firstresult):
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from _HookCaller.__call__().
        """
        __tracebackhide__ = True
        results = []
        excinfo = None
        try:  # run impl and wrapper setup functions in a loop
            teardowns = []
            try:
                for hook_impl in reversed(hook_impls):
                    try:
                        args = [caller_kwargs[argname] for argname in hook_impl.argnames]
                    except KeyError:
                        for argname in hook_impl.argnames:
                            if argname not in caller_kwargs:
                                raise HookCallError(
                                    f"hook call must provide argument {argname!r}"
                                )
    
                    if hook_impl.hookwrapper:
                        try:
                            gen = hook_impl.function(*args)
                            next(gen)  # first yield
                            teardowns.append(gen)
                        except StopIteration:
                            _raise_wrapfail(gen, "did not yield")
                    else:
                        res = hook_impl.function(*args)
                        if res is not None:
                            results.append(res)
                            if firstresult:  # halt further impl calls
                                break
            except BaseException:
                excinfo = sys.exc_info()
        finally:
            if firstresult:  # first result hooks return a single value
                outcome = _Result(results[0] if results else None, excinfo)
            else:
                outcome = _Result(results, excinfo)
    
            # run all wrapper post-yield blocks
            for gen in reversed(teardowns):
                try:
                    gen.send(outcome)
                    _raise_wrapfail(gen, "has second yield")
                except StopIteration:
                    pass
    
>           return outcome.get_result()
../../../../.tox/tavern/lib/python3.8/site-packages/pluggy/_callers.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <pluggy._result._Result object at 0x7fbab9615a90>
    def get_result(self):
        """Get the result(s) for this hook call.
    
        If the hook was marked as a ``firstresult`` only a single value
        will be returned otherwise a list of results.
        """
        __tracebackhide__ = True
        if self._excinfo is None:
            return self._result
        else:
            ex = self._excinfo
>           raise ex[1].with_traceback(ex[2])
../../../../.tox/tavern/lib/python3.8/site-packages/pluggy/_result.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
hook_name = 'pytest_runtest_call'
hook_impls = [<HookImpl plugin_name='runner', plugin=<module '_pytest.runner' from '/builds/XYZ/.tox/tavern/lib/python3.8...est.threadexception' from '/builds/XYZ/.tox/tavern/lib/python3.8/site-packages/_pytest/threadexception.py'>>]
caller_kwargs = {'item': <YamlItem XYZ>}, firstresult = False
    def _multicall(hook_name, hook_impls, caller_kwargs, firstresult):
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from _HookCaller.__call__().
        """
        __tracebackhide__ = True
        results = []
        excinfo = None
        try:  # run impl and wrapper setup functions in a loop
            teardowns = []
            try:
                for hook_impl in reversed(hook_impls):
                    try:
                        args = [caller_kwargs[argname] for argname in hook_impl.argnames]
                    except KeyError:
                        for argname in hook_impl.argnames:
                            if argname not in caller_kwargs:
                                raise HookCallError(
                                    f"hook call must provide argument {argname!r}"
                                )
    
                    if hook_impl.hookwrapper:
                        try:
                            gen = hook_impl.function(*args)
                            next(gen)  # first yield
                            teardowns.append(gen)
                        except StopIteration:
                            _raise_wrapfail(gen, "did not yield")
                    else:
>                       res = hook_impl.function(*args)
../../../../.tox/tavern/lib/python3.8/site-packages/pluggy/_callers.py:39: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
item = <YamlItem XYZ>
    def pytest_runtest_call(item: Item) -> None:
        _update_current_test_var(item, "call")
        try:
            del sys.last_type
            del sys.last_value
            del sys.last_traceback
        except AttributeError:
            pass
        try:
            item.runtest()
        except Exception as e:
            # Store trace info to allow postmortem debugging
            sys.last_type = type(e)
            sys.last_value = e
            assert e.__traceback__ is not None
            # Skip *this* frame
            sys.last_traceback = e.__traceback__.tb_next
>           raise e
../../../../.tox/tavern/lib/python3.8/site-packages/_pytest/runner.py:174: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
item = <YamlItem XYZ>
    def pytest_runtest_call(item: Item) -> None:
        _update_current_test_var(item, "call")
        try:
            del sys.last_type
            del sys.last_value
            del sys.last_traceback
        except AttributeError:
            pass
        try:
>           item.runtest()
../../../../.tox/tavern/lib/python3.8/site-packages/_pytest/runner.py:166: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <YamlItem XYZ>
    def runtest(self):
        # Do a deep copy because this sometimes still retains things from previous tests(?)
        self.global_cfg = copy.deepcopy(load_global_cfg(self.config))
    
        self.global_cfg.setdefault("variables", {})
    
        load_plugins(self.global_cfg)
    
        self.global_cfg["tavern_internal"] = {"pytest_hook_caller": self.config.hook}
    
        # INTERNAL
        # NOTE - now that we can 'mark' tests, we could use pytest.mark.xfail
        # instead. This doesn't differentiate between an error in verification
        # and an error when running the test though.
        xfail = self.spec.get("_xfail", False)
    
        try:
            fixture_values = self._load_fixture_values()
            self.global_cfg["variables"].update(fixture_values)
    
            call_hook(
                self.global_cfg,
                "pytest_tavern_beta_before_every_test_run",
                test_dict=self.spec,
                variables=self.global_cfg["variables"],
            )
    
>           verify_tests(self.spec)
../../../../.tox/tavern/lib/python3.8/site-packages/tavern/testutils/pytesthook/item.py:184: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
test_spec = {'test_name': 'XYZ', 'marks': ['patterntest'], 'includes': [{'name': 'Common test values', 'description': 'Common...unction': 'validate_response:compare_response_with_pattern', 'extra_kwargs': {'ignore_tags': '<bridge community>'}}}}]}
with_plugins = True
    def verify_tests(test_spec, with_plugins=True):
        """Verify that a specific test block is correct
    
        Todo:
            Load schema file once. Requires some caching of the file
    
        Args:
            test_spec (dict): Test in dictionary form
    
        Raises:
            BadSchemaError: Schema did not match
        """
        here = os.path.dirname(os.path.abspath(__file__))
    
        schema_filename = os.path.join(here, "tests.schema.yaml")
        schema = load_schema_file(schema_filename, with_plugins)
    
>       verify_generic(test_spec, schema)
../../../../.tox/tavern/lib/python3.8/site-packages/tavern/schemas/files.py:152: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
to_verify = {'test_name': 'XYZ', 'marks': ['patterntest'], 'includes': [{'name': 'Common test values', 'description': 'Common...unction': 'validate_response:compare_response_with_pattern', 'extra_kwargs': {'ignore_tags': '<bridge community>'}}}}]}
schema = {'name': 'Test schema', 'desc': 'Matches test blocks', 'schema;any_request_json': {'func': 'validate_request_json', 't... 'map', 'mapping': {'username': {'type': 'str', 'required': True}, 'password': {'type': 'str', 'required': False}}}}}}}
    def verify_generic(to_verify, schema):
        """Verify a generic file against a given schema
    
        Args:
            to_verify (dict): Filename of source tests to check
            schema (dict): Schema to verify against
    
        Raises:
            BadSchemaError: Schema did not match
        """
        logger.debug("Verifying %s against %s", to_verify, schema)
    
        here = os.path.dirname(os.path.abspath(__file__))
        extension_module_filename = os.path.join(here, "extensions.py")
    
>       verifier = core.Core(
            source_data=to_verify,
            schema_data=schema,
            extensions=[extension_module_filename],
        )
../../../../.tox/tavern/lib/python3.8/site-packages/tavern/schemas/files.py:99: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <pykwalify.core.Core object at 0x7fbab94ec820>, source_file = None
schema_files = []
source_data = {'test_name': 'XYZ', 'marks': ['patterntest'], 'includes': [{'name': 'Common test values', 'description': 'Common...unction': 'validate_response:compare_response_with_pattern', 'extra_kwargs': {'ignore_tags': '<bridge community>'}}}}]}
schema_data = {'name': 'Test schema', 'desc': 'Matches test blocks', 'schema;any_request_json': {'func': 'validate_request_json', 't... 'map', 'mapping': {'username': {'type': 'str', 'required': True}, 'password': {'type': 'str', 'required': False}}}}}}}
extensions = ['/builds/XYZ/.tox/tavern/lib/python3.8/site-packages/tavern/schemas/extensions.py']
strict_rule_validation = False, fix_ruby_style_regex = False
allow_assertions = False, file_encoding = None, schema_file_obj = None
data_file_obj = None
    def __init__(self, source_file=None, schema_files=None, source_data=None, schema_data=None, extensions=None, strict_rule_validation=False,
                 fix_ruby_style_regex=False, allow_assertions=False, file_encoding=None, schema_file_obj=None, data_file_obj=None):
        """
        :param extensions:
            List of paths to python files that should be imported and available via 'func' keywork.
            This list of extensions can be set manually or they should be provided by the `--extension`
            flag from the cli. This list should not contain files specified by the `extensions` list keyword
            that can be defined at the top level of the schema.
        """
        if schema_files is None:
            schema_files = []
        if extensions is None:
            extensions = []
    
        log.debug(u"source_file: %s", source_file)
        log.debug(u"schema_file: %s", schema_files)
        log.debug(u"source_data: %s", source_data)
        log.debug(u"schema_data: %s", schema_data)
        log.debug(u"extension files: %s", extensions)
    
        self.source = None
        self.schema = None
        self.validation_errors = None
        self.validation_errors_exceptions = None
        self.root_rule = None
        self.extensions = extensions
        self.errors = []
        self.strict_rule_validation = strict_rule_validation
        self.fix_ruby_style_regex = fix_ruby_style_regex
        self.allow_assertions = allow_assertions
    
        # Patch in all the normal python types into the yaml load instance so we can use all the
        # internal python types in the yaml loading.
        yml.constructor.add_constructor('tag:yaml.org,2002:python/bool', Constructor.construct_yaml_bool)
        yml.constructor.add_constructor('tag:yaml.org,2002:python/complex', Constructor.construct_python_complex)
        yml.constructor.add_constructor('tag:yaml.org,2002:python/dict', Constructor.construct_yaml_map)
        yml.constructor.add_constructor('tag:yaml.org,2002:python/float', Constructor.construct_yaml_float)
        yml.constructor.add_constructor('tag:yaml.org,2002:python/int', Constructor.construct_yaml_int)
        yml.constructor.add_constructor('tag:yaml.org,2002:python/list', Constructor.construct_yaml_seq)
        yml.constructor.add_constructor('tag:yaml.org,2002:python/long', Constructor.construct_python_long)
        yml.constructor.add_constructor('tag:yaml.org,2002:python/none', Constructor.construct_yaml_null)
        yml.constructor.add_constructor('tag:yaml.org,2002:python/str', Constructor.construct_python_str)
        yml.constructor.add_constructor('tag:yaml.org,2002:python/tuple', Constructor.construct_python_tuple)
        yml.constructor.add_constructor('tag:yaml.org,2002:python/unicode', Constructor.construct_python_unicode)
    
        if data_file_obj:
            try:
                self.source = yml.load(data_file_obj.read())
            except Exception as e:
                raise CoreError("Unable to load data_file_obj input")
    
        if schema_file_obj:
            try:
                self.schema = yml.load(schema_file_obj.read())
            except Exception as e:
                raise CoreError("Unable to load schema_file_obj")
    
        if source_file is not None:
            if not os.path.exists(source_file):
                raise CoreError(u"Provided source_file do not exists on disk: {0}".format(source_file))
    
            with open(source_file, "r", encoding=file_encoding) as stream:
                if source_file.endswith(".json"):
                    self.source = json.load(stream)
                elif source_file.endswith(".yaml") or source_file.endswith('.yml'):
                    self.source = yml.load(stream)
                else:
                    raise CoreError(u"Unable to load source_file. Unknown file format of specified file path: {0}".format(source_file))
    
        if not isinstance(schema_files, list):
            raise CoreError(u"schema_files must be of list type")
    
        # Merge all schema files into one single file for easy parsing
        if len(schema_files) > 0:
            schema_data = {}
            for f in schema_files:
                if not os.path.exists(f):
                    raise CoreError(u"Provided source_file do not exists on disk : {0}".format(f))
    
                with open(f, "r", encoding=file_encoding) as stream:
                    if f.endswith(".json"):
                        data = json.load(stream)
                    elif f.endswith(".yaml") or f.endswith(".yml"):
                        data = yml.load(stream)
                        if not data:
                            raise CoreError(u"No data loaded from file : {0}".format(f))
                    else:
                        raise CoreError(u"Unable to load file : {0} : Unknown file format. Supported file endings is [.json, .yaml, .yml]")
    
                    for key in data.keys():
                        if key in schema_data.keys():
                            raise CoreError(u"Parsed key : {0} : two times in schema files...".format(key))
    
                    schema_data = dict(schema_data, **data)
    
            self.schema = schema_data
    
        # Nothing was loaded so try the source_data variable
        if self.source is None:
            log.debug(u"No source file loaded, trying source data variable")
            self.source = source_data
        if self.schema is None:
            log.debug(u"No schema file loaded, trying schema data variable")
            self.schema = schema_data
    
        # Test if anything was loaded
        if self.source is None:
            raise CoreError(u"No source file/data was loaded")
        if self.schema is None:
            raise CoreError(u"No schema file/data was loaded")
    
        # Merge any extensions defined in the schema with the provided list of extensions from the cli
        for f in self.schema.get('extensions', []):
            self.extensions.append(f)
    
        if not isinstance(self.extensions, list) and all(isinstance(e, str) for e in self.extensions):
            raise CoreError(u"Specified extensions must be a list of file paths")
    
>       self._load_extensions()
../../../../.tox/tavern/lib/python3.8/site-packages/pykwalify/core.py:153: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <pykwalify.core.Core object at 0x7fbab94ec820>
    def _load_extensions(self):
        """
        Load all extension files into the namespace pykwalify.ext
        """
        log.debug(u"loading all extensions : %s", self.extensions)
    
        self.loaded_extensions = []
    
        for f in self.extensions:
            if not os.path.isabs(f):
                f = os.path.abspath(f)
    
            if not os.path.exists(f):
                raise CoreError(u"Extension file: {0} not found on disk".format(f))
    
>           self.loaded_extensions.append(SourceFileLoader("", f).load_module())
../../../../.tox/tavern/lib/python3.8/site-packages/pykwalify/core.py:173: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <_frozen_importlib_external.SourceFileLoader object at 0x7fbab94ecb80>
name = '', args = (), kwargs = {}
>   ???
<frozen importlib._bootstrap_external>:522: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <_frozen_importlib_external.SourceFileLoader object at 0x7fbab94ecb80>
fullname = ''
>   ???
<frozen importlib._bootstrap_external>:1022: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <_frozen_importlib_external.SourceFileLoader object at 0x7fbab94ecb80>
fullname = ''
>   ???
<frozen importlib._bootstrap_external>:847: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <_frozen_importlib_external.SourceFileLoader object at 0x7fbab94ecb80>
fullname = ''
>   ???
<frozen importlib._bootstrap>:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
spec = ModuleSpec(name='', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7fbab94ecb80>, origin='/builds/XYZ/.tox/tavern/lib/python3.8/site-packages/tavern/schemas/extensions.py')
module = <module '' from '/builds/XYZ/.tox/tavern/lib/python3.8/site-packages/tavern/schemas/extensions.py'>
>   ???
E   ImportError: module '' not in sys.modules
<frozen importlib._bootstrap>:589: ImportError
------ generated xml file: /builds/XYZ/api_smoketest_bridge.xml ------
=========================== short test summary info ============================
FAILED patterns/get_community/1.tavern.yaml::XYZ - ...
======================== 1 failed, 455 passed in 30.33s ========================
@michaelboulton
Copy link
Member

That's a weird error, it looks like it may be some kind of race condition?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants