#!/usr/bin/env python3
# --------------------( LICENSE                            )--------------------
# Copyright (c) 2014-2024 Beartype authors.
# See "LICENSE" for further details.

'''
**Beartype decorator noop** unit tests.

This submodule unit tests edge cases of the :func:`beartype.beartype` decorator
efficiently reducing to a noop.
'''

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# WARNING: To raise human-readable test errors, avoid importing from
# package-specific submodules at module scope.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

# ....................{ TESTS ~ sync                       }....................
def test_decor_noop_unhinted_sync() -> None:
    '''
    Test that the :func:`beartype.beartype` decorator efficiently reduces to a
    noop on **unannotated synchronous callables** (i.e., callables with *no*
    annotations declared with the ``def`` rather than ``async def`` keyword).
    '''

    # Defer test-specific imports.
    from beartype import beartype

    # Undecorated unannotated function.
    def khorne(gork, mork):
        return gork + mork

    # Decorated unannotated function.
    khorne_typed = beartype(khorne)

    # Assert that @beartype efficiently reduces to a noop (i.e., the identity
    # decorator) when decorating this function.
    assert khorne_typed is khorne

    # Call this function and assert the expected return value.
    assert khorne_typed('WAAAGH!', '!HGAAAW') == 'WAAAGH!!HGAAAW'


def test_decor_noop_redecorated_sync() -> None:
    '''
    Test that the :func:`beartype.beartype` decorator efficiently reduces to a
    noop on **synchronous wrappers** (i.e., wrapper functions declared with the
    ``def`` rather than ``async def`` keyword) generated by prior calls to this
    decorator.
    '''

    # Defer test-specific imports.
    from beartype import beartype

    # Arbitrary function.
    @beartype
    def xenos(interex: str, diasporex: str):
        return None if interex and diasporex else interex + diasporex

    # Assert that attempting to redecorate this function yields the same
    # wrapper generated by the above decoration.
    assert xenos is beartype(xenos)

    # Call this function and assert no value to be returned.
    assert xenos('Luna Wolves', diasporex='Iron Hands Legion') is None

# ....................{ TESTS ~ async                      }....................
async def test_decor_noop_unhinted_async() -> None:
    '''
    Test that the :func:`beartype.beartype` decorator efficiently reduces to a
    noop on **unannotated asynchronous callables** (i.e., callables with *no*
    annotations declared with the ``async def`` rather than ``def`` keywords).
    '''

    # Defer test-specific imports.
    from asyncio import sleep
    from beartype import beartype

    # Undecorated unannotated coroutine.
    async def kaela(mensha, khaine):
        await sleep(0)
        return mensha + khaine

    # Decorated unannotated function.
    kaela_typed = beartype(kaela)

    # Assert that @beartype efficiently reduces to a noop (i.e., the identity
    # decorator) when decorating this coroutine.
    assert kaela_typed is kaela

    # Call this coroutine and assert the expected return value.
    assert await kaela_typed('Blood Runs...', 'Anger Rises') == (
        'Blood Runs...Anger Rises')


async def test_decor_noop_redecorated_async() -> None:
    '''
    Test that the :func:`beartype.beartype` decorator efficiently reduces to a
    noop on **asynchronous wrappers** (i.e., wrapper functions declared with
    the ``async def`` rather than ``def`` keywords) generated by prior calls to
    this decorator.
    '''

    # Defer test-specific imports.
    from asyncio import sleep
    from beartype import beartype

    # Arbitrary coroutine.
    @beartype
    async def isha(kurnous: str, asuryan: str):
        await sleep(0)
        return None if kurnous and asuryan else kurnous + asuryan

    # Assert that attempting to redecorate this coroutine yields the same
    # wrapper generated by the above decoration.
    assert isha is beartype(isha)

    # Call this coroutine and assert no value to be returned.
    assert await isha(
        'God of the hunt', asuryan='ruler of the Aeldari pantheon') is None

# ....................{ TESTS ~ ignorable                  }....................
def test_decor_noop_hint_ignorable_iter(hints_ignorable) -> None:
    '''
    Test that the :func:`beartype.beartype` decorator efficiently reduces to a
    noop on callables annotated with only ignorable type hints in a manner
    generically exercising non-trivial edge cases with iteration.

    Parameters
    ----------
    hints_ignorable : frozenset
        Frozen set of ignorable PEP-agnostic type hints.
    '''

    # Defer test-specific imports.
    from beartype import beartype

    # Assert that @beartype efficiently reduces to a noop when passed only type
    # hints known to be ignorable.
    for hint_ignorable in hints_ignorable:
        def revenant_scout_titan(
            antecedent:  hint_ignorable,
            preposition: hint_ignorable,
            succedent:   hint_ignorable,
        ) -> hint_ignorable:
            return antecedent + preposition + succedent

        # Decorated annotated functions with ignorable type hints.
        revenant_scout_titan_typed = beartype(revenant_scout_titan)

        # Assert these functions efficiently reduces to their untyped variants.
        assert revenant_scout_titan_typed is revenant_scout_titan

        # Assert these functions return the expected values.
        assert revenant_scout_titan_typed(
            'Hearts Armoured', ' for ', 'Battle') == (
            'Hearts Armoured for Battle')


def test_decor_noop_hint_ignorable_order() -> None:
    '''
    Test that the :func:`beartype.beartype` decorator efficiently reduces to a
    noop on callables annotated with only ignorable type hints in a manner
    specifically exercising non-trivial edge cases with respect to ordering
    that would be pragmatically infeasible to exercise with to generic
    iteration in the :func:`test_decor_noop_hint_ignorable_iter` test.
    '''

    # Defer test-specific imports.
    from beartype import beartype
    from beartype._cave._cavefast import AnyType
    from typing import Any

    # Undecorated annotated function with ignorable type hints.
    def gork(stompa: AnyType, gargant: object) -> Any:
        return stompa + gargant

    # Undecorated annotated function with ignorable type hints (in the reverse
    # direction of the previously defined function). Since low-level decorator
    # code necessarily handles parameters differently from return values *AND*
    # since the return value for any function may be annotated with at most one
    # type hint, exercising all edge cases necessitates two or more functions.
    def mork(gargant: Any, stompa: object) -> AnyType:
        return stompa + gargant

    # Decorated annotated functions with ignorable type hints.
    gork_typed = beartype(gork)
    mork_typed = beartype(mork)

    # Assert that @beartype efficiently reduces to a noop (i.e., the identity
    # decorator) when decorating these functions.
    assert gork_typed is gork
    assert mork_typed is mork

    # Assert these functions return the expected values.
    assert gork_typed('Goff Klawstompa: ', 'Mega-Gargant') == (
        'Goff Klawstompa: Mega-Gargant')
    assert mork_typed('Killa Kan', "Big Mek's Stompa: ") == (
        "Big Mek's Stompa: Killa Kan")
