Coverage for pyquickhelper/pycode/pytest_helper.py: 100%
58 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-03 02:21 +0200
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-03 02:21 +0200
1"""
2@file
3@brief :epkg:`pytest` is sometimes slow. This file provides helpers to
4easily run test function.
5"""
6import os
7import importlib
8import re
9import warnings
10import time
11from traceback import format_exc
12from inspect import signature
15class TestExecutionError(RuntimeError):
16 """
17 Raised when the execution of a test fails.
18 """
20 def __init__(self, module, name, exc):
21 """
22 @param module module
23 @param name function name
24 @param exc exception
25 """
26 if name is None and isinstance(exc, list):
27 msg = "Test module '{}' failed\n---\n{}".format(
28 module.__name__, "\n---\n".join(str(_) for _ in exc))
29 RuntimeError.__init__(self, msg)
30 elif name is not None:
31 msg = "Function '{}' from module '{}' failed due to '{}'\n{}".format(
32 name, module.__name__, exc, format_exc())
33 RuntimeError.__init__(self, msg)
34 else:
35 raise RuntimeError( # pragma: no cover
36 "Unknown test error.")
39def run_test_function(module, pattern="^test_.*", stop_first=False, verbose=False, fLOG=print):
40 """
41 Runs test functions from *module*.
43 :param module: module (string or module)
44 :param pattern: function pattern
45 :param stop_first: stops at the first error or run all of them
46 :param verbose: prints out the name of the functions
47 :param fLOG: logging function
49 The following piece of code could also be used to
50 run all tests not using any parameter.
52 ::
54 fcts = [v for k, v in locals().items() if k.startswith('test_')]
55 for fct in fcts:
56 print("run", fct.__name__)
57 try:
58 fct()
59 except Exception as e:
60 if 'missing' in str(e):
61 print(e)
62 continue
63 raise e
64 """
65 if isinstance(module, str):
66 module_path = module
67 module = os.path.splitext(module)[0]
68 _, module_name = os.path.split(os.path.splitext(module)[0])
69 with warnings.catch_warnings(record=False):
70 spec = importlib.util.spec_from_file_location(
71 module_name, module_path)
72 if spec is None:
73 raise ImportError( # pragma: no cover
74 f"Cannot import module '{module_name}' from '{module_path}'.")
75 module = importlib.util.module_from_spec(spec)
76 spec.loader.exec_module(module)
77 if module is None:
78 raise ValueError( # pragma: no cover
79 "module must be specified.")
81 reg = re.compile(pattern)
82 fcts = []
83 for name, fct in module.__dict__.items():
84 if not reg.search(name):
85 continue
86 if not callable(fct):
87 continue # pragma: no cover
88 sig = signature(fct)
89 if sig.parameters:
90 continue
91 fcts.append((name, fct))
93 excs = []
94 tested = []
95 i = 0
96 for name, fct in fcts:
98 t0 = time.perf_counter()
99 with warnings.catch_warnings(record=False):
100 try:
101 fct()
102 exc = None
103 except Exception as e:
104 exc = TestExecutionError(module, name, e)
105 if stop_first:
106 raise exc
107 excs.append(exc)
108 dt = time.perf_counter() - t0
109 if verbose:
110 fLOG( # pragma: no cover
111 "[run_test_function] {}/{}: {} '{}' in {:0.000}s".format(
112 i + 1, len(fcts), 'OK' if exc is None else '--', name, dt))
113 tested.append(name)
114 i += 1
116 if len(excs) > 0:
117 raise TestExecutionError(module, None, excs)
118 if len(tested) == 0:
119 raise ValueError(
120 f"No function found in '{module}' with pattern '{pattern}'.")