|
3 | 3 | """ |
4 | 4 |
|
5 | 5 | import unittest |
| 6 | +import struct |
6 | 7 | import sys |
7 | 8 | import pickle |
8 | 9 | from test import support |
@@ -442,3 +443,78 @@ def test_pickle(self): |
442 | 443 | def test_free_after_iterating(self): |
443 | 444 | support.check_free_after_iterating(self, iter, self.type2test) |
444 | 445 | support.check_free_after_iterating(self, reversed, self.type2test) |
| 446 | + |
| 447 | + |
| 448 | +class NativeItemsSearchTest: |
| 449 | + """Tests of count(), index() and "in" for 1-d buffer protocol objects. |
| 450 | +
|
| 451 | + Tests use "needle" for the value being searched for and "haystack" for |
| 452 | + the object being searched. |
| 453 | + """ |
| 454 | + |
| 455 | + # All the native single item formats of memoryview. |
| 456 | + search_formats = ('b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q', |
| 457 | + 'n', 'N', 'P', 'e', 'f', 'd', 'Zf', 'Zd', '?', 'c') |
| 458 | + |
| 459 | + def make_haystacks(self, fmt, raw): |
| 460 | + """Make type-specific haystacks interpreting *raw* as *fmt*. |
| 461 | +
|
| 462 | + Subclasses make one haystack per layout under test (for example a |
| 463 | + contiguous, strided or unaligned memoryview). Yield (name, haystack) |
| 464 | + pairs, where name identifies the haystack in subtests. |
| 465 | + """ |
| 466 | + raise NotImplementedError |
| 467 | + |
| 468 | + def run_search_cases(self, check): |
| 469 | + """Call check(haystack, haystack_objects, needle) for every needle |
| 470 | + of every haystack, with a subtest for each haystack. |
| 471 | +
|
| 472 | + haystack_objects is the list of the items of haystack as Python |
| 473 | + objects, to check results against. |
| 474 | + """ |
| 475 | + class AlwaysEqual(int): |
| 476 | + def __eq__(self, other): |
| 477 | + return True |
| 478 | + __hash__ = int.__hash__ |
| 479 | + |
| 480 | + nan = float('nan') |
| 481 | + # Every byte value, then the doubles 0.0, -0.0, NaN and 1.0, all |
| 482 | + # twice so that values are found more than once. |
| 483 | + raw = (bytes(range(256)) + struct.pack('4d', 0.0, -0.0, nan, 1)) * 2 |
| 484 | + # Needles which may not be items of the format: of other types, out |
| 485 | + # of range, or equal to items with different bytes. |
| 486 | + other_needles = [0, 1, -1, 2**64, 0.0, -0.0, nan, True, False, 1.0, |
| 487 | + b'a', AlwaysEqual(5)] |
| 488 | + for fmt in self.search_formats: |
| 489 | + for name, haystack in self.make_haystacks(fmt, raw): |
| 490 | + with self.subTest(fmt=fmt, haystack=name): |
| 491 | + haystack_objects = haystack.tolist() |
| 492 | + for needle in haystack_objects + other_needles: |
| 493 | + check(haystack, haystack_objects, needle) |
| 494 | + |
| 495 | + def test_search_formats(self): |
| 496 | + self.run_search_cases(self.check_search) |
| 497 | + |
| 498 | + def check_search(self, haystack, haystack_objects, needle): |
| 499 | + """Validate optimized search matches python object search. |
| 500 | +
|
| 501 | + Compares search results between optimized format search and python |
| 502 | + rich object comparison. |
| 503 | + """ |
| 504 | + msg = f'needle={needle!r}' |
| 505 | + n = len(haystack_objects) |
| 506 | + matches = [i for i, x in enumerate(haystack_objects) if x == needle] |
| 507 | + self.assertEqual(haystack.count(needle), len(matches), msg) |
| 508 | + self.assertEqual(needle in haystack, bool(matches), msg) |
| 509 | + # index() bounds which are negative or past either end are clipped |
| 510 | + # like slice bounds. |
| 511 | + bounds = [(0, n), (5, -5), (-3, n), (2, n + 100), (-n - 5, n // 2)] |
| 512 | + for start, stop in bounds: |
| 513 | + r = range(n)[start:stop] |
| 514 | + expected = [i for i in matches if i in r] |
| 515 | + if expected: |
| 516 | + self.assertEqual(haystack.index(needle, start, stop), |
| 517 | + expected[0], msg) |
| 518 | + else: |
| 519 | + with self.assertRaises(ValueError, msg=msg): |
| 520 | + haystack.index(needle, start, stop) |
0 commit comments