from glob import glob
from importlib import import_module
from inspect import getargspec, getmembers
from os import path
from types import FunctionType
from unittest import TestCase

from pyinfra import operations
from pyinfra.api.operation_kwargs import OPERATION_KWARGS


def _is_pyinfra_operation(module, key, value):
    return (
        isinstance(value, FunctionType)
        and value.__module__ == module.__name__
        and getattr(value, '_pyinfra_op', False)
        and not value.__name__.startswith('_')
        and not key.startswith('_')
    )


def iter_operations():
    module_filenames = glob(path.join(path.dirname(operations.__file__), '*.py'))

    for module_name in sorted(module_filenames):
        module = import_module('pyinfra.operations.{0}'.format(
            path.basename(module_name)[:-3],
        ))

        for key, value in sorted(getmembers(module)):
            if _is_pyinfra_operation(module, key, value):
                yield module, value


class TestOperationGlobalArguments(TestCase):
    def test_operations_do_not_use_global_arguments(self):
        global_arg_keys = set()
        for group, kwargs in OPERATION_KWARGS.items():
            global_arg_keys.update(kwargs.keys())

        for op_module, op_func in iter_operations():
            argspec = getargspec(op_func._pyinfra_op)
            for arg in argspec.args:
                assert arg not in global_arg_keys, \
                    '`{0}` argument found in {1}.{2} operation function'.format(
                        arg, op_module.__name__, op_func.__name__,
                    )

Add tests for operation arguments clashing with global arguments.
Add tests for operation arguments clashing with global arguments.

This makes it possible to expand the global argument list with
confidence. It's worth noting this shouldn't be done lightly, as it
prevents any argument keys being used elsewhere.
