coverage 7.13.1__cp313-cp313-musllinux_1_2_riscv64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. a1_coverage.pth +1 -0
  2. coverage/__init__.py +38 -0
  3. coverage/__main__.py +12 -0
  4. coverage/annotate.py +113 -0
  5. coverage/bytecode.py +197 -0
  6. coverage/cmdline.py +1220 -0
  7. coverage/collector.py +487 -0
  8. coverage/config.py +732 -0
  9. coverage/context.py +74 -0
  10. coverage/control.py +1514 -0
  11. coverage/core.py +139 -0
  12. coverage/data.py +251 -0
  13. coverage/debug.py +669 -0
  14. coverage/disposition.py +59 -0
  15. coverage/env.py +135 -0
  16. coverage/exceptions.py +85 -0
  17. coverage/execfile.py +329 -0
  18. coverage/files.py +553 -0
  19. coverage/html.py +860 -0
  20. coverage/htmlfiles/coverage_html.js +735 -0
  21. coverage/htmlfiles/favicon_32.png +0 -0
  22. coverage/htmlfiles/index.html +199 -0
  23. coverage/htmlfiles/keybd_closed.png +0 -0
  24. coverage/htmlfiles/pyfile.html +149 -0
  25. coverage/htmlfiles/style.css +389 -0
  26. coverage/htmlfiles/style.scss +844 -0
  27. coverage/inorout.py +590 -0
  28. coverage/jsonreport.py +200 -0
  29. coverage/lcovreport.py +218 -0
  30. coverage/misc.py +381 -0
  31. coverage/multiproc.py +120 -0
  32. coverage/numbits.py +146 -0
  33. coverage/parser.py +1215 -0
  34. coverage/patch.py +118 -0
  35. coverage/phystokens.py +197 -0
  36. coverage/plugin.py +617 -0
  37. coverage/plugin_support.py +299 -0
  38. coverage/pth_file.py +16 -0
  39. coverage/py.typed +1 -0
  40. coverage/python.py +272 -0
  41. coverage/pytracer.py +370 -0
  42. coverage/regions.py +127 -0
  43. coverage/report.py +298 -0
  44. coverage/report_core.py +117 -0
  45. coverage/results.py +502 -0
  46. coverage/sqldata.py +1212 -0
  47. coverage/sqlitedb.py +226 -0
  48. coverage/sysmon.py +509 -0
  49. coverage/templite.py +319 -0
  50. coverage/tomlconfig.py +212 -0
  51. coverage/tracer.cpython-313-riscv64-linux-musl.so +0 -0
  52. coverage/tracer.pyi +43 -0
  53. coverage/types.py +214 -0
  54. coverage/version.py +35 -0
  55. coverage/xmlreport.py +263 -0
  56. coverage-7.13.1.dist-info/METADATA +200 -0
  57. coverage-7.13.1.dist-info/RECORD +61 -0
  58. coverage-7.13.1.dist-info/WHEEL +5 -0
  59. coverage-7.13.1.dist-info/entry_points.txt +4 -0
  60. coverage-7.13.1.dist-info/licenses/LICENSE.txt +177 -0
  61. coverage-7.13.1.dist-info/top_level.txt +1 -0
coverage/misc.py ADDED
@@ -0,0 +1,381 @@
1
+ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2
+ # For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
3
+
4
+ """Miscellaneous stuff for coverage.py."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import contextlib
9
+ import datetime
10
+ import errno
11
+ import functools
12
+ import hashlib
13
+ import importlib
14
+ import importlib.util
15
+ import inspect
16
+ import os
17
+ import os.path
18
+ import re
19
+ import sys
20
+ import types
21
+ from collections.abc import Iterable, Iterator, Mapping, Sequence
22
+ from types import ModuleType
23
+ from typing import Any, NoReturn, TypeVar
24
+
25
+ # In 6.0, the exceptions moved from misc.py to exceptions.py. But a number of
26
+ # other packages were importing the exceptions from misc, so import them here.
27
+ # pylint: disable=unused-wildcard-import
28
+ from coverage.exceptions import * # pylint: disable=wildcard-import
29
+ from coverage.exceptions import CoverageException
30
+ from coverage.types import TArc
31
+
32
+ ISOLATED_MODULES: dict[ModuleType, ModuleType] = {}
33
+
34
+
35
+ def isolate_module(mod: ModuleType) -> ModuleType:
36
+ """Copy a module so that we are isolated from aggressive mocking.
37
+
38
+ If a test suite mocks os.path.exists (for example), and then we need to use
39
+ it during the test, everything will get tangled up if we use their mock.
40
+ Making a copy of the module when we import it will isolate coverage.py from
41
+ those complications.
42
+ """
43
+ if mod not in ISOLATED_MODULES:
44
+ new_mod = types.ModuleType(mod.__name__)
45
+ ISOLATED_MODULES[mod] = new_mod
46
+ for name in dir(mod):
47
+ value = getattr(mod, name)
48
+ if isinstance(value, types.ModuleType):
49
+ value = isolate_module(value)
50
+ setattr(new_mod, name, value)
51
+ return ISOLATED_MODULES[mod]
52
+
53
+
54
+ os = isolate_module(os)
55
+
56
+
57
+ class SysModuleSaver:
58
+ """Saves the contents of sys.modules, and removes new modules later."""
59
+
60
+ def __init__(self) -> None:
61
+ self.old_modules = set(sys.modules)
62
+
63
+ def restore(self) -> None:
64
+ """Remove any modules imported since this object started."""
65
+ new_modules = set(sys.modules) - self.old_modules
66
+ for m in new_modules:
67
+ del sys.modules[m]
68
+
69
+
70
+ @contextlib.contextmanager
71
+ def sys_modules_saved() -> Iterator[None]:
72
+ """A context manager to remove any modules imported during a block."""
73
+ saver = SysModuleSaver()
74
+ try:
75
+ yield
76
+ finally:
77
+ saver.restore()
78
+
79
+
80
+ def import_third_party(modname: str) -> tuple[ModuleType, bool]:
81
+ """Import a third-party module we need, but might not be installed.
82
+
83
+ This also cleans out the module after the import, so that coverage won't
84
+ appear to have imported it. This lets the third party use coverage for
85
+ their own tests.
86
+
87
+ Arguments:
88
+ modname (str): the name of the module to import.
89
+
90
+ Returns:
91
+ The imported module, and a boolean indicating if the module could be imported.
92
+
93
+ If the boolean is False, the module returned is not the one you want: don't use it.
94
+
95
+ """
96
+ with sys_modules_saved():
97
+ try:
98
+ return importlib.import_module(modname), True
99
+ except ImportError:
100
+ return sys, False
101
+
102
+
103
+ def nice_pair(pair: TArc) -> str:
104
+ """Make a nice string representation of a pair of numbers.
105
+
106
+ If the numbers are equal, just return the number, otherwise return the pair
107
+ with a dash between them, indicating the range.
108
+
109
+ """
110
+ start, end = pair
111
+ if start == end:
112
+ return f"{start}"
113
+ else:
114
+ return f"{start}-{end}"
115
+
116
+
117
+ def bool_or_none(b: Any) -> bool | None:
118
+ """Return bool(b), but preserve None."""
119
+ if b is None:
120
+ return None
121
+ else:
122
+ return bool(b)
123
+
124
+
125
+ def join_regex(regexes: Iterable[str]) -> str:
126
+ """Combine a series of regex strings into one that matches any of them."""
127
+ regexes = list(regexes)
128
+ if len(regexes) == 1:
129
+ return regexes[0]
130
+ else:
131
+ return "|".join(f"(?:{r})" for r in regexes)
132
+
133
+
134
+ def file_be_gone(path: str) -> None:
135
+ """Remove a file, and don't get annoyed if it doesn't exist."""
136
+ try:
137
+ os.remove(path)
138
+ except OSError as e:
139
+ if e.errno != errno.ENOENT:
140
+ raise
141
+
142
+
143
+ def ensure_dir(directory: str) -> None:
144
+ """Make sure the directory exists.
145
+
146
+ If `directory` is None or empty, do nothing.
147
+ """
148
+ if directory:
149
+ os.makedirs(directory, exist_ok=True)
150
+
151
+
152
+ def ensure_dir_for_file(path: str) -> None:
153
+ """Make sure the directory for the path exists."""
154
+ ensure_dir(os.path.dirname(path))
155
+
156
+
157
+ class Hasher:
158
+ """Hashes Python data for fingerprinting."""
159
+
160
+ def __init__(self) -> None:
161
+ self.hash = hashlib.new("sha3_256", usedforsecurity=False)
162
+
163
+ def update(self, v: Any) -> None:
164
+ """Add `v` to the hash, recursively if needed."""
165
+ self.hash.update(str(type(v)).encode("utf-8"))
166
+ match v:
167
+ case None:
168
+ pass
169
+ case str():
170
+ self.hash.update(f"{len(v)}:".encode("utf-8"))
171
+ self.hash.update(v.encode("utf-8"))
172
+ case bytes():
173
+ self.hash.update(f"{len(v)}:".encode("utf-8"))
174
+ self.hash.update(v)
175
+ case int() | float():
176
+ self.hash.update(str(v).encode("utf-8"))
177
+ case tuple() | list():
178
+ for e in v:
179
+ self.update(e)
180
+ case dict():
181
+ for k, kv in sorted(v.items()):
182
+ self.update(k)
183
+ self.update(kv)
184
+ case set():
185
+ for e in sorted(v):
186
+ self.update(e)
187
+ case _:
188
+ for k in dir(v):
189
+ if k.startswith("__"):
190
+ continue
191
+ a = getattr(v, k)
192
+ if inspect.isroutine(a):
193
+ continue
194
+ self.update(k)
195
+ self.update(a)
196
+ self.hash.update(b".")
197
+
198
+ def digest(self) -> bytes:
199
+ """Get the full binary digest of the hash."""
200
+ return self.hash.digest()
201
+
202
+ def hexdigest(self) -> str:
203
+ """Retrieve a 32-char hex digest of the hash."""
204
+ return self.hash.hexdigest()[:32]
205
+
206
+
207
+ def _needs_to_implement(that: Any, func_name: str) -> NoReturn:
208
+ """Helper to raise NotImplementedError in interface stubs."""
209
+ if hasattr(that, "_coverage_plugin_name"):
210
+ thing = "Plugin"
211
+ name = that._coverage_plugin_name
212
+ else:
213
+ thing = "Class"
214
+ klass = that.__class__
215
+ name = f"{klass.__module__}.{klass.__name__}"
216
+
217
+ raise NotImplementedError(
218
+ f"{thing} {name!r} needs to implement {func_name}()",
219
+ )
220
+
221
+
222
+ class DefaultValue:
223
+ """A sentinel object to use for unusual default-value needs.
224
+
225
+ Construct with a string that will be used as the repr, for display in help
226
+ and Sphinx output.
227
+
228
+ """
229
+
230
+ def __init__(self, display_as: str) -> None:
231
+ self.display_as = display_as
232
+
233
+ def __repr__(self) -> str:
234
+ return self.display_as
235
+
236
+
237
+ def substitute_variables(text: str, variables: Mapping[str, str]) -> str:
238
+ """Substitute ``${VAR}`` variables in `text` with their values.
239
+
240
+ Variables in the text can take a number of shell-inspired forms::
241
+
242
+ $VAR
243
+ ${VAR}
244
+ ${VAR?} strict: an error if VAR isn't defined.
245
+ ${VAR-missing} defaulted: "missing" if VAR isn't defined.
246
+ $$ just a dollar sign.
247
+
248
+ `variables` is a dictionary of variable values.
249
+
250
+ Returns the resulting text with values substituted.
251
+
252
+ """
253
+ dollar_pattern = r"""(?x) # Use extended regex syntax
254
+ \$ # A dollar sign,
255
+ (?: # then
256
+ (?P<dollar> \$ ) | # a dollar sign, or
257
+ (?P<word1> \w+ ) | # a plain word, or
258
+ \{ # a {-wrapped
259
+ (?P<word2> \w+ ) # word,
260
+ (?: # either
261
+ (?P<strict> \? ) | # with a strict marker
262
+ -(?P<defval> [^}]* ) # or a default value
263
+ )? # maybe.
264
+ }
265
+ )
266
+ """
267
+
268
+ dollar_groups = ("dollar", "word1", "word2")
269
+
270
+ def dollar_replace(match: re.Match[str]) -> str:
271
+ """Called for each $replacement."""
272
+ # Only one of the dollar_groups will have matched, just get its text.
273
+ word = next(g for g in match.group(*dollar_groups) if g) # pragma: always breaks
274
+ if word == "$":
275
+ return "$"
276
+ elif word in variables:
277
+ return variables[word]
278
+ elif match["strict"]:
279
+ msg = f"Variable {word} is undefined: {text!r}"
280
+ raise CoverageException(msg)
281
+ else:
282
+ return match["defval"]
283
+
284
+ text = re.sub(dollar_pattern, dollar_replace, text)
285
+ return text
286
+
287
+
288
+ def format_local_datetime(dt: datetime.datetime) -> str:
289
+ """Return a string with local timezone representing the date."""
290
+ return dt.astimezone().strftime("%Y-%m-%d %H:%M %z")
291
+
292
+
293
+ def import_local_file(modname: str, modfile: str | None = None) -> ModuleType:
294
+ """Import a local file as a module.
295
+
296
+ Opens a file in the current directory named `modname`.py, imports it
297
+ as `modname`, and returns the module object. `modfile` is the file to
298
+ import if it isn't in the current directory.
299
+
300
+ """
301
+ if modfile is None:
302
+ modfile = modname + ".py"
303
+ spec = importlib.util.spec_from_file_location(modname, modfile)
304
+ assert spec is not None
305
+ mod = importlib.util.module_from_spec(spec)
306
+ sys.modules[modname] = mod
307
+ assert spec.loader is not None
308
+ spec.loader.exec_module(mod)
309
+
310
+ return mod
311
+
312
+
313
+ @functools.cache
314
+ def _human_key(s: str) -> tuple[list[str | int], str]:
315
+ """Turn a string into a list of string and number chunks.
316
+
317
+ "z23a" -> (["z", 23, "a"], "z23a")
318
+
319
+ The original string is appended as a last value to ensure the
320
+ key is unique enough so that "x1y" and "x001y" can be distinguished.
321
+ """
322
+
323
+ def tryint(s: str) -> str | int:
324
+ """If `s` is a number, return an int, else `s` unchanged."""
325
+ try:
326
+ return int(s)
327
+ except ValueError:
328
+ return s
329
+
330
+ return ([tryint(c) for c in re.split(r"(\d+)", s)], s)
331
+
332
+
333
+ def human_sorted(strings: Iterable[str]) -> list[str]:
334
+ """Sort the given iterable of strings the way that humans expect.
335
+
336
+ Numeric components in the strings are sorted as numbers.
337
+
338
+ Returns the sorted list.
339
+
340
+ """
341
+ return sorted(strings, key=_human_key)
342
+
343
+
344
+ SortableItem = TypeVar("SortableItem", bound=Sequence[Any])
345
+
346
+
347
+ def human_sorted_items(
348
+ items: Iterable[SortableItem],
349
+ reverse: bool = False,
350
+ ) -> list[SortableItem]:
351
+ """Sort (string, ...) items the way humans expect.
352
+
353
+ The elements of `items` can be any tuple/list. They'll be sorted by the
354
+ first element (a string), with ties broken by the remaining elements.
355
+
356
+ Returns the sorted list of items.
357
+ """
358
+ return sorted(items, key=lambda item: (_human_key(item[0]), *item[1:]), reverse=reverse)
359
+
360
+
361
+ def plural(n: int, thing: str = "", things: str = "") -> str:
362
+ """Pluralize a word.
363
+
364
+ If n is 1, return thing. Otherwise return things, or thing+s.
365
+ """
366
+ if n == 1:
367
+ return thing
368
+ else:
369
+ return things or (thing + "s")
370
+
371
+
372
+ def stdout_link(text: str, url: str) -> str:
373
+ """Format text+url as a clickable link for stdout.
374
+
375
+ If attached to a terminal, use escape sequences. Otherwise, just return
376
+ the text.
377
+ """
378
+ if hasattr(sys.stdout, "isatty") and sys.stdout.isatty():
379
+ return f"\033]8;;{url}\a{text}\033]8;;\a"
380
+ else:
381
+ return text
coverage/multiproc.py ADDED
@@ -0,0 +1,120 @@
1
+ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2
+ # For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
3
+
4
+ """Monkey-patching to add multiprocessing support for coverage.py"""
5
+
6
+ from __future__ import annotations
7
+
8
+ import multiprocessing
9
+ import multiprocessing.process
10
+ import os
11
+ import os.path
12
+ import sys
13
+ import traceback
14
+ from typing import Any
15
+
16
+ from coverage.debug import DebugControl
17
+
18
+ # An attribute that will be set on the module to indicate that it has been
19
+ # monkey-patched.
20
+ PATCHED_MARKER = "_coverage$patched"
21
+
22
+
23
+ OriginalProcess = multiprocessing.process.BaseProcess
24
+ original_bootstrap = OriginalProcess._bootstrap # type: ignore[attr-defined]
25
+
26
+
27
+ class ProcessWithCoverage(OriginalProcess): # pylint: disable=abstract-method
28
+ """A replacement for multiprocess.Process that starts coverage."""
29
+
30
+ def _bootstrap(self, *args, **kwargs): # type: ignore[no-untyped-def]
31
+ """Wrapper around _bootstrap to start coverage."""
32
+ debug: DebugControl | None = None
33
+ try:
34
+ from coverage import Coverage # avoid circular import
35
+
36
+ cov = Coverage(data_suffix=True, auto_data=True)
37
+ cov._warn_preimported_source = False
38
+ cov.start()
39
+ _debug = cov._debug
40
+ assert _debug is not None
41
+ if _debug.should("multiproc"):
42
+ debug = _debug
43
+ if debug:
44
+ debug.write("Calling multiprocessing bootstrap")
45
+ except Exception:
46
+ print("Exception during multiprocessing bootstrap init:", file=sys.stderr)
47
+ traceback.print_exc(file=sys.stderr)
48
+ sys.stderr.flush()
49
+ raise
50
+ try:
51
+ return original_bootstrap(self, *args, **kwargs)
52
+ finally:
53
+ if debug:
54
+ debug.write("Finished multiprocessing bootstrap")
55
+ try:
56
+ cov.stop()
57
+ cov.save()
58
+ except Exception as exc:
59
+ if debug:
60
+ debug.write("Exception during multiprocessing bootstrap cleanup", exc=exc)
61
+ raise
62
+ if debug:
63
+ debug.write("Saved multiprocessing data")
64
+
65
+
66
+ class Stowaway:
67
+ """An object to pickle, so when it is unpickled, it can apply the monkey-patch."""
68
+
69
+ def __init__(self, rcfile: str) -> None:
70
+ self.rcfile = rcfile
71
+
72
+ def __getstate__(self) -> dict[str, str]:
73
+ return {"rcfile": self.rcfile}
74
+
75
+ def __setstate__(self, state: dict[str, str]) -> None:
76
+ patch_multiprocessing(state["rcfile"])
77
+
78
+
79
+ def patch_multiprocessing(rcfile: str) -> None:
80
+ """Monkey-patch the multiprocessing module.
81
+
82
+ This enables coverage measurement of processes started by multiprocessing.
83
+ This involves aggressive monkey-patching.
84
+
85
+ `rcfile` is the path to the rcfile being used.
86
+
87
+ """
88
+
89
+ if hasattr(multiprocessing, PATCHED_MARKER):
90
+ return
91
+
92
+ OriginalProcess._bootstrap = ProcessWithCoverage._bootstrap # type: ignore[attr-defined]
93
+
94
+ # Set the value in ProcessWithCoverage that will be pickled into the child
95
+ # process.
96
+ os.environ["COVERAGE_RCFILE"] = os.path.abspath(rcfile)
97
+
98
+ # When spawning processes rather than forking them, we have no state in the
99
+ # new process. We sneak in there with a Stowaway: we stuff one of our own
100
+ # objects into the data that gets pickled and sent to the subprocess. When
101
+ # the Stowaway is unpickled, its __setstate__ method is called, which
102
+ # re-applies the monkey-patch.
103
+ # Windows only spawns, so this is needed to keep Windows working.
104
+ try:
105
+ from multiprocessing import spawn
106
+
107
+ original_get_preparation_data = spawn.get_preparation_data
108
+ except (ImportError, AttributeError):
109
+ pass
110
+ else:
111
+
112
+ def get_preparation_data_with_stowaway(name: str) -> dict[str, Any]:
113
+ """Get the original preparation data, and also insert our stowaway."""
114
+ d = original_get_preparation_data(name)
115
+ d["stowaway"] = Stowaway(rcfile)
116
+ return d
117
+
118
+ spawn.get_preparation_data = get_preparation_data_with_stowaway
119
+
120
+ setattr(multiprocessing, PATCHED_MARKER, True)
coverage/numbits.py ADDED
@@ -0,0 +1,146 @@
1
+ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2
+ # For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
3
+
4
+ """
5
+ Functions to manipulate packed binary representations of number sets.
6
+
7
+ To save space, coverage stores sets of line numbers in SQLite using a packed
8
+ binary representation called a numbits. A numbits is a set of positive
9
+ integers.
10
+
11
+ A numbits is stored as a blob in the database. The exact meaning of the bytes
12
+ in the blobs should be considered an implementation detail that might change in
13
+ the future. Use these functions to work with those binary blobs of data.
14
+
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import sqlite3
21
+ from collections.abc import Iterable
22
+ from itertools import zip_longest
23
+
24
+
25
+ def nums_to_numbits(nums: Iterable[int]) -> bytes:
26
+ """Convert `nums` into a numbits.
27
+
28
+ Arguments:
29
+ nums: a reusable iterable of integers, the line numbers to store.
30
+
31
+ Returns:
32
+ A binary blob.
33
+ """
34
+ try:
35
+ nbytes = max(nums) // 8 + 1
36
+ except ValueError:
37
+ # nums was empty.
38
+ return b""
39
+ b = bytearray(nbytes)
40
+ for num in nums:
41
+ b[num // 8] |= 1 << num % 8
42
+ return bytes(b)
43
+
44
+
45
+ def numbits_to_nums(numbits: bytes) -> list[int]:
46
+ """Convert a numbits into a list of numbers.
47
+
48
+ Arguments:
49
+ numbits: a binary blob, the packed number set.
50
+
51
+ Returns:
52
+ A list of ints.
53
+
54
+ When registered as a SQLite function by :func:`register_sqlite_functions`,
55
+ this returns a string, a JSON-encoded list of ints.
56
+
57
+ """
58
+ nums = []
59
+ for byte_i, byte in enumerate(numbits):
60
+ for bit_i in range(8):
61
+ if byte & (1 << bit_i):
62
+ nums.append(byte_i * 8 + bit_i)
63
+ return nums
64
+
65
+
66
+ def numbits_union(numbits1: bytes, numbits2: bytes) -> bytes:
67
+ """Compute the union of two numbits.
68
+
69
+ Returns:
70
+ A new numbits, the union of `numbits1` and `numbits2`.
71
+ """
72
+ byte_pairs = zip_longest(numbits1, numbits2, fillvalue=0)
73
+ return bytes(b1 | b2 for b1, b2 in byte_pairs)
74
+
75
+
76
+ def numbits_intersection(numbits1: bytes, numbits2: bytes) -> bytes:
77
+ """Compute the intersection of two numbits.
78
+
79
+ Returns:
80
+ A new numbits, the intersection `numbits1` and `numbits2`.
81
+ """
82
+ byte_pairs = zip_longest(numbits1, numbits2, fillvalue=0)
83
+ intersection_bytes = bytes(b1 & b2 for b1, b2 in byte_pairs)
84
+ return intersection_bytes.rstrip(b"\0")
85
+
86
+
87
+ def numbits_any_intersection(numbits1: bytes, numbits2: bytes) -> bool:
88
+ """Is there any number that appears in both numbits?
89
+
90
+ Determine whether two number sets have a non-empty intersection. This is
91
+ faster than computing the intersection.
92
+
93
+ Returns:
94
+ A bool, True if there is any number in both `numbits1` and `numbits2`.
95
+ """
96
+ byte_pairs = zip_longest(numbits1, numbits2, fillvalue=0)
97
+ return any(b1 & b2 for b1, b2 in byte_pairs)
98
+
99
+
100
+ def num_in_numbits(num: int, numbits: bytes) -> bool:
101
+ """Does the integer `num` appear in `numbits`?
102
+
103
+ Returns:
104
+ A bool, True if `num` is a member of `numbits`.
105
+ """
106
+ nbyte, nbit = divmod(num, 8)
107
+ if nbyte >= len(numbits):
108
+ return False
109
+ return bool(numbits[nbyte] & (1 << nbit))
110
+
111
+
112
+ def register_sqlite_functions(connection: sqlite3.Connection) -> None:
113
+ """
114
+ Define numbits functions in a SQLite connection.
115
+
116
+ This defines these functions for use in SQLite statements:
117
+
118
+ * :func:`numbits_union`
119
+ * :func:`numbits_intersection`
120
+ * :func:`numbits_any_intersection`
121
+ * :func:`num_in_numbits`
122
+ * :func:`numbits_to_nums`
123
+
124
+ `connection` is a :class:`sqlite3.Connection <python:sqlite3.Connection>`
125
+ object. After creating the connection, pass it to this function to
126
+ register the numbits functions. Then you can use numbits functions in your
127
+ queries::
128
+
129
+ import sqlite3
130
+ from coverage.numbits import register_sqlite_functions
131
+
132
+ conn = sqlite3.connect("example.db")
133
+ register_sqlite_functions(conn)
134
+ c = conn.cursor()
135
+ # Kind of a nonsense query:
136
+ # Find all the files and contexts that executed line 47 in any file:
137
+ c.execute(
138
+ "select file_id, context_id from line_bits where num_in_numbits(?, numbits)",
139
+ (47,)
140
+ )
141
+ """
142
+ connection.create_function("numbits_union", 2, numbits_union)
143
+ connection.create_function("numbits_intersection", 2, numbits_intersection)
144
+ connection.create_function("numbits_any_intersection", 2, numbits_any_intersection)
145
+ connection.create_function("num_in_numbits", 2, num_in_numbits)
146
+ connection.create_function("numbits_to_nums", 1, lambda b: json.dumps(numbits_to_nums(b)))