Runs given code (and init code) n times and checks for memory leaks. Args: desc: A descriptor of the test. init: Optional code to be executed initially. code: The actual code to be checked for producing memory leaks. repeats: How many times to repeatedly execute
(
desc: str,
init: Optional[Callable[[], None]],
code: Callable[[], None],
repeats: int,
max_num_trials: int = 1,
min_memory_increase: int = 0,
)
| 107 | |
| 108 | |
| 109 | def _test_some_code_for_memory_leaks( |
| 110 | desc: str, |
| 111 | init: Optional[Callable[[], None]], |
| 112 | code: Callable[[], None], |
| 113 | repeats: int, |
| 114 | max_num_trials: int = 1, |
| 115 | min_memory_increase: int = 0, |
| 116 | ) -> List[Suspect]: |
| 117 | """Runs given code (and init code) n times and checks for memory leaks. |
| 118 | |
| 119 | Args: |
| 120 | desc: A descriptor of the test. |
| 121 | init: Optional code to be executed initially. |
| 122 | code: The actual code to be checked for producing memory leaks. |
| 123 | repeats: How many times to repeatedly execute `code`. |
| 124 | max_num_trials: The maximum number of trials to run. A new trial is only |
| 125 | run, if the previous one produced a memory leak. For all non-1st trials, |
| 126 | `repeats` calculates as: actual_repeats = `repeats` * (trial + 1), where |
| 127 | the first trial is 0. |
| 128 | min_memory_increase: Minimum total memory increase in bytes for a suspect |
| 129 | to be reported. Use this to ignore small allocations from internal |
| 130 | caches and allocator artifacts that aren't real leaks. |
| 131 | |
| 132 | Returns: |
| 133 | A list of Suspect objects, describing possible memory leaks. If list |
| 134 | is empty, no leaks have been found. |
| 135 | """ |
| 136 | |
| 137 | def _i_print(i): |
| 138 | if (i + 1) % 10 == 0: |
| 139 | print(".", end="" if (i + 1) % 100 else f" {i + 1}\n", flush=True) |
| 140 | |
| 141 | # Do n trials to make sure a found leak is really one. |
| 142 | suspicious = set() |
| 143 | suspicious_stats = [] |
| 144 | for trial in range(max_num_trials): |
| 145 | # Store up to n frames of each call stack. |
| 146 | tracemalloc.start(20) |
| 147 | |
| 148 | table = defaultdict(list) |
| 149 | |
| 150 | # Repeat running code for n times. |
| 151 | # Increase repeat value with each trial to make sure stats are more |
| 152 | # solid each time (avoiding false positives). |
| 153 | actual_repeats = repeats * (trial + 1) |
| 154 | |
| 155 | print(f"{desc} {actual_repeats} times.") |
| 156 | |
| 157 | # Initialize if necessary. |
| 158 | if init is not None: |
| 159 | init() |
| 160 | # Run `code` n times, each time taking a memory snapshot. |
| 161 | for i in range(actual_repeats): |
| 162 | _i_print(i) |
| 163 | # Manually trigger garbage collection before and after code runs in order to |
| 164 | # make tracemalloc snapshots as accurate as possible. |
| 165 | gc.collect() |
| 166 | code() |
searching dependent graphs…