mirror of
https://github.com/Zulko/moviepy.git
synced 2021-07-27 01:17:47 +03:00
Add tests to prevent decorators arguments inconsistency (#1610)
* Add tests to prevent decorators arguments inconsistency
This commit is contained in:
@@ -13,7 +13,6 @@ __all__ = (
|
||||
"audio_delay",
|
||||
"audio_fadein",
|
||||
"audio_fadeout",
|
||||
"audio_left_right",
|
||||
"audio_loop",
|
||||
"audio_normalize",
|
||||
"multiply_stereo_volume",
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
"""Define general test helper attributes and utilities."""
|
||||
|
||||
import ast
|
||||
import contextlib
|
||||
import functools
|
||||
import http.server
|
||||
import importlib
|
||||
import inspect
|
||||
import io
|
||||
import pkgutil
|
||||
import socketserver
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -52,10 +57,85 @@ def get_mono_wave(freq=440):
|
||||
|
||||
@contextlib.contextmanager
|
||||
def static_files_server(port=8000):
|
||||
class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
pass
|
||||
|
||||
my_server = socketserver.TCPServer(("", port), MyHttpRequestHandler)
|
||||
my_server = socketserver.TCPServer(("", port), http.server.SimpleHTTPRequestHandler)
|
||||
thread = threading.Thread(target=my_server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
yield thread
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def get_moviepy_modules():
|
||||
"""Get all moviepy module names and if each one is a package."""
|
||||
response = []
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
moviepy_module = importlib.import_module("moviepy")
|
||||
|
||||
modules = pkgutil.walk_packages(
|
||||
path=moviepy_module.__path__,
|
||||
prefix=moviepy_module.__name__ + ".",
|
||||
)
|
||||
|
||||
for importer, modname, ispkg in modules:
|
||||
response.append((modname, ispkg))
|
||||
return response
|
||||
|
||||
|
||||
def get_functions_with_decorator_defined(code, decorator_name):
|
||||
"""Get all functions in a code object which have a decorator defined,
|
||||
along with the arguments of the function and the decorator.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
code : object
|
||||
Module or class object from which to retrieve the functions.
|
||||
|
||||
decorator_name : str
|
||||
Name of the decorator defined in the functions to search.
|
||||
"""
|
||||
|
||||
class FunctionsWithDefinedDecoratorExtractor(ast.NodeVisitor):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.functions_with_decorator = []
|
||||
|
||||
def generic_visit(self, node):
|
||||
if isinstance(node, ast.FunctionDef) and node.decorator_list:
|
||||
for dec in node.decorator_list:
|
||||
if not isinstance(dec, ast.Call) or dec.func.id != decorator_name:
|
||||
continue
|
||||
|
||||
decorator_argument_names = []
|
||||
if isinstance(dec.args, ast.List):
|
||||
for args in dec.args:
|
||||
decorator_argument_names.extend(
|
||||
[e.value for e in args.elts]
|
||||
)
|
||||
else:
|
||||
for args in dec.args:
|
||||
if isinstance(args, (ast.List, ast.Tuple)):
|
||||
decorator_argument_names.extend(
|
||||
[e.value for e in args.elts]
|
||||
)
|
||||
else:
|
||||
decorator_argument_names.append(args.value)
|
||||
|
||||
function_argument_names = [arg.arg for arg in node.args.args]
|
||||
for arg in node.args.kwonlyargs:
|
||||
function_argument_names.append(arg.arg)
|
||||
|
||||
self.functions_with_decorator.append(
|
||||
{
|
||||
"function_name": node.name,
|
||||
"function_arguments": function_argument_names,
|
||||
"decorator_arguments": decorator_argument_names,
|
||||
}
|
||||
)
|
||||
|
||||
ast.NodeVisitor.generic_visit(self, node)
|
||||
|
||||
modtree = ast.parse(inspect.getsource(code))
|
||||
visitor = FunctionsWithDefinedDecoratorExtractor()
|
||||
visitor.visit(modtree)
|
||||
return visitor.functions_with_decorator
|
||||
|
||||
@@ -13,7 +13,12 @@ import pytest
|
||||
import moviepy.tools as tools
|
||||
from moviepy.video.io.downloader import download_webfile
|
||||
|
||||
from tests.test_helper import TMP_DIR, static_files_server
|
||||
from tests.test_helper import (
|
||||
TMP_DIR,
|
||||
get_functions_with_decorator_defined,
|
||||
get_moviepy_modules,
|
||||
static_files_server,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -274,5 +279,53 @@ def test_config_check():
|
||||
del sys.modules["moviepy.config"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8), reason="Requires Python 3.8 or greater")
|
||||
@pytest.mark.parametrize(
|
||||
"decorator_name",
|
||||
("convert_parameter_to_seconds", "convert_path_to_string"),
|
||||
)
|
||||
def test_decorators_argument_converters_consistency(decorator_name):
|
||||
"""Checks that for all functions that have a decorator defined (like
|
||||
``@convert_parameter_to_seconds``), the parameters passed to the decorator
|
||||
correspond to the parameters taken by the function.
|
||||
|
||||
This test is util to prevent next case in which the parameter names doesn't
|
||||
match between the decorator and the function definition:
|
||||
|
||||
>>> @convert_parameter_to_seconds(['foo']) # doctest: +SKIP
|
||||
>>> def whatever_function(bar): # bar not converted to seconds
|
||||
... pass
|
||||
|
||||
Some wrong defintions remained unnoticed in the past before this test was
|
||||
added.
|
||||
"""
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
for modname, ispkg in get_moviepy_modules():
|
||||
if ispkg:
|
||||
continue
|
||||
|
||||
try:
|
||||
module = importlib.import_module(modname)
|
||||
except ImportError:
|
||||
continue
|
||||
|
||||
functions_with_decorator = get_functions_with_decorator_defined(
|
||||
module,
|
||||
decorator_name,
|
||||
)
|
||||
|
||||
for function_data in functions_with_decorator:
|
||||
for argument_name in function_data["decorator_arguments"]:
|
||||
funcname = function_data["function_name"]
|
||||
assert argument_name in function_data["function_arguments"], (
|
||||
f"Wrong argument name '{argument_name}' in"
|
||||
f" '@{decorator_name}' decorator for function"
|
||||
f" '{funcname}' found inside module '{modname}'"
|
||||
)
|
||||
|
||||
assert function_data["decorator_arguments"]
|
||||
assert function_data["function_arguments"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main()
|
||||
|
||||
Reference in New Issue
Block a user