Example #1
0
    def get_execution_monitor(self, pytest_node, args, kwargs):
        """
        Returns the StepsMonitor in charge of monitoring execution of the provided pytest node. The same StepsMonitor
        will be used to execute all steps of the generator function.

        If there is no monitor yet (first function call with this combination of parameters), then one is created,
        that will be used subsequently.

        :param pytest_node:
        :param args:
        :param kwargs:
        :return:
        """
        # Get the unique id that is shared between the steps of the same execution, by removing the step parameter
        # Note: when the id was using not only param values but also fixture values we had to discard
        # 'request' and maybe some fixtures here. But that's not the case anymore,simply discard the "test step" param
        id_without_steps = get_pytest_node_hash_id(
            pytest_node, params_to_ignore=(GENERATOR_MODE_STEP_ARGNAME, ))

        if id_without_steps not in self:
            # First time we call the function with this combination of parameters
            # print("DEBUG - creating StepsMonitor for %s" % id_without_steps)

            # create the monitor, in charge of managing the execution flow
            self[id_without_steps] = StepsMonitor(self.step_ids,
                                                  self.test_func, args, kwargs)

        return self[id_without_steps]
Example #2
0
            def wrapped_test_function(*args, **kwargs):
                """Executes the current step only if its dependencies are correct, and registers its execution result"""
                request = kwargs[
                    'request'] if func_needs_request else kwargs.pop('request')
                if request is None:
                    # manual call (maybe for pre-loading?), no dependency management, ability to execute several steps
                    _execute_manually(test_func, s, test_step_argname,
                                      step_ids, steps, args, kwargs)
                else:
                    # (a) retrieve the "current step" function
                    current_step_fun = get_fixture_or_param_value(
                        request, test_step_argname)

                    # Get the unique id that is shared between the steps of the same execution
                    # Note: when the id was using not only param values but also fixture values we had to discard
                    # steps_data_holder_name and 'request'. But that's not the case anymore, simply discard "test step"
                    test_id_without_steps = get_pytest_node_hash_id(
                        request.node, params_to_ignore={test_step_argname})

                    # Make sure that it has a field to store its execution success
                    if not hasattr(current_step_fun, STEP_SUCCESS_FIELD):
                        # this is a dict where the key is the `test_id_without_steps` and the value is a boolean
                        setattr(current_step_fun, STEP_SUCCESS_FIELD, dict())

                    # (b) skip or fail it if needed
                    dependencies, should_fail = getattr(
                        current_step_fun, DEPENDS_ON_FIELD, ([], False))
                    # -- check that dependencies have all run (execution order is correct)
                    if not all(
                            hasattr(step, STEP_SUCCESS_FIELD)
                            for step in dependencies):
                        raise ValueError(
                            "Test step {} depends on another step that has not yet been executed. In "
                            "current version the steps execution order is manual, make sure it is correct."
                            "".format(current_step_fun.__name__))
                    # -- check that dependencies all ran with success
                    deps_successess = {
                        step: getattr(step, STEP_SUCCESS_FIELD).get(
                            test_id_without_steps, False)
                        for step in dependencies
                    }
                    failed_deps = [
                        d.__name__ for d, res in deps_successess.items()
                        if res is False
                    ]
                    if not all(deps_successess.values()):
                        msg = "This test step depends on other steps, and the following have failed: %s" % failed_deps
                        if should_fail:
                            pytest.fail(msg)
                        else:
                            pytest.skip(msg)

                    # (c) execute the test function for this step
                    res = test_func(*args, **kwargs)

                    # (d) declare execution as a success
                    getattr(current_step_fun,
                            STEP_SUCCESS_FIELD)[test_id_without_steps] = True

                    return res
def results(request):
    """
    The fixture for the StepsDataHolder
    :param request:
    :return:
    """
    test_id = get_pytest_node_hash_id(request.node, params_to_ignore={'test_step'})

    return get_results_holder(id=test_id)
Example #4
0
 def _init_and_check(request):
     """
     Checks that the current request is not session but a specific node.
     :param request:
     :return:
     """
     scope = get_scope(request)
     if scope == 'function':
         # function-scope: ok
         id_without_steps = get_pytest_node_hash_id(
             request.node,
             params_to_ignore=_get_step_param_names_or_default(
                 step_param_names))
         return id_without_steps
     else:
         # session- or module-scope
         raise Exception(
             "The `@cross_steps_fixture` decorator is only useful for function-scope fixtures. `%s`"
             " seems to have scope='%s'. Consider removing `@cross_steps_fixture` or changing "
             "the scope to 'function'." % (fixture_fun, scope))
Example #5
0
            def results(request):
                """
                The fixture for the StepsDataHolder.

                It is function-scoped (so oit is called for each step of each param combination)
                but it implements an intelligent cache so that the same StepsDataHolder object is returned across all
                test steps belonging to the same param combination.

                :param request:
                :return:
                """
                # Get a good unique identifier of the test.
                # The id should be different everytime anything changes, except when the test step changes
                # Note: when the id was using not only param values but also fixture values we had to discard
                # steps_data_holder_name and 'request'. But that's not the case anymore,simply discard "test step" param
                test_id = get_pytest_node_hash_id(
                    request.node, params_to_ignore={test_step_argname})

                # Get or create the cached Result holder for this combination of parameters
                return get_results_holder(id=test_id)