# Path: paco/decorator.py
# def decorate(fn):
#     """
#     Generic decorator for coroutines helper functions allowing
#     multiple variadic initialization arguments.
# 
#     This function is intended to be used internally.
# 
#     Arguments:
#         fn (function): target function to decorate.
# 
#     Raises:
#         TypeError: if function or coroutine function is not provided.
# 
#     Returns:
#         function: decorated function.
#     """
#     if not isfunction(fn):
#         raise TypeError('paco: fn must be a callable object')
# 
#     @functools.wraps(fn)
#     def decorator(*args, **kw):
#         # If coroutine object is passed
#         for arg in args:
#             if iscoro_or_corofunc(arg):
#                 return fn(*args, **kw)
# 
#         # Explicit argument must be at least a coroutine
#         if len(args) and args[0] is None:
#             raise TypeError('paco: first argument cannot be empty')
# 
#         def wrapper(coro, *_args, **_kw):
#             # coro must be a valid type
#             if not iscoro_or_corofunc(coro):
#                 raise TypeError('paco: first argument must be a '
#                                 'coroutine or coroutine function')
# 
#             # Merge call arguments
#             _args = ((coro,) + (args + _args))
#             kw.update(_kw)
# 
#             # Trigger original decorated function
#             return fn(*_args, **kw)
#         return wrapper
#     return decorator
# 
# Path: tests/helpers.py
# def run_in_loop(coro, *args, **kw):
#     loop = asyncio.get_event_loop()
#     if asyncio.iscoroutinefunction(coro) or isfunction(coro):
#         coro = coro(*args, **kw)
#     return loop.run_until_complete(coro)


import pytest
import asyncio
from paco.decorator import decorate
from .helpers import run_in_loop
@asyncio.coroutine
def coro(*args, **kw):
    return args, kw


def sample(coro, *args, **kw):
    return coro(*args, **kw)


def test_decorate_with_arguments():
    wrapper = decorate(sample)
    task = wrapper(1, foo='bar')
    args, kw = run_in_loop(task, coro, 2, bar='baz')

    assert args == (1, 2)
    assert kw == {'foo': 'bar', 'bar': 'baz'}


def test_decorate_coro_argument():
    wrapper = decorate(sample)
    task = wrapper(coro, 1, foo='bar')
    args, kw = run_in_loop(task)

    assert args == (1,)
    assert kw == {'foo': 'bar'}


def test_decorate_coro_object_argument():
    wrapper = decorate(lambda coro: coro)
    task = wrapper(coro(1, foo='bar'))

# Path: paco/decorator.py
# def decorate(fn):
#     """
#     Generic decorator for coroutines helper functions allowing
#     multiple variadic initialization arguments.
# 
#     This function is intended to be used internally.
# 
#     Arguments:
#         fn (function): target function to decorate.
# 
#     Raises:
#         TypeError: if function or coroutine function is not provided.
# 
#     Returns:
#         function: decorated function.
#     """
#     if not isfunction(fn):
#         raise TypeError('paco: fn must be a callable object')
# 
#     @functools.wraps(fn)
#     def decorator(*args, **kw):
#         # If coroutine object is passed
#         for arg in args:
#             if iscoro_or_corofunc(arg):
#                 return fn(*args, **kw)
# 
#         # Explicit argument must be at least a coroutine
#         if len(args) and args[0] is None:
#             raise TypeError('paco: first argument cannot be empty')
# 
#         def wrapper(coro, *_args, **_kw):
#             # coro must be a valid type
#             if not iscoro_or_corofunc(coro):
#                 raise TypeError('paco: first argument must be a '
#                                 'coroutine or coroutine function')
# 
#             # Merge call arguments
#             _args = ((coro,) + (args + _args))
#             kw.update(_kw)
# 
#             # Trigger original decorated function
#             return fn(*_args, **kw)
#         return wrapper
#     return decorator
# 
# Path: tests/helpers.py
# def run_in_loop(coro, *args, **kw):
#     loop = asyncio.get_event_loop()
#     if asyncio.iscoroutinefunction(coro) or isfunction(coro):
#         coro = coro(*args, **kw)
#     return loop.run_until_complete(coro)


# Path: tests/decorator_test.py
import pytest
import asyncio
from paco.decorator import decorate
from .helpers import run_in_loop

@asyncio.coroutine
def coro(*args, **kw):
    return args, kw


def sample(coro, *args, **kw):
    return coro(*args, **kw)


def test_decorate_with_arguments():
    wrapper = decorate(sample)
    task = wrapper(1, foo='bar')
    args, kw = run_in_loop(task, coro, 2, bar='baz')

    assert args == (1, 2)
    assert kw == {'foo': 'bar', 'bar': 'baz'}


def test_decorate_coro_argument():
    wrapper = decorate(sample)
    task = wrapper(coro, 1, foo='bar')
    args, kw = run_in_loop(task)

    assert args == (1,)
    assert kw == {'foo': 'bar'}


def test_decorate_coro_object_argument():
    wrapper = decorate(lambda coro: coro)
    task = wrapper(coro(1, foo='bar'))

    args, kw = run_in_loop(task)