"""This module is used to run all the doctests for all the modules in a given directory... image:: https://raw.githubusercontent.com/Stoupy51/stouputils/refs/heads/main/assets/all_doctests_module.gif :alt: stouputils all_doctests examples"""# Importsimportosimportsysfrom.printimportinfo,error,progressfrom.decoratorsimportmeasure_time,handle_error,LogLevelsfrom.importdecoratorsfromdoctestimportTestResults,testmodfromtypesimportModuleTypeimportimportlibimportpkgutildeftest_module_with_progress(module:ModuleType,separator:str)->TestResults:@measure_time(progress,message=f"Testing module '{module.__name__}' {separator}took")definternal()->TestResults:returntestmod(m=module)returninternal()# Main program
[docs]deflaunch_tests(root_dir:str,importing_errors:LogLevels=LogLevels.WARNING_TRACEBACK,strict:bool=True)->None:""" Main function to launch tests for all modules in the given directory. Args: root_dir (str): Root directory to search for modules importing_errors (LogLevels): Log level for the errors when importing modules strict (bool): Modify the force_raise_exception variable to True in the decorators module Examples: >>> launch_tests("unknown_dir") Traceback (most recent call last): ... ValueError: No modules found in 'unknown_dir' .. code-block:: python > launch_tests("/path/to/source") [PROGRESS HH:MM:SS] Importing module 'module1' took 0.001s [PROGRESS HH:MM:SS] Importing module 'module2' took 0.002s [PROGRESS HH:MM:SS] Importing module 'module3' took 0.003s [PROGRESS HH:MM:SS] Importing module 'module4' took 0.004s [INFO HH:MM:SS] Testing 4 modules... [PROGRESS HH:MM:SS] Testing module 'module1' took 0.005s [PROGRESS HH:MM:SS] Testing module 'module2' took 0.006s [PROGRESS HH:MM:SS] Testing module 'module3' took 0.007s [PROGRESS HH:MM:SS] Testing module 'module4' took 0.008s """ifstrict:old_value:bool=strictdecorators.force_raise_exception=Truestrict=old_value# Get all modules from foldersys.path.insert(0,root_dir)modules_file_paths:list[str]=[]fordirectory_path,_,_inos.walk(root_dir):formodule_infoinpkgutil.walk_packages([directory_path]):absolute_module_path:str=os.path.join(directory_path,module_info.name)path:str=absolute_module_path.split(root_dir,1)[1].replace(os.sep,".")[1:]ifpathnotinmodules_file_paths:modules_file_paths.append(path)ifnotmodules_file_paths:raiseValueError(f"No modules found in '{root_dir}'")# Find longest module path for alignmentmax_length:int=max(len(path)forpathinmodules_file_paths)# Dynamically import all modules from iacob package recursively using pkgutil and importlibmodules:list[ModuleType]=[]separators:list[str]=[]formodule_pathinmodules_file_paths:separator:str=" "*(max_length-len(module_path))@handle_error(error_log=importing_errors)@measure_time(progress,message=f"Importing module '{module_path}' {separator}took")definternal()->None:modules.append(importlib.import_module(module_path))separators.append(separator)internal()# Run tests for each moduleinfo(f"Testing {len(modules)} modules...")separators=[s+" "*(len("Importing")-len("Testing"))forsinseparators]results:list[TestResults]=[test_module_with_progress(module,separator)formodule,separatorinzip(modules,separators)]# Display any error lines for each module at the end of the scriptformodule,resultinzip(modules,results):ifresult.failed>0:error(f"Errors in module {module.__name__}",exit=False)# Reset force_raise_exception backdecorators.force_raise_exception=strict