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

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 

13 

14 

15class TestExecutionError(RuntimeError): 

16 """ 

17 Raised when the execution of a test fails. 

18 """ 

19 

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.") 

37 

38 

39def run_test_function(module, pattern="^test_.*", stop_first=False, verbose=False, fLOG=print): 

40 """ 

41 Runs test functions from *module*. 

42 

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 

48 

49 The following piece of code could also be used to 

50 run all tests not using any parameter. 

51 

52 :: 

53 

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.") 

80 

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)) 

92 

93 excs = [] 

94 tested = [] 

95 i = 0 

96 for name, fct in fcts: 

97 

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 

115 

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}'.")