#!/usr/bin/env python3
# --------------------( LICENSE                            )--------------------
# Copyright (c) 2014-2025 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                              }....................
def test_decor_noop_python_optimized(monkeypatch: 'pytest.MonkeyPatch') -> None:
    '''
    Test that the :func:`beartype.beartype` decorator efficiently reduces to a
    noop for **optimized Python processes** (i.e., optimized *after* process
    invocation time, typically by the user setting the ``${PYTHONOPTIMIZED}``
    environment variable to a non-zero integer from an interactive REPL).

    Parameters
    ----------
    monkeypatch : MonkeyPatch
        :mod:`pytest` fixture allowing various state associated with the active
        Python process to be temporarily changed for the duration of this test.
    '''

    # ....................{ IMPORTS                        }....................
    # Defer test-specific imports.
    from beartype import beartype
    from beartype.roar import BeartypeCallHintParamViolation
    from pytest import raises

    # ....................{ CALLABLES                      }....................
    def and_what_am_I(that_I: str, should_linger_here: str) -> str:
        '''
        Arbitrary annotated function.
        '''

        return that_I + should_linger_here

    # ....................{ ASSERTS ~ unoptimized          }....................
    # This function decorated by @beartype.
    and_what_am_I_typed = beartype(and_what_am_I)

    # Assert that @beartype actually decorated this function rather than
    # reducing to a noop.
    assert and_what_am_I_typed is not and_what_am_I

    # Assert that @beartype decorated this function with type-checking by
    # raising the expected exception when passed invalid parameters.
    with raises(BeartypeCallHintParamViolation):
        and_what_am_I_typed(b'With voice far sweeter', b'than thy dying notes,')

    # ....................{ ASSERTS ~ optimized            }....................
    # Temporarily set the ${PYTHONOPTIMIZE} environment variable governing
    # Python optimization to an arbitrary positive integer.
    monkeypatch.setenv('PYTHONOPTIMIZE', '1')

    # This function decorated by @beartype yet again.
    and_what_am_I_typed = beartype(and_what_am_I)

    # Assert that @beartype efficiently reduces to a noop (i.e., the identity
    # decorator) when the current Python process is "optimized."
    assert and_what_am_I_typed is and_what_am_I

# ....................{ 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

    def khorne(gork, mork):
        '''
        Undecorated unannotated function.
        '''

        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

    @beartype
    def xenos(interex: str, diasporex: str):
        '''
        Arbitrary function.
        '''

        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")
