Repository: spulec/freezegun

Issue:
Mark notation does not work when using pytest-asyncio library
Simple test case fails when using both libraries and the mark notation:

```
from freezegun import freeze_time
import datetime
import asyncio
import pytest
import pytest_asyncio

@freeze_time("2012-01-14")
async def test():
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)  # passes

@pytest.mark.asyncio
@freeze_time("2012-01-14")
async def test_async():
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)  # fails
```

If we use a different notation then it works as expected:

```
from freezegun import freeze_time
import datetime
import asyncio
import pytest
import pytest_asyncio

@freeze_time("2012-01-14")
async def test():
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)  # passes

@pytest.mark.asyncio
async def test_async():
    with freeze_time("2012-01-14"):
        assert datetime.datetime.now() == datetime.datetime(2012, 1, 14) # passes
```

Dependencies using Python 3:
```
freezegun==0.3.8
pytest==3.0.7
pytest-asyncio==0.5.0
```


Discussion:

Patch:
diff --git a/freezegun/_async.py b/freezegun/_async.py
new file mode 100644
index 0000000..a4f2e03
--- /dev/null
+++ b/freezegun/_async.py
@@ -0,0 +1,17 @@
+import functools
+
+import asyncio
+
+
+def wrap_coroutine(api, coroutine):
+    @functools.wraps(coroutine)
+    @asyncio.coroutine
+    def wrapper(*args, **kwargs):
+        with api as time_factory:
+            if api.as_arg:
+                result = yield from coroutine(time_factory, *args, **kwargs)
+            else:
+                result = yield from coroutine(*args, **kwargs)
+        return result
+
+    return wrapper
diff --git a/freezegun/api.py b/freezegun/api.py
index c642635..7c3585e 100644
--- a/freezegun/api.py
+++ b/freezegun/api.py
@@ -36,6 +36,15 @@ try:
 except ImportError:
     import copyreg
 
+try:
+    iscoroutinefunction = inspect.iscoroutinefunction
+    from freezegun._async import wrap_coroutine
+except AttributeError:
+    iscoroutinefunction = lambda x: False
+
+    def wrap_coroutine(*args):
+        raise NotImplementedError()
+
 
 # Stolen from six
 def with_metaclass(meta, *bases):
@@ -318,6 +327,8 @@ class _freeze_time(object):
     def __call__(self, func):
         if inspect.isclass(func):
             return self.decorate_class(func)
+        elif iscoroutinefunction(func):
+            return self.decorate_coroutine(func)
         return self.decorate_callable(func)
 
     def decorate_class(self, klass):
@@ -495,6 +506,9 @@ class _freeze_time(object):
         uuid._uuid_generate_time = real_uuid_generate_time
         uuid._UuidCreate = real_uuid_create
 
+    def decorate_coroutine(self, coroutine):
+        return wrap_coroutine(self, coroutine)
+
     def decorate_callable(self, func):
         def wrapper(*args, **kwargs):
             with self as time_factory:


Test patch:
diff --git a/tests/test_asyncio.py b/tests/test_asyncio.py
new file mode 100644
index 0000000..3be1ebf
--- /dev/null
+++ b/tests/test_asyncio.py
@@ -0,0 +1,36 @@
+import datetime
+from textwrap import dedent
+
+from nose.plugins import skip
+
+from freezegun import freeze_time
+
+try:
+    import asyncio
+except ImportError:
+    asyncio = False
+
+
+def test_time_freeze_coroutine():
+    if not asyncio:
+        raise skip.SkipTest('asyncio required')
+    @asyncio.coroutine
+    @freeze_time('1970-01-01')
+    def frozen_coroutine():
+        assert datetime.date.today() == datetime.date(1970, 1, 1)
+
+    asyncio.get_event_loop().run_until_complete(frozen_coroutine())
+
+
+def test_time_freeze_async_def():
+    try:
+        exec('async def foo(): pass')
+    except SyntaxError:
+        raise skip.SkipTest('async def not supported')
+    else:
+        exec(dedent('''
+        @freeze_time('1970-01-01')
+        async def frozen_coroutine():
+            assert datetime.date.today() == datetime.date(1970, 1, 1)
+        asyncio.get_event_loop().run_until_complete(frozen_coroutine())
+        '''))