summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2019-08-01 09:42:54 -0400
committerNed Batchelder <ned@nedbatchelder.com>2019-08-01 09:42:54 -0400
commita361ff3897736917544da1a4e28d8db6457f0fcc (patch)
treecdbee5c982a527dd89974ac38feb41fbc063b00f
parent79111b14cd70bbfd75555bb819c6dbdc25a543aa (diff)
downloadpython-coveragepy-git-a361ff3897736917544da1a4e28d8db6457f0fcc.tar.gz
Add num_in_numbits
-rw-r--r--coverage/backward.py8
-rw-r--r--coverage/numbits.py12
-rw-r--r--tests/test_numbits.py11
3 files changed, 28 insertions, 3 deletions
diff --git a/coverage/backward.py b/coverage/backward.py
index 0df2a41e..34ab2f1a 100644
--- a/coverage/backward.py
+++ b/coverage/backward.py
@@ -114,6 +114,10 @@ if env.PY3:
"""Produce a byte string with the ints from `byte_values`."""
return bytes(byte_values)
+ def byte_to_int(byte):
+ """Turn a byte indexed from a bytes object into an int."""
+ return byte
+
def bytes_to_ints(bytes_value):
"""Turn a bytes object into a sequence of ints."""
# In Python 3, iterating bytes gives ints.
@@ -132,6 +136,10 @@ else:
"""Produce a byte string with the ints from `byte_values`."""
return "".join(chr(b) for b in byte_values)
+ def byte_to_int(byte):
+ """Turn a byte indexed from a bytes object into an int."""
+ return ord(byte)
+
def bytes_to_ints(bytes_value):
"""Turn a bytes object into a sequence of ints."""
for byte in bytes_value:
diff --git a/coverage/numbits.py b/coverage/numbits.py
index 19c328c4..c044073c 100644
--- a/coverage/numbits.py
+++ b/coverage/numbits.py
@@ -12,13 +12,13 @@ work with those binary blobs of data.
"""
-from coverage.backward import bytes_to_ints, binary_bytes, zip_longest
+from coverage.backward import byte_to_int, bytes_to_ints, binary_bytes, zip_longest
from coverage.misc import contract
@contract(nums='Iterable', returns='bytes')
def nums_to_numbits(nums):
- """Convert `nums` (an iterable of ints) into a numbits."""
+ """Convert `nums` (a non-empty iterable of ints) into a numbits."""
nbytes = max(nums) // 8 + 1
b = bytearray(nbytes)
for num in nums:
@@ -46,3 +46,11 @@ def numbits_any_intersection(numbits1, numbits2):
"""Is there any number that appears in both numbits?"""
byte_pairs = zip_longest(bytes_to_ints(numbits1), bytes_to_ints(numbits2), fillvalue=0)
return any(b1 & b2 for b1, b2 in byte_pairs)
+
+@contract(num='int', numbits='bytes', returns='bool')
+def num_in_numbits(num, numbits):
+ """Does the integer `num` appear in `numbits`?"""
+ nbyte, nbit = divmod(num, 8)
+ if nbyte > len(numbits):
+ return False
+ return bool(byte_to_int(numbits[nbyte]) & (1 << nbit))
diff --git a/tests/test_numbits.py b/tests/test_numbits.py
index 4a835c4f..f4e52997 100644
--- a/tests/test_numbits.py
+++ b/tests/test_numbits.py
@@ -9,12 +9,14 @@ from hypothesis.strategies import sets, integers
from coverage import env
from coverage.numbits import (
nums_to_numbits, numbits_to_nums, merge_numbits, numbits_any_intersection,
+ num_in_numbits,
)
from tests.coveragetest import CoverageTest
# Hypothesis-generated line number data
-line_numbers = sets(integers(min_value=1, max_value=9999), min_size=1)
+line_number = integers(min_value=1, max_value=9999)
+line_numbers = sets(line_number, min_size=1)
# When coverage-testing ourselves, hypothesis complains about a test being
# flaky because the first run exceeds the deadline (and fails), and the second
@@ -47,3 +49,10 @@ class NumbitsOpTest(CoverageTest):
inter = numbits_any_intersection(nums_to_numbits(nums1), nums_to_numbits(nums2))
expect = bool(nums1 & nums2)
self.assertEqual(expect, bool(inter))
+
+ @given(line_number, line_numbers)
+ @settings(default_settings)
+ def test_num_in_numbits(self, num, nums):
+ numbits = nums_to_numbits(nums)
+ is_in = num_in_numbits(num, numbits)
+ self.assertEqual(num in nums, is_in)