# Source code for nose2.plugins.loader.parameters

    """
    Load tests from parameterized functions and methods.

    This plugin implements :func:`getTestCaseNames`,
    :func:`loadTestsFromModule`, and :func:`loadTestsFromName` to support
    loading tests from parameterized test functions and methods.

    To parameterize a function or test case method, use :func:`nose2.tools.params`.

    To address a particular parameterized test via a command-line test name,
    append a colon (':') followed by the index (*starting from 1*) of the
    case you want to execute.

    Such And The Parameters Plugin
    ------------------------------

    The parameters plugin can work with the Such DSL, as long as the first argument
    of the test function is the "case" argument, followed by the other parameters::

        from nose2.tools import such
        from nose2.tools.params import params

        with such.A('foo') as it:
            @it.should('do bar')
            @params(1,2,3)
            def test(case, bar):
                case.assertTrue(isinstance(bar, int))

            @it.should('do bar and extra')
            @params((1, 2), (3, 4) ,(5, 6))
            def testExtraArg(case, bar, foo):
                case.assertTrue(isinstance(bar, int))
                case.assertTrue(isinstance(foo, int))

        it.createTests(globals())

    """

    # This module contains some code copied from unittest2 and other code
    # developed in reference to unittest2.
    # unittest2 is Copyright (c) 2001-2010 Python Software Foundation; All
    # Rights Reserved. See: http://docs.python.org/license.html

    import functools
    import logging
    import sys
    import types
    import unittest

    from nose2 import exceptions, util
    from nose2.events import Plugin
    from nose2.plugins.loader.testclasses import MethodTestCase

    log = logging.getLogger(__name__)
    __unittest = True

    class ParamsFunctionCase(unittest.FunctionTestCase):
        def __init__(self, name, func, **args):
            self._funcName = name
            unittest.FunctionTestCase.__init__(self, func, **args)

        def __repr__(self):
            return self._funcName

        id = __str__ = __repr__

    class Parameters(Plugin):
        """Loader plugin that loads parameterized tests"""

        alwaysOn = True
        configSection = "parameters"

        def registerInSubprocess(self, event):
            event.pluginClasses.append(self.__class__)

        def getTestCaseNames(self, event):
            """Generate test case names for all parameterized methods"""
            log.debug("getTestCaseNames %s", event)
            for name, method in util.iter_attrs(event.testCase):
                if not event.isTestMethod(name):
                    continue
                paramList = getattr(method, "paramList", None)
                if paramList is None:
                    continue
                # exclude this method from normal collection
                event.excludedNames.append(name)
                # generate the methods to be loaded by the testcase loader
                self._generate(event, name, method, event.testCase)

        def getTestMethodNames(self, event):
            return self.getTestCaseNames(event)

        def loadTestsFromModule(self, event):
            """Load tests from parameterized test functions in the module"""
            module = event.module

            def is_test(obj):
                return obj.__name__.startswith(self.session.testMethodPrefix) and hasattr(
                    obj, "paramList"
                )

            tests = []
            for _, obj in util.iter_attrs(module):
                if isinstance(obj, types.FunctionType) and is_test(obj):
                    tests.extend(self._generateFuncTests(obj))
            event.extraTests.extend(tests)

        def loadTestsFromName(self, event):
            """Load parameterized test named on command line"""
            original_name = name = event.name
            module = event.module
            try:
                result = util.test_from_name(name, module)
            except (AttributeError, ImportError):
                event.handled = True
                return event.loader.failedLoadTests(name, sys.exc_info())
            if result is None:
                # we can't find it - let the default case handle it
                return

            parent, obj, fqname, index = result
            if not hasattr(obj, "paramList"):
                return

            if (
                index is None
                and not isinstance(parent, type)
                and not isinstance(obj, types.FunctionType)
            ):
                log.debug("Don't know how to load parameterized tests from %s", obj)
                return

            if (
                parent
                and isinstance(parent, type)
                and issubclass(parent, unittest.TestCase)
            ):
                # parameterized method in test case
                names = self._generate(event, obj.__name__, obj, parent)
                tests = [parent(n) for n in names]
            elif parent and isinstance(parent, type):
                # parameterized method in test class
                names = self._generate(event, obj.__name__, obj, parent)
                tests = [MethodTestCase(parent)(name) for name in names]
            else:
                # parameterized func
                tests = list(self._generateFuncTests(obj))

            if index is not None:
                try:
                    tests = [tests[index - 1]]
                except IndexError:
                    raise exceptions.TestNotFoundError(original_name)

            suite = event.loader.suiteClass()
            suite.addTests(tests)
            event.handled = True
            return suite

        def _generate(self, event, name, method, testCaseClass):
            names = []
            for index, argSet in enumerate_params(method.paramList):
                method_name = util.name_from_args(name, index, argSet)
                if not hasattr(testCaseClass, method_name):
                    # not already generated
                    def _method(self, method=method, argSet=argSet):
                        return method(self, *argSet)

                    _method = functools.update_wrapper(_method, method)
                    delattr(_method, "paramList")
                    setattr(testCaseClass, method_name, _method)
                names.append(method_name)
            return names

        def _generateFuncTests(self, obj):
            args = {}
            setUp = getattr(obj, "setUp", None)
            tearDown = getattr(obj, "tearDown", None)
            if setUp is not None:
                args["setUp"] = setUp
            if tearDown is not None:
                args["tearDown"] = tearDown
            for index, argSet in enumerate_params(obj.paramList):

                def func(argSet=argSet, obj=obj):
                    return obj(*argSet)

                func = functools.update_wrapper(func, obj)
                delattr(func, "paramList")
                name = f"{obj.__module__}.{obj.__name__}"
                func_name = util.name_from_args(name, index, argSet)
                yield util.transplant_class(ParamsFunctionCase, obj.__module__)(
                    func_name, func, **args
                )

    def enumerate_params(paramList):
        for index, argSet in enumerate(paramList):
            if not isinstance(argSet, tuple):
                argSet = (argSet,)
            yield index, argSet