a
    4ñ»j'<  ã                   @   sb   d Z ddlZdd„ Zdd„ ZG dd„ deƒZd	d
„ Zdd„ Zdd„ Zdd„ Z	dd„ Z
ddd„ZdS )a»
  
A **fixture** provides a concept to simplify test support functionality
that needs a setup/cleanup cycle per scenario, feature or test-run.
A fixture is provided as fixture-function that contains the setup part and
cleanup part similar to :func:`contextlib.contextmanager` or `pytest.fixture`_.

.. _pytest.fixture: https://docs.pytest.org/en/latest/fixture.html

A fixture is used when:

* the (registered) fixture tag is used for a scenario or feature
* the :func:`.use_fixture()` is called in the environment file (normally)

.. sourcecode:: python

    # -- FILE: behave4my_project/fixtures.py (or: features/environment.py)
    from behave import fixture
    from somewhere.browser.firefox import FirefoxBrowser

    @fixture
    def browser_firefox(context, timeout=30, **kwargs):
        # -- SETUP-FIXTURE PART:
        context.browser = FirefoxBrowser(timeout, *args, **kwargs)
        yield context.browser
        # -- CLEANUP-FIXTURE PART:
        context.browser.shutdown()

.. sourcecode:: gherkin

    # -- FILE: features/use_fixture.feature
    Feature: Use Fixture in Scenario

        @fixture.browser.firefox
        Scenario: Use browser=firefox
          Given I use the browser
          ...
        # -- AFTER-SCENEARIO: Cleanup fixture.browser.firefox

.. sourcecode:: python

    # -- FILE: features/environment.py
    from behave import use_fixture
    from behave4my_project.fixtures import browser_firefox

    def before_tag(context, tag):
        if tag == "fixture.browser.firefox":
            # -- Performs fixture setup and registers fixture cleanup
            use_fixture(browser_firefox, context, timeout=10)

.. hidden:

    BEHAVIORAL DECISIONS:

    * Should scenario/feature be executed when fixture-setup fails
      (similar to before-hook failures) ?
      NO, scope is skipped, but after-hooks and cleanups are executed.

    * Should remaining fixture-setups be performed after first fixture fails?
      NO, first setup-error aborts the setup and execution of the scope.

    * Should remaining fixture-cleanups be performed when first cleanup-error
      occurs?
      YES, try to perform all fixture-cleanups and then reraise the
      first cleanup-error.


    OPEN ISSUES:

    * AUTO_CALL_REGISTERED_FIXTURE (planned in future):
        Run fixture setup before or after before-hooks?

    IDEAS:

    * Fixture registers itself in fixture registry (runtime context).
    * Code in before_tag() will either be replaced w/ fixture processing function
      or will be automatically be executed (AUTO_CALL_REGISTERED_FIXTURE)
    * Support fixture tags w/ parameters that are automatically parsed and
      passed to fixture function, like:
      @fixture(name="foo", pattern="{name}={browser}")
é    Nc                 C   s    t | ddƒpttdƒot | ¡S )aÝ  Checks if a function is a coroutine-function, like:

     * ``async def f(): ...`` (since Python 3.5)
     * ``@asyncio.coroutine def f(): ...`` (since Python3)

    .. note:: Compatibility helper

        Avoids to import :mod:`asyncio` module directly (since Python3),
        which in turns initializes the :mod:`logging` module as side-effect.

    :param func:  Function to check.
    :return: True, if function is a coroutine function.
             False, otherwise.
    Z_is_coroutineFÚiscoroutinefunction)ÚgetattrÚhasattrÚinspectr   ©Úfunc© r   ú4E:\soccer\ml_env\Lib\site-packages\behave/fixture.pyr   Z   s    
þr   c                 C   s   t  | ¡}|ot| ƒ S )aç  Checks if a fixture function provides context-manager functionality,
    similar to :func`contextlib.contextmanager()` function decorator.

    .. code-block:: python

        @fixture
        def foo(context, *args, **kwargs):
            context.foo = setup_foo()
            yield context.foo
            cleanup_foo()

        @fixture
        def bar(context, *args, **kwargs):
            context.bar = setup_bar()
            return context.bar

        assert is_context_manager(foo) is True      # Generator-function
        assert is_context_manager(bar) is False     # Normal function

    :param func:    Function to check.
    :return: True, if function is a generator/context-manager function.
             False, otherwise.
    )r   Úisgeneratorfunctionr   )r   Zgenfuncr   r   r	   Úis_context_managerq   s    
r   c                   @   s   e Zd ZdZdS )ÚInvalidFixtureErrorz”Raised when a fixture is invalid.
    This occurs when a generator-function with more than one yield statement
    is used as fixture-function.
    N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r   r   r	   r   �   s   r   c                    sZ   t ˆ ƒr@‡ ‡fdd„}ˆ |g|¢R i |¤Ž‰| |¡ tˆƒ}nˆ |g|¢R i |¤Ž}|S )zcProvides core functionality to setup a fixture and registers its
    cleanup part (if needed).
    c                     s6   zt ˆƒ W n ty    Y dS 0 dˆ  } t| ƒ‚d S )NFzHas more than one yield: %r)ÚnextÚStopIterationr   )Úmessage©Úfixture_funcZfunc_itr   r	   Úcleanup_fixture    s    z'_setup_fixture.<locals>.cleanup_fixture)r   Zadd_cleanupr   )r   ÚcontextÚfixture_argsÚfixture_kwargsr   Zsetup_resultr   r   r	   Ú_setup_fixtureš   s    

r   c                 O   s   t | |g|¢R i |¤ŽS )aÏ  Use fixture (function) and call it to perform its setup-part.

    The fixture-function is similar to a :func:`contextlib.contextmanager`
    (and contains a yield-statement to seperate setup and cleanup part).
    If it contains a yield-statement, it registers a context-cleanup function
    to the context object to perform the fixture-cleanup at the end of the
    current scoped when the context layer is removed
    (and all context-cleanup functions are called).

    Therefore, fixture-cleanup is performed after scenario, feature or test-run
    (depending when its fixture-setup is performed).

    .. code-block:: python

        # -- FILE: behave4my_project/fixtures.py (or: features/environment.py)
        from behave import fixture
        from somewhere.browser import FirefoxBrowser

        @fixture(name="fixture.browser.firefox")
        def browser_firefox(context, *args, **kwargs):
            # -- SETUP-FIXTURE PART:
            context.browser = FirefoxBrowser(*args, **kwargs)
            yield context.browser
            # -- CLEANUP-FIXTURE PART:
            context.browser.shutdown()

    .. code-block:: python

        # -- FILE: features/environment.py
        from behave import use_fixture
        from behave4my_project.fixtures import browser_firefox

        def before_tag(context, tag):
            if tag == "fixture.browser.firefox":
                use_fixture(browser_firefox, context, timeout=10)


    :param fixture_func: Fixture function to use.
    :param context: Context object to use
    :param fixture_kwargs: Positional args, passed to the fixture function.
    :param fixture_kwargs: Additional kwargs, passed to the fixture function.
    :return: Setup result object (may be None).
    )r   )r   r   r   r   r   r   r	   Úuse_fixture»   s    ,r   c                 C   sŒ   |  | d¡}|du r td|  ƒ‚t|ƒr8|}t||ƒ nPt|ttfƒrxt|ƒdksVJ ‚|\}}}t||g|¢R i |¤ŽS d}t|| ƒ‚dS )a†  Process any fixture-tag to perform :func:`use_fixture()` for its fixture.
    If the fixture-tag is known, the fixture data is retrieved from the
    fixture registry.

    .. code-block:: python

        # -- FILE: features/environment.py
        from behave.fixture import use_fixture_by_tag
        from behave4my_project.fixtures import browser_firefox, browser_chrome

        # -- SCHEMA 1: fixture_func
        fixture_registry1 = {
            "fixture.browser.firefox": browser_firefox,
            "fixture.browser.chrome":  browser_chrome,
        }
        # -- SCHEMA 2: fixture_func, fixture_args, fixture_kwargs
        fixture_registry2 = {
            "fixture.browser.firefox": (browser_firefox, (), dict(timeout=10)),
            "fixture.browser.chrome":  (browser_chrome,  (), dict(timeout=12)),
        }

        def before_tag(context, tag):
            if tag.startswith("fixture."):
                return use_fixture_by_tag(tag, context, fixture_registry1):
            # -- MORE: Tag processing steps ...


    :param tag:     Fixture tag to process.
    :param context: Runtime context object, used for :func:`use_fixture()`.
    :param fixture_registry:  Registry maps fixture-tag to fixture data.
    :return: Fixture-setup result (same as: use_fixture())
    :raises LookupError: If fixture-tag/fixture is unknown.
    :raises ValueError: If fixture data type is not supported.
    NzUnknown fixture-tag: %sé   z8fixture_data: Expected tuple or fixture-func, but is: %r)	ÚgetÚLookupErrorÚcallabler   Ú
isinstanceÚtupleÚlistÚlenÚ
ValueError)Útagr   Zfixture_registryZfixture_datar   r   r   r   r   r   r	   Úuse_fixture_by_tagê   s    #
r&   c                 O   s
   | ||fS )Nr   )r   ÚargsÚkwargsr   r   r	   Úfixture_call_params  s    r)   c                 C   s:   g }|D ],\}}}t || g|¢R i |¤Ž}| |¡ q|S )a·  Helper function when complex fixtures should be created and
    safe-cleanup is needed even if an setup-fixture-error occurs.

    This function ensures that fixture-cleanup is performed
    for every fixture that was setup before the setup-error occured.

    .. code-block:: python

        # -- BAD-EXAMPLE: Simplistic composite-fixture
        # NOTE: Created fixtures (fixture1) are not cleaned up.
        @fixture
        def foo_and_bad0(context, *args, **kwargs):
            the_fixture1 = setup_fixture_foo(*args, **kwargs)
            the_fixture2 = setup_fixture_bar_with_error("OOPS-HERE")
            yield (the_fixture1, the_fixture2)  # NOT_REACHED.
            # -- NOT_REACHED: Due to fixture2-setup-error.
            the_fixture1.cleanup()  # NOT-CALLED (SAD).
            the_fixture2.cleanup()  # OOPS, the_fixture2 is None (if called).

    .. code-block:: python

        # -- GOOD-EXAMPLE: Sane composite-fixture
        # NOTE: Fixture foo.cleanup() occurs even after fixture2-setup-error.
        @fixture
        def foo(context, *args, **kwargs):
            the_fixture = setup_fixture_foo(*args, **kwargs)
            yield the_fixture
            cleanup_fixture_foo(the_fixture)

        @fixture
        def bad_with_setup_error(context, *args, **kwargs):
            raise RuntimeError("BAD-FIXTURE-SETUP")

        # -- SOLUTION 1: With use_fixture()
        @fixture
        def foo_and_bad1(context, *args, **kwargs):
            the_fixture1 = use_fixture(foo, context, *args, **kwargs)
            the_fixture2 = use_fixture(bad_with_setup_error, context, "OOPS")
            return (the_fixture1, the_fixture2) # NOT_REACHED

        # -- SOLUTION 2: With use_composite_fixture_with()
        @fixture
        def foo_and_bad2(context, *args, **kwargs):
            the_fixture = use_composite_fixture_with(context, [
                fixture_call_params(foo, *args, **kwargs),
                fixture_call_params(bad_with_setup_error, "OOPS")
             ])
            return the_fixture

    :param context:     Runtime context object, used for all fixtures.
    :param fixture_funcs_with_params: List of fixture functions with params.
    :return: List of created fixture objects.
    )r   Úappend)r   Zfixture_funcs_with_paramsZcomposite_fixturer   r'   r(   Zthe_fixturer   r   r	   Úuse_composite_fixture_with"  s
    6r+   c                    sT   ddd„‰ | du r&‡ ‡‡fdd„}|S t | ƒr<ˆ | ˆˆd�S d| ˆf }t|ƒ‚dS )	aÉ  Fixture decorator (currently mostly syntactic sugar).

    .. code-block:: python

        # -- FILE: features/environment.py
        # CASE FIXTURE-GENERATOR-FUNCTION (like @contextlib.contextmanager):
        @fixture
        def foo(context, *args, **kwargs):
            the_fixture = setup_fixture_foo(*args, **kwargs)
            context.foo = the_fixture
            yield the_fixture
            cleanup_fixture_foo(the_fixture)

        # CASE FIXTURE-FUNCTION: No cleanup or cleanup via context-cleanup.
        @fixture(name="fixture.bar")
        def bar(context, *args, **kwargs):
            the_fixture = setup_fixture_bar(*args, **kwargs)
            context.bar = the_fixture
            context.add_cleanup(cleanup_fixture_bar, the_fixture.cleanup)
            return the_fixture

    :param name:    Specifies the fixture tag name (as string).

    .. seealso::

        * :func:`contextlib.contextmanager` decorator
        * `@pytest.fixture`_
    Nc                 S   s   || _ || _d| _| S )NT)ÚnameÚpatternZbehave_fixture)r   r,   r-   r   r   r	   Úmark_as_fixture€  s    z fixture.<locals>.mark_as_fixturec                    s   ˆ | ˆˆd�S )N©r-   r   r   ©r.   r,   r-   r   r	   Ú	decorator‰  s    zfixture.<locals>.decoratorr/   zInvalid func: func=%r, name=%r)NN)r   Ú	TypeError)r   r,   r-   r1   r   r   r0   r	   Úfixturec  s    
r3   )NNN)r   r   r   r   ÚRuntimeErrorr   r   r   r&   r)   r+   r3   r   r   r   r	   Ú<module>   s   Q
!/3A