Repository: wolever/parameterized

Issue:
prameterized.expand make mock.patch class decorator evaluated against same function multiple times
If we use parameterized.expand with mock.patch class decorator,  mock.patch decorator is evaluated against same function object multiple times and messed up arguments.

Suppose I have a following code

```
import os
from mock import patch
from parameterized import parameterized

@patch("os.listdir")
class Test():
    @parameterized.expand([[1,2], [3,4], [5,6]])
    @patch("sys.path")
    def test1(self, t1, t2, mock_syspath, mock_listdir):
        pass
```

I expected above test code to be succeeded, but  actually above code raise following error

```
    @wraps(func)
    def patched(*args, **keywargs):
        extra_args = []
        entered_patchers = []

        exc_info = tuple()
        try:
            for patching in patched.patchings:
                arg = patching.__enter__()
                entered_patchers.append(patching)
                if patching.attribute_name is not None:
                    keywargs.update(arg)
                elif patching.new is DEFAULT:
                    extra_args.append(arg)

            args += tuple(extra_args)
>           return func(*args, **keywargs)
E           TypeError: test1() takes exactly 5 arguments (7 given)
```

The root cause of failure is that mock.patch class decorator add mock.patch function decorator for each methods in class and the function be created by parameterised.expand decorator is also decorated but obviously function object to be wrapped by standalone_func  is same among each function created, thus mock.patch function decorator unintentionally executed against same function object and add argument for the times of the number of parameterised argument. 
That's why the number of argument miss match exception raised in above test code.

mock module is often used in test and python 3 add it under unittest module  so I think it's better to support above case. 

Discussion:

Patch:
diff --git a/parameterized/parameterized.py b/parameterized/parameterized.py
index 1d83404..96de811 100644
--- a/parameterized/parameterized.py
+++ b/parameterized/parameterized.py
@@ -40,6 +40,29 @@ else:
 
 _param = namedtuple("param", "args kwargs")
 
+
+def reapply_patches_if_need(func):
+
+    def dummy_wrapper(orgfunc):
+        @wraps(orgfunc)
+        def dummy_func(*args, **kwargs):
+            return orgfunc(*args, **kwargs)
+        return dummy_func
+
+    if hasattr(func, 'patchings'):
+        func = dummy_wrapper(func)
+        tmp_patchings = func.patchings
+        delattr(func, 'patchings')
+        for patch_obj in tmp_patchings:
+            func = patch_obj.decorate_callable(func)
+    return func
+
+
+def delete_patches_if_need(func):
+    if hasattr(func, 'patchings'):
+        func.patchings[:] = []
+
+
 class param(_param):
     """ Represents a single parameter to a test case.
 
@@ -426,8 +449,16 @@ class parameterized(object):
             paramters = cls.input_as_callable(input)()
             for num, p in enumerate(paramters):
                 name = name_func(f, num, p)
-                frame_locals[name] = cls.param_as_standalone_func(p, f, name)
+                # If the original function has patches applied by 'mock.patch',
+                # re-construct all patches on the just former decoration layer
+                # of param_as_standalone_func so as not to share
+                # patch objects between new functions
+                nf = reapply_patches_if_need(f)
+                frame_locals[name] = cls.param_as_standalone_func(p, nf, name)
                 frame_locals[name].__doc__ = doc_func(f, num, p)
+            # Delete original patches to prevent new function from evaluating
+            # original patching object as well as re-constructed patches.
+            delete_patches_if_need(f)
 
             f.__test__ = False
         return parameterized_expand_wrapper
diff --git a/tox.ini b/tox.ini
index c75a0ab..c58219f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,6 +4,7 @@ envlist=py{27,35,36,py}-{nose,nose2,pytest,unit,unit2}
 [testenv]
 deps=
     nose
+    mock
     nose2: nose2
     pytest: pytest>=2
     unit2: unittest2


Test patch:
diff --git a/parameterized/test.py b/parameterized/test.py
index d5bcd8f..2cfded3 100644
--- a/parameterized/test.py
+++ b/parameterized/test.py
@@ -1,6 +1,7 @@
 # coding=utf-8
 
 import inspect
+import mock
 from unittest import TestCase
 from nose.tools import assert_equal
 from nose.plugins.skip import SkipTest
@@ -101,6 +102,74 @@ def custom_naming_func(custom_tag):
     return custom_naming_func
 
 
+@mock.patch("os.getpid")
+class TestParameterizedExpandWithMockPatchForClass(TestCase):
+    expect([
+        "test_one_function_patch_decorator('foo1', 'umask', 'getpid')",
+        "test_one_function_patch_decorator('foo0', 'umask', 'getpid')",
+        "test_one_function_patch_decorator(42, 'umask', 'getpid')",
+    ])
+
+    @parameterized.expand([(42, ), "foo0", param("foo1")])
+    @mock.patch("os.umask")
+    def test_one_function_patch_decorator(self, foo, mock_umask, mock_getpid):
+        missing_tests.remove("test_one_function_patch_decorator(%r, %r, %r)" %
+                             (foo, mock_umask._mock_name,
+                              mock_getpid._mock_name))
+
+    expect([
+        "test_multiple_function_patch_decorator"
+        "(42, 51, 'umask', 'fdopen', 'getpid')",
+        "test_multiple_function_patch_decorator"
+        "('foo0', 'bar0', 'umask', 'fdopen', 'getpid')",
+        "test_multiple_function_patch_decorator"
+        "('foo1', 'bar1', 'umask', 'fdopen', 'getpid')",
+    ])
+
+    @parameterized.expand([(42, 51), ("foo0", "bar0"), param("foo1", "bar1")])
+    @mock.patch("os.fdopen")
+    @mock.patch("os.umask")
+    def test_multiple_function_patch_decorator(self, foo, bar, mock_umask,
+                                               mock_fdopen, mock_getpid):
+        missing_tests.remove("test_multiple_function_patch_decorator"
+                             "(%r, %r, %r, %r, %r)" %
+                             (foo, bar, mock_umask._mock_name,
+                              mock_fdopen._mock_name, mock_getpid._mock_name))
+
+
+class TestParameterizedExpandWithNoMockPatchForClass(TestCase):
+    expect([
+        "test_one_function_patch_decorator('foo1', 'umask')",
+        "test_one_function_patch_decorator('foo0', 'umask')",
+        "test_one_function_patch_decorator(42, 'umask')",
+    ])
+
+    @parameterized.expand([(42, ), "foo0", param("foo1")])
+    @mock.patch("os.umask")
+    def test_one_function_patch_decorator(self, foo, mock_umask):
+        missing_tests.remove("test_one_function_patch_decorator(%r, %r)" %
+                             (foo, mock_umask._mock_name))
+
+    expect([
+        "test_multiple_function_patch_decorator"
+        "(42, 51, 'umask', 'fdopen')",
+        "test_multiple_function_patch_decorator"
+        "('foo0', 'bar0', 'umask', 'fdopen')",
+        "test_multiple_function_patch_decorator"
+        "('foo1', 'bar1', 'umask', 'fdopen')",
+    ])
+
+    @parameterized.expand([(42, 51), ("foo0", "bar0"), param("foo1", "bar1")])
+    @mock.patch("os.fdopen")
+    @mock.patch("os.umask")
+    def test_multiple_function_patch_decorator(self, foo, bar, mock_umask,
+                                               mock_fdopen):
+        missing_tests.remove("test_multiple_function_patch_decorator"
+                             "(%r, %r, %r, %r)" %
+                             (foo, bar, mock_umask._mock_name,
+                              mock_fdopen._mock_name))
+
+
 class TestParamerizedOnTestCase(TestCase):
     expect([
         "test_on_TestCase('foo0', bar=None)",