platform linux -- Python 3.9.0, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /usr/local/bin/python cachedir: .pytest_cache metadata: {'Python': '3.9.0', 'Platform': 'Linux-4.19.128-microsoft-standard-x86_64-with', 'Packages': {'pytest': '6.2.5', 'py': '1.11.0', 'pluggy': '1.0.0'}, 'Plugins': {'metadata': '1.11.0', 'tavern': '1.17.0', 'html': '3.1.1'}} rootdir: /tavern_tests plugins: metadata-1.11.0, tavern-1.17.0, html-3.1.1 ___________________________________ /tavern_tests/test_tdcp_display_field.tavern.yaml::Multiple fields ____________________________________ cls = , func = . at 0x7fd370c021f0>, when = 'call' reraise = (, ) @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]": excinfo = None start = timing.time() precise_start = timing.perf_counter() try: > result: Optional[TResult] = func() usr/local/lib/python3.9/site-packages/_pytest/runner.py:311: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ > lambda: ihook(item=item, **kwds), when=when, reraise=reraise ) usr/local/lib/python3.9/site-packages/_pytest/runner.py:255: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <_HookCaller 'pytest_runtest_call'>, args = (), kwargs = {'item': }, 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) usr/local/lib/python3.9/site-packages/pluggy/_hooks.py:265: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <_pytest.config.PytestPluginManager object at 0x7fd3727cfa00>, hook_name = 'pytest_runtest_call' methods = [>] kwargs = {'item': }, 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) usr/local/lib/python3.9/site-packages/pluggy/_manager.py:80: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ hook_name = 'pytest_runtest_call' hook_impls = [>] caller_kwargs = {'item': }, 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() usr/local/lib/python3.9/site-packages/pluggy/_callers.py:60: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = 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]) usr/local/lib/python3.9/site-packages/pluggy/_result.py:60: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ hook_name = 'pytest_runtest_call' hook_impls = [>] caller_kwargs = {'item': }, 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) usr/local/lib/python3.9/site-packages/pluggy/_callers.py:39: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ item = 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 usr/local/lib/python3.9/site-packages/_pytest/runner.py:170: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ item = 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() usr/local/lib/python3.9/site-packages/_pytest/runner.py:162: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = 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) for stage in self.spec["stages"]: if not stage.get("name"): if not stage.get("id"): # Should never actually reach here, should be caught at schema check time raise exceptions.BadSchemaError( "One of name or ID must be specified" ) stage["name"] = stage["id"] > run_test(self.path, self.spec, self.global_cfg) usr/local/lib/python3.9/site-packages/tavern/testutils/pytesthook/item.py:196: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ in_file = local('/tavern_tests/test_tdcp_display_field.tavern.yaml') test_spec = {'test_name': 'Multiple fields', 'strict': ['json:off'], 'includes': [{'name': 'Common test information', 'description...: 'Status 1 Action', 'line3_text': '{Explanation 1 ', 'line4_text': 'S#2 S#3 Status#4 '}, 'timeout': 1}}]} global_cfg = {'backends': {'http': 'requests', 'mqtt': 'paho-mqtt'}, 'follow_redirects': False, 'merge_ext_values': False, 'strict'...NSET: 3>), redirect_query_params=StrictOption(section='redirect_query_params', setting=)), ...} def run_test(in_file, test_spec, global_cfg): """Run a single tavern test Note that each tavern test can consist of multiple requests (log in, create, update, delete, etc). The global configuration is copied and used as an initial configuration for this test. Any values which are saved from any tests are saved into this test block and can be used for formatting in later stages in the test. Args: in_file (str): filename containing this test test_spec (dict): The specification for this test global_cfg (dict): Any global configuration for this test No Longer Raises: TavernException: If any of the tests failed """ # pylint: disable=too-many-locals # Initialise test config for this test with the global configuration before # starting test_block_config = dict(global_cfg) default_global_stricness = global_cfg["strict"] if "variables" not in test_block_config: test_block_config["variables"] = {} tavern_box = get_tavern_box() if not test_spec: logger.warning("Empty test block in %s", in_file) return # Get included stages and resolve any into the test spec dictionary available_stages = test_block_config.get("stages", []) included_stages = _get_included_stages( tavern_box, test_block_config, test_spec, available_stages ) all_stages = {s["id"]: s for s in available_stages + included_stages} test_spec["stages"] = _resolve_test_stages(test_spec, all_stages) test_block_config["variables"]["tavern"] = tavern_box["tavern"] test_block_name = test_spec["test_name"] logger.info("Running test : %s", test_block_name) with ExitStack() as stack: sessions = get_extra_sessions(test_spec, test_block_config) for name, session in sessions.items(): logger.debug("Entering context for %s", name) stack.enter_context(session) def getonly(stage): o = stage.get("only") if o is None: return False elif isinstance(o, bool): return o else: return strtobool(o) has_only = any(getonly(stage) for stage in test_spec["stages"]) # Run tests in a path in order for idx, stage in enumerate(test_spec["stages"]): if stage.get("skip"): continue if has_only and not getonly(stage): continue test_block_config["strict"] = default_global_stricness _calculate_stage_strictness(stage, test_block_config, test_spec) # Wrap run_stage with retry helper run_stage_with_retries = retry(stage, test_block_config)(run_stage) partial = functools.partial( run_stage_with_retries, sessions, stage, test_block_config ) allure_name = "Stage {}: {}".format(idx, stage["name"]) step = wrap_step(allure_name, partial) try: > step() usr/local/lib/python3.9/site-packages/tavern/core.py:189: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ args = ({'paho-mqtt': }, {'name': 'Step 2 - Verify display',...SET: 3>), redirect_query_params=StrictOption(section='redirect_query_params', setting=)), ...}) kwargs = {} @wraps(fn) def wrapped(*args, **kwargs): > res = fn(*args, **kwargs) usr/local/lib/python3.9/site-packages/tavern/util/retry.py:31: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ sessions = {'paho-mqtt': } stage = {'name': 'Step 2 - Verify display', 'mqtt_publish': {'topic': '{dcpsim_information_request_topic}', 'json': {'request'...t': 'Status 1 Action', 'line3_text': '{Explanation 1 ', 'line4_text': 'S#2 S#3 Status#4 '}, 'timeout': 1}} test_block_config = {'backends': {'http': 'requests', 'mqtt': 'paho-mqtt'}, 'follow_redirects': False, 'merge_ext_values': False, 'strict'...NSET: 3>), redirect_query_params=StrictOption(section='redirect_query_params', setting=)), ...} def run_stage(sessions, stage, test_block_config): """Run one stage from the test Args: sessions (dict): Dictionary of relevant 'session' objects used for this test stage (dict): specification of stage to be run test_block_config (dict): available variables for test """ stage = copy.deepcopy(stage) name = stage["name"] attach_stage_content(stage) r = get_request_type(stage, test_block_config, sessions) tavern_box = test_block_config["variables"]["tavern"] tavern_box.update(request_vars=r.request_vars) > expected = get_expected(stage, test_block_config, sessions) usr/local/lib/python3.9/site-packages/tavern/core.py:252: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ stage = {'name': 'Step 2 - Verify display', 'mqtt_publish': {'topic': '{dcpsim_information_request_topic}', 'json': {'request'...t': 'Status 1 Action', 'line3_text': '{Explanation 1 ', 'line4_text': 'S#2 S#3 Status#4 '}, 'timeout': 1}} test_block_config = {'backends': {'http': 'requests', 'mqtt': 'paho-mqtt'}, 'follow_redirects': False, 'merge_ext_values': False, 'strict'...NSET: 3>), redirect_query_params=StrictOption(section='redirect_query_params', setting=)), ...} sessions = {'paho-mqtt': } def get_expected(stage, test_block_config, sessions): """Get expected responses for each type of request Though only 1 request can be made, it can cause multiple responses. Because we need to subcribe to MQTT topics, which might be formatted from keys from included files, the 'expected'/'response' needs to be formatted BEFORE running the request. Args: stage (dict): test stage test_block_config (dict): available configuration for this test sessions (dict): all available sessions Returns: dict: mapping of request type: expected response dict """ plugins = load_plugins(test_block_config) expected = {} for p in plugins: if p.plugin.response_block_name in stage: logger.debug("Getting expected response for %s", p.name) > plugin_expected = p.plugin.get_expected_from_request( stage, test_block_config, sessions[p.name] ) usr/local/lib/python3.9/site-packages/tavern/plugins.py:243: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ stage = {'name': 'Step 2 - Verify display', 'mqtt_publish': {'topic': '{dcpsim_information_request_topic}', 'json': {'request'...t': 'Status 1 Action', 'line3_text': '{Explanation 1 ', 'line4_text': 'S#2 S#3 Status#4 '}, 'timeout': 1}} test_block_config = {'backends': {'http': 'requests', 'mqtt': 'paho-mqtt'}, 'follow_redirects': False, 'merge_ext_values': False, 'strict'...NSET: 3>), redirect_query_params=StrictOption(section='redirect_query_params', setting=)), ...} session = def get_expected_from_request(stage, test_block_config, session): # mqtt response is not required m_expected = stage.get("mqtt_response") if m_expected: # format so we can subscribe to the right topic > f_expected = format_keys(m_expected, test_block_config["variables"]) usr/local/lib/python3.9/site-packages/tavern/_plugins/mqtt/tavernhook.py:26: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ val = {'topic': '{dcpsim_current_display_text_topic}', 'json': {'line1_text': 'Registered Alias', 'line2_text': 'Status 1 Action', 'line3_text': '{Explanation 1 ', 'line4_text': 'S#2 S#3 Status#4 '}, 'timeout': 1} variables = {'acceptKey': 26, 'audio_call_status_update_topic': 'audio/call_status_update', 'audio_connection_topic': 'audio/target_audio_connection', 'audio_file': 'configuration/keyclick.wav', ...} no_double_format = True def format_keys(val, variables, no_double_format=True): """recursively format a dictionary with the given values Args: val (object): Input dictionary to format variables (dict): Dictionary of keys to format it with no_double_format (bool): Whether to use the 'inner formatted string' class to avoid double formatting This is required if passing something via pytest-xdist, such as markers: https://github.com/taverntesting/tavern/issues/431 Returns: str,int,list,dict: recursively formatted values """ formatted = val if not isinstance(variables, Box): box_vars = Box(variables) else: box_vars = variables if isinstance(val, dict): formatted = {} # formatted = {key: format_keys(val[key], box_vars) for key in val} for key in val: > formatted[key] = format_keys(val[key], box_vars) usr/local/lib/python3.9/site-packages/tavern/util/dict_util.py:116: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ val = {'line1_text': 'Registered Alias', 'line2_text': 'Status 1 Action', 'line3_text': '{Explanation 1 ', 'line4_text': 'S#2 S#3 Status#4 '} variables = no_double_format = True def format_keys(val, variables, no_double_format=True): """recursively format a dictionary with the given values Args: val (object): Input dictionary to format variables (dict): Dictionary of keys to format it with no_double_format (bool): Whether to use the 'inner formatted string' class to avoid double formatting This is required if passing something via pytest-xdist, such as markers: https://github.com/taverntesting/tavern/issues/431 Returns: str,int,list,dict: recursively formatted values """ formatted = val if not isinstance(variables, Box): box_vars = Box(variables) else: box_vars = variables if isinstance(val, dict): formatted = {} # formatted = {key: format_keys(val[key], box_vars) for key in val} for key in val: > formatted[key] = format_keys(val[key], box_vars) usr/local/lib/python3.9/site-packages/tavern/util/dict_util.py:116: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ val = '{Explanation 1 ' variables = no_double_format = True def format_keys(val, variables, no_double_format=True): """recursively format a dictionary with the given values Args: val (object): Input dictionary to format variables (dict): Dictionary of keys to format it with no_double_format (bool): Whether to use the 'inner formatted string' class to avoid double formatting This is required if passing something via pytest-xdist, such as markers: https://github.com/taverntesting/tavern/issues/431 Returns: str,int,list,dict: recursively formatted values """ formatted = val if not isinstance(variables, Box): box_vars = Box(variables) else: box_vars = variables if isinstance(val, dict): formatted = {} # formatted = {key: format_keys(val[key], box_vars) for key in val} for key in val: formatted[key] = format_keys(val[key], box_vars) elif isinstance(val, (list, tuple)): formatted = [format_keys(item, box_vars) for item in val] elif isinstance(formatted, FormattedString): logger.debug("Already formatted %s, not double-formatting", formatted) elif isinstance(val, str): > formatted = _check_and_format_values(val, box_vars) usr/local/lib/python3.9/site-packages/tavern/util/dict_util.py:122: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ to_format = '{Explanation 1 ' box_vars = def _check_and_format_values(to_format, box_vars): formatter = string.Formatter() would_format = formatter.parse(to_format) > for (_, field_name, _, _) in would_format: E ValueError: expected '}' before end of string usr/local/lib/python3.9/site-packages/tavern/util/dict_util.py:29: ValueError