testing-cabal / mock

The Python mock library
https://docs.python.org/dev/library/unittest.mock.html
BSD 2-Clause "Simplified" License
536 stars 107 forks source link

version 4.0 #476

Closed cjw296 closed 4 years ago

cjw296 commented 4 years ago

@lisroach - anything you can suggest to get these 6 failing AsyncMock tests passing on Python 3.6?

https://app.circleci.com/jobs/github/testing-cabal/mock/4513

__________________________________________ AsyncArguments.test_add_return_value ___________________________________________

self = <mock.tests.testasync.AsyncArguments testMethod=test_add_return_value>

    def test_add_return_value(self):
        async def addition(self, var):
            return var + 1

        mock = AsyncMock(addition, return_value=10)
        output = run(mock(5))

>       self.assertEqual(output, 10)
E       AssertionError: None != 10

mock/tests/testasync.py:343: AssertionError
______________________________________ AsyncArguments.test_add_side_effect_function _______________________________________

self = <mock.tests.testasync.AsyncArguments testMethod=test_add_side_effect_function>

    def test_add_side_effect_function(self):
        async def addition(var):
            return var + 1
        mock = AsyncMock(side_effect=addition)
        result = run(mock(5))
>       self.assertEqual(result, 6)
E       AssertionError: None != 6

mock/tests/testasync.py:357: AssertionError
______________________________________ AsyncArguments.test_add_side_effect_iterable _______________________________________

self = <mock.tests.testasync.AsyncArguments testMethod=test_add_side_effect_iterable>

    def test_add_side_effect_iterable(self):
        vals = [1, 2, 3]
        mock = AsyncMock(side_effect=vals)
        for item in vals:
>           self.assertEqual(item, run(mock()))
E           AssertionError: 1 != None

mock/tests/testasync.py:363: AssertionError
____________________________ AsyncContextManagerTest.test_mock_customize_async_context_manager ____________________________

self = <mock.tests.testasync.AsyncContextManagerTest testMethod=test_mock_customize_async_context_manager>

    def test_mock_customize_async_context_manager(self):
        instance = self.WithAsyncContextManager()
        mock_instance = MagicMock(instance)

        expected_result = object()
        mock_instance.__aenter__.return_value = expected_result

        async def use_context_manager():
            async with mock_instance as result:
                return result

>       self.assertIs(run(use_context_manager()), expected_result)
E       AssertionError: None is not <object object at 0x103093870>

mock/tests/testasync.py:424: AssertionError
____________________________ AsyncContextManagerTest.test_mock_supports_async_context_manager _____________________________

self = <mock.tests.testasync.AsyncContextManagerTest testMethod=test_mock_supports_async_context_manager>

    def test_mock_supports_async_context_manager(self):
        called = False
        instance = self.WithAsyncContextManager()
        mock_instance = MagicMock(instance)

        async def use_context_manager():
            nonlocal called
            async with mock_instance as result:
                called = True
            return result

        result = run(use_context_manager())
        self.assertFalse(instance.entered)
        self.assertFalse(instance.exited)
        self.assertTrue(called)
        self.assertTrue(mock_instance.entered)
        self.assertTrue(mock_instance.exited)
        self.assertTrue(mock_instance.__aenter__.called)
        self.assertTrue(mock_instance.__aexit__.called)
        self.assertIsNot(mock_instance, result)
>       self.assertIsInstance(result, AsyncMock)
E       AssertionError: None is not an instance of <class 'mock.mock.AsyncMock'>

mock/tests/testasync.py:411: AssertionError
__________________________________________ AsyncIteratorTest.test_mock_async_for __________________________________________

self = <mock.tests.testasync.AsyncIteratorTest testMethod=test_mock_async_for>

    def test_mock_async_for(self):
        async def iterate(iterator):
            accumulator = []
            async for item in iterator:
                accumulator.append(item)

            return accumulator

        expected = ["FOO", "BAR", "BAZ"]
        with self.subTest("iterate through default value"):
            mock_instance = MagicMock(self.WithAsyncIterator())
>           self.assertEqual([], run(iterate(mock_instance)))
E           AssertionError: [] != None

mock/tests/testasync.py:512: AssertionError
tirkarthi commented 4 years ago

Questions :

Thank you very much Chris for this effort :)

cjw296 commented 4 years ago

I've started from the revision in https://github.com/testing-cabal/mock/pull/476/commits/ce5b6961d0b38b9f26e6bffabf46fadfc55696ba, it's also stored in lastsync.txt in the repo (the backporting instructions in the docs and backport.py give the full details).

The reason for this is that it's the revision before the problematic positional-only argument commit. As we discussed by email, my plan is to skip that patch and the use backport.py to bring the repo up to cpython's current master before doing a 4.0 release. So, all the changes you mention will get pulled in, along with their problems and challenges :-/

Only DeprecationWarnings are hidden: https://github.com/testing-cabal/mock/blob/master/setup.cfg#L54 - so all others should be visible in the test run output.

I'd suggest waiting until I've got up to current cpython master before really looking at this, if I hit issues before then I'll ping you on this issue for help :-)

tirkarthi commented 4 years ago

Thanks for the details. Sure, I will be happy to help :)

cjw296 commented 4 years ago

It's interesting to see what's missing coverage: https://app.circleci.com/jobs/github/testing-cabal/mock/4555 I wish we could introduce coverage gating upstream, especially for a library like mock, there's no excuse for having lines of code that aren't covered by tests.

cjw296 commented 4 years ago

@lisroach - can you talk me through the change in https://github.com/python/cpython/commit/f1a297acb60b88917712450ebd3cfa707e6efd6b as it causes the following failure on Py3.6 when backported:

______________________________________ SpecSignatureTest.test_autospec_on_bound_builtin_function _______________________________________

self = <mock.tests.testhelpers.SpecSignatureTest testMethod=test_autospec_on_bound_builtin_function>

    @pytest.mark.skipif(IS_PYPY,
                        reason="https://bitbucket.org/pypy/pypy/issues/3010")
    def test_autospec_on_bound_builtin_function(self):
        meth = types.MethodType(time.ctime, time.time())
        self.assertIsInstance(meth(), str)
>       mocked = create_autospec(meth)

mock/tests/testhelpers.py:899: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
mock/mock.py:2603: in create_autospec
    name=_name, **_kwargs)
mock/mock.py:416: in __new__
    if _is_async_obj(bound_args[spec_arg[0]]):
mock/mock.py:52: in _is_async_obj
    return asyncio.iscoroutinefunction(obj) or inspect.isawaitable(obj)
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/coroutines.py:256: in iscoroutinefunction
    _inspect_iscoroutinefunction(func))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

object = <bound method ctime of 1579078759.885324>

    def iscoroutinefunction(object):
        """Return true if the object is a coroutine function.

        Coroutine functions are defined with "async def" syntax.
        """
        return bool((isfunction(object) or ismethod(object)) and
>                   object.__code__.co_flags & CO_COROUTINE)
E       AttributeError: 'builtin_function_or_method' object has no attribute '__code__'

/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/inspect.py:185: AttributeError
tirkarthi commented 4 years ago

My upstream PR helps with code coverage for AsyncMock https://github.com/python/cpython/pull/17906 .. IIRC you have pruned some untested code to get to 100% coverage earlier.

lisroach commented 4 years ago

@cjw296 thanks for doing all of this work!

I think that test failure is because the inspect.iscoroutinefunction was fixed between 3.6 and 3.8, in 3.8 it actually checks there is a __code__ before doing the call (https://github.com/python/cpython/commit/fcef60f59d04c63b3540b4c4886226098c1bacd1) to prevent crashing. I made that change in my commit because I found not all Awaitables have a __code__ attribute, making the check invalid in those cases.

We can update upstream to be backwards compatible here, maybe we need to pull in the _has_code_flag function from 3.8's inspect module into mock? That's not very elegant but I'm trying to think of another way around it.

cjw296 commented 4 years ago

@lisroach - Yeah, for now I've backported the stuff from Python 3.8 in 0c4ceaa. It's bigger than I'd like and maybe we can find a different way once I catch this branch up to cpython master.

tirkarthi commented 4 years ago

Hmm, is the call.count() error due to removal of the overridden definition in https://github.com/testing-cabal/mock/pull/476/commits/fadfea9d6325024e904cbbbfd9906da98b38a43f#diff-82bfc8fa2208ad81d6c698a452a77c2aL2560 ? I am wondering why it's not an error in CPython test suite.

cjw296 commented 4 years ago

Yeah, going to dig into it now. My only guess is that I screwed up a backport, or the need for these went away sometime after 3.8...

cjw296 commented 4 years ago

Yep, screwed up backporting on 72c359912d36705a94fca8b63d80451905a14ae4 / GH-15565 so redoing.