| 1 | n/a | import errno |
|---|
| 2 | n/a | import os |
|---|
| 3 | n/a | import re |
|---|
| 4 | n/a | import sys |
|---|
| 5 | n/a | import warnings |
|---|
| 6 | n/a | from inspect import isabstract |
|---|
| 7 | n/a | from test import support |
|---|
| 8 | n/a | |
|---|
| 9 | n/a | |
|---|
| 10 | n/a | try: |
|---|
| 11 | n/a | MAXFD = os.sysconf("SC_OPEN_MAX") |
|---|
| 12 | n/a | except Exception: |
|---|
| 13 | n/a | MAXFD = 256 |
|---|
| 14 | n/a | |
|---|
| 15 | n/a | |
|---|
| 16 | n/a | def fd_count(): |
|---|
| 17 | n/a | """Count the number of open file descriptors""" |
|---|
| 18 | n/a | if sys.platform.startswith(('linux', 'freebsd')): |
|---|
| 19 | n/a | try: |
|---|
| 20 | n/a | names = os.listdir("/proc/self/fd") |
|---|
| 21 | n/a | return len(names) |
|---|
| 22 | n/a | except FileNotFoundError: |
|---|
| 23 | n/a | pass |
|---|
| 24 | n/a | |
|---|
| 25 | n/a | count = 0 |
|---|
| 26 | n/a | for fd in range(MAXFD): |
|---|
| 27 | n/a | try: |
|---|
| 28 | n/a | # Prefer dup() over fstat(). fstat() can require input/output |
|---|
| 29 | n/a | # whereas dup() doesn't. |
|---|
| 30 | n/a | fd2 = os.dup(fd) |
|---|
| 31 | n/a | except OSError as e: |
|---|
| 32 | n/a | if e.errno != errno.EBADF: |
|---|
| 33 | n/a | raise |
|---|
| 34 | n/a | else: |
|---|
| 35 | n/a | os.close(fd2) |
|---|
| 36 | n/a | count += 1 |
|---|
| 37 | n/a | return count |
|---|
| 38 | n/a | |
|---|
| 39 | n/a | |
|---|
| 40 | n/a | def dash_R(the_module, test, indirect_test, huntrleaks): |
|---|
| 41 | n/a | """Run a test multiple times, looking for reference leaks. |
|---|
| 42 | n/a | |
|---|
| 43 | n/a | Returns: |
|---|
| 44 | n/a | False if the test didn't leak references; True if we detected refleaks. |
|---|
| 45 | n/a | """ |
|---|
| 46 | n/a | # This code is hackish and inelegant, but it seems to do the job. |
|---|
| 47 | n/a | import copyreg |
|---|
| 48 | n/a | import collections.abc |
|---|
| 49 | n/a | |
|---|
| 50 | n/a | if not hasattr(sys, 'gettotalrefcount'): |
|---|
| 51 | n/a | raise Exception("Tracking reference leaks requires a debug build " |
|---|
| 52 | n/a | "of Python") |
|---|
| 53 | n/a | |
|---|
| 54 | n/a | # Save current values for dash_R_cleanup() to restore. |
|---|
| 55 | n/a | fs = warnings.filters[:] |
|---|
| 56 | n/a | ps = copyreg.dispatch_table.copy() |
|---|
| 57 | n/a | pic = sys.path_importer_cache.copy() |
|---|
| 58 | n/a | try: |
|---|
| 59 | n/a | import zipimport |
|---|
| 60 | n/a | except ImportError: |
|---|
| 61 | n/a | zdc = None # Run unmodified on platforms without zipimport support |
|---|
| 62 | n/a | else: |
|---|
| 63 | n/a | zdc = zipimport._zip_directory_cache.copy() |
|---|
| 64 | n/a | abcs = {} |
|---|
| 65 | n/a | for abc in [getattr(collections.abc, a) for a in collections.abc.__all__]: |
|---|
| 66 | n/a | if not isabstract(abc): |
|---|
| 67 | n/a | continue |
|---|
| 68 | n/a | for obj in abc.__subclasses__() + [abc]: |
|---|
| 69 | n/a | abcs[obj] = obj._abc_registry.copy() |
|---|
| 70 | n/a | |
|---|
| 71 | n/a | nwarmup, ntracked, fname = huntrleaks |
|---|
| 72 | n/a | fname = os.path.join(support.SAVEDCWD, fname) |
|---|
| 73 | n/a | repcount = nwarmup + ntracked |
|---|
| 74 | n/a | rc_deltas = [0] * repcount |
|---|
| 75 | n/a | alloc_deltas = [0] * repcount |
|---|
| 76 | n/a | fd_deltas = [0] * repcount |
|---|
| 77 | n/a | |
|---|
| 78 | n/a | print("beginning", repcount, "repetitions", file=sys.stderr) |
|---|
| 79 | n/a | print(("1234567890"*(repcount//10 + 1))[:repcount], file=sys.stderr, |
|---|
| 80 | n/a | flush=True) |
|---|
| 81 | n/a | # initialize variables to make pyflakes quiet |
|---|
| 82 | n/a | rc_before = alloc_before = fd_before = 0 |
|---|
| 83 | n/a | for i in range(repcount): |
|---|
| 84 | n/a | indirect_test() |
|---|
| 85 | n/a | alloc_after, rc_after, fd_after = dash_R_cleanup(fs, ps, pic, zdc, |
|---|
| 86 | n/a | abcs) |
|---|
| 87 | n/a | print('.', end='', file=sys.stderr, flush=True) |
|---|
| 88 | n/a | if i >= nwarmup: |
|---|
| 89 | n/a | rc_deltas[i] = rc_after - rc_before |
|---|
| 90 | n/a | alloc_deltas[i] = alloc_after - alloc_before |
|---|
| 91 | n/a | fd_deltas[i] = fd_after - fd_before |
|---|
| 92 | n/a | alloc_before = alloc_after |
|---|
| 93 | n/a | rc_before = rc_after |
|---|
| 94 | n/a | fd_before = fd_after |
|---|
| 95 | n/a | print(file=sys.stderr) |
|---|
| 96 | n/a | # These checkers return False on success, True on failure |
|---|
| 97 | n/a | def check_rc_deltas(deltas): |
|---|
| 98 | n/a | return any(deltas) |
|---|
| 99 | n/a | def check_alloc_deltas(deltas): |
|---|
| 100 | n/a | # At least 1/3rd of 0s |
|---|
| 101 | n/a | if 3 * deltas.count(0) < len(deltas): |
|---|
| 102 | n/a | return True |
|---|
| 103 | n/a | # Nothing else than 1s, 0s and -1s |
|---|
| 104 | n/a | if not set(deltas) <= {1,0,-1}: |
|---|
| 105 | n/a | return True |
|---|
| 106 | n/a | return False |
|---|
| 107 | n/a | failed = False |
|---|
| 108 | n/a | for deltas, item_name, checker in [ |
|---|
| 109 | n/a | (rc_deltas, 'references', check_rc_deltas), |
|---|
| 110 | n/a | (alloc_deltas, 'memory blocks', check_alloc_deltas), |
|---|
| 111 | n/a | (fd_deltas, 'file descriptors', check_rc_deltas)]: |
|---|
| 112 | n/a | if checker(deltas): |
|---|
| 113 | n/a | msg = '%s leaked %s %s, sum=%s' % ( |
|---|
| 114 | n/a | test, deltas[nwarmup:], item_name, sum(deltas)) |
|---|
| 115 | n/a | print(msg, file=sys.stderr, flush=True) |
|---|
| 116 | n/a | with open(fname, "a") as refrep: |
|---|
| 117 | n/a | print(msg, file=refrep) |
|---|
| 118 | n/a | refrep.flush() |
|---|
| 119 | n/a | failed = True |
|---|
| 120 | n/a | return failed |
|---|
| 121 | n/a | |
|---|
| 122 | n/a | |
|---|
| 123 | n/a | def dash_R_cleanup(fs, ps, pic, zdc, abcs): |
|---|
| 124 | n/a | import gc, copyreg |
|---|
| 125 | n/a | import collections.abc |
|---|
| 126 | n/a | from weakref import WeakSet |
|---|
| 127 | n/a | |
|---|
| 128 | n/a | # Restore some original values. |
|---|
| 129 | n/a | warnings.filters[:] = fs |
|---|
| 130 | n/a | copyreg.dispatch_table.clear() |
|---|
| 131 | n/a | copyreg.dispatch_table.update(ps) |
|---|
| 132 | n/a | sys.path_importer_cache.clear() |
|---|
| 133 | n/a | sys.path_importer_cache.update(pic) |
|---|
| 134 | n/a | try: |
|---|
| 135 | n/a | import zipimport |
|---|
| 136 | n/a | except ImportError: |
|---|
| 137 | n/a | pass # Run unmodified on platforms without zipimport support |
|---|
| 138 | n/a | else: |
|---|
| 139 | n/a | zipimport._zip_directory_cache.clear() |
|---|
| 140 | n/a | zipimport._zip_directory_cache.update(zdc) |
|---|
| 141 | n/a | |
|---|
| 142 | n/a | # clear type cache |
|---|
| 143 | n/a | sys._clear_type_cache() |
|---|
| 144 | n/a | |
|---|
| 145 | n/a | # Clear ABC registries, restoring previously saved ABC registries. |
|---|
| 146 | n/a | for abc in [getattr(collections.abc, a) for a in collections.abc.__all__]: |
|---|
| 147 | n/a | if not isabstract(abc): |
|---|
| 148 | n/a | continue |
|---|
| 149 | n/a | for obj in abc.__subclasses__() + [abc]: |
|---|
| 150 | n/a | obj._abc_registry = abcs.get(obj, WeakSet()).copy() |
|---|
| 151 | n/a | obj._abc_cache.clear() |
|---|
| 152 | n/a | obj._abc_negative_cache.clear() |
|---|
| 153 | n/a | |
|---|
| 154 | n/a | clear_caches() |
|---|
| 155 | n/a | |
|---|
| 156 | n/a | # Collect cyclic trash and read memory statistics immediately after. |
|---|
| 157 | n/a | func1 = sys.getallocatedblocks |
|---|
| 158 | n/a | func2 = sys.gettotalrefcount |
|---|
| 159 | n/a | gc.collect() |
|---|
| 160 | n/a | return func1(), func2(), fd_count() |
|---|
| 161 | n/a | |
|---|
| 162 | n/a | |
|---|
| 163 | n/a | def clear_caches(): |
|---|
| 164 | n/a | import gc |
|---|
| 165 | n/a | |
|---|
| 166 | n/a | # Clear the warnings registry, so they can be displayed again |
|---|
| 167 | n/a | for mod in sys.modules.values(): |
|---|
| 168 | n/a | if hasattr(mod, '__warningregistry__'): |
|---|
| 169 | n/a | del mod.__warningregistry__ |
|---|
| 170 | n/a | |
|---|
| 171 | n/a | # Flush standard output, so that buffered data is sent to the OS and |
|---|
| 172 | n/a | # associated Python objects are reclaimed. |
|---|
| 173 | n/a | for stream in (sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__): |
|---|
| 174 | n/a | if stream is not None: |
|---|
| 175 | n/a | stream.flush() |
|---|
| 176 | n/a | |
|---|
| 177 | n/a | # Clear assorted module caches. |
|---|
| 178 | n/a | # Don't worry about resetting the cache if the module is not loaded |
|---|
| 179 | n/a | try: |
|---|
| 180 | n/a | distutils_dir_util = sys.modules['distutils.dir_util'] |
|---|
| 181 | n/a | except KeyError: |
|---|
| 182 | n/a | pass |
|---|
| 183 | n/a | else: |
|---|
| 184 | n/a | distutils_dir_util._path_created.clear() |
|---|
| 185 | n/a | re.purge() |
|---|
| 186 | n/a | |
|---|
| 187 | n/a | try: |
|---|
| 188 | n/a | _strptime = sys.modules['_strptime'] |
|---|
| 189 | n/a | except KeyError: |
|---|
| 190 | n/a | pass |
|---|
| 191 | n/a | else: |
|---|
| 192 | n/a | _strptime._regex_cache.clear() |
|---|
| 193 | n/a | |
|---|
| 194 | n/a | try: |
|---|
| 195 | n/a | urllib_parse = sys.modules['urllib.parse'] |
|---|
| 196 | n/a | except KeyError: |
|---|
| 197 | n/a | pass |
|---|
| 198 | n/a | else: |
|---|
| 199 | n/a | urllib_parse.clear_cache() |
|---|
| 200 | n/a | |
|---|
| 201 | n/a | try: |
|---|
| 202 | n/a | urllib_request = sys.modules['urllib.request'] |
|---|
| 203 | n/a | except KeyError: |
|---|
| 204 | n/a | pass |
|---|
| 205 | n/a | else: |
|---|
| 206 | n/a | urllib_request.urlcleanup() |
|---|
| 207 | n/a | |
|---|
| 208 | n/a | try: |
|---|
| 209 | n/a | linecache = sys.modules['linecache'] |
|---|
| 210 | n/a | except KeyError: |
|---|
| 211 | n/a | pass |
|---|
| 212 | n/a | else: |
|---|
| 213 | n/a | linecache.clearcache() |
|---|
| 214 | n/a | |
|---|
| 215 | n/a | try: |
|---|
| 216 | n/a | mimetypes = sys.modules['mimetypes'] |
|---|
| 217 | n/a | except KeyError: |
|---|
| 218 | n/a | pass |
|---|
| 219 | n/a | else: |
|---|
| 220 | n/a | mimetypes._default_mime_types() |
|---|
| 221 | n/a | |
|---|
| 222 | n/a | try: |
|---|
| 223 | n/a | filecmp = sys.modules['filecmp'] |
|---|
| 224 | n/a | except KeyError: |
|---|
| 225 | n/a | pass |
|---|
| 226 | n/a | else: |
|---|
| 227 | n/a | filecmp._cache.clear() |
|---|
| 228 | n/a | |
|---|
| 229 | n/a | try: |
|---|
| 230 | n/a | struct = sys.modules['struct'] |
|---|
| 231 | n/a | except KeyError: |
|---|
| 232 | n/a | pass |
|---|
| 233 | n/a | else: |
|---|
| 234 | n/a | struct._clearcache() |
|---|
| 235 | n/a | |
|---|
| 236 | n/a | try: |
|---|
| 237 | n/a | doctest = sys.modules['doctest'] |
|---|
| 238 | n/a | except KeyError: |
|---|
| 239 | n/a | pass |
|---|
| 240 | n/a | else: |
|---|
| 241 | n/a | doctest.master = None |
|---|
| 242 | n/a | |
|---|
| 243 | n/a | try: |
|---|
| 244 | n/a | ctypes = sys.modules['ctypes'] |
|---|
| 245 | n/a | except KeyError: |
|---|
| 246 | n/a | pass |
|---|
| 247 | n/a | else: |
|---|
| 248 | n/a | ctypes._reset_cache() |
|---|
| 249 | n/a | |
|---|
| 250 | n/a | try: |
|---|
| 251 | n/a | typing = sys.modules['typing'] |
|---|
| 252 | n/a | except KeyError: |
|---|
| 253 | n/a | pass |
|---|
| 254 | n/a | else: |
|---|
| 255 | n/a | for f in typing._cleanups: |
|---|
| 256 | n/a | f() |
|---|
| 257 | n/a | |
|---|
| 258 | n/a | gc.collect() |
|---|
| 259 | n/a | |
|---|
| 260 | n/a | |
|---|
| 261 | n/a | def warm_caches(): |
|---|
| 262 | n/a | # char cache |
|---|
| 263 | n/a | s = bytes(range(256)) |
|---|
| 264 | n/a | for i in range(256): |
|---|
| 265 | n/a | s[i:i+1] |
|---|
| 266 | n/a | # unicode cache |
|---|
| 267 | n/a | [chr(i) for i in range(256)] |
|---|
| 268 | n/a | # int cache |
|---|
| 269 | n/a | list(range(-5, 257)) |
|---|