diff --git a/Lib/test/test_bigmem.py b/Lib/test/test_bigmem.py index 61b9bce330..87280b625a 100644 --- a/Lib/test/test_bigmem.py +++ b/Lib/test/test_bigmem.py @@ -1,1291 +1,1291 @@ -"""Bigmem tests - tests for the 32-bit boundary in containers. - -These tests try to exercise the 32-bit boundary that is sometimes, if -rarely, exceeded in practice, but almost never tested. They are really only -meaningful on 64-bit builds on machines with a *lot* of memory, but the -tests are always run, usually with very low memory limits to make sure the -tests themselves don't suffer from bitrot. To run them for real, pass a -high memory limit to regrtest, with the -M option. -""" - -from test import support -from test.support import bigmemtest, _1G, _2G, _4G - -import unittest -import operator -import sys - -# These tests all use one of the bigmemtest decorators to indicate how much -# memory they use and how much memory they need to be even meaningful. The -# decorators take two arguments: a 'memuse' indicator declaring -# (approximate) bytes per size-unit the test will use (at peak usage), and a -# 'minsize' indicator declaring a minimum *useful* size. A test that -# allocates a bytestring to test various operations near the end will have a -# minsize of at least 2Gb (or it wouldn't reach the 32-bit limit, so the -# test wouldn't be very useful) and a memuse of 1 (one byte per size-unit, -# if it allocates only one big string at a time.) -# -# When run with a memory limit set, both decorators skip tests that need -# more memory than available to be meaningful. The precisionbigmemtest will -# always pass minsize as size, even if there is much more memory available. -# The bigmemtest decorator will scale size upward to fill available memory. -# -# Bigmem testing houserules: -# -# - Try not to allocate too many large objects. It's okay to rely on -# refcounting semantics, and don't forget that 's = create_largestring()' -# doesn't release the old 's' (if it exists) until well after its new -# value has been created. Use 'del s' before the create_largestring call. -# -# - Do *not* compare large objects using assertEqual, assertIn or similar. -# It's a lengthy operation and the errormessage will be utterly useless -# due to its size. To make sure whether a result has the right contents, -# better to use the strip or count methods, or compare meaningful slices. -# -# - Don't forget to test for large indices, offsets and results and such, -# in addition to large sizes. Anything that probes the 32-bit boundary. -# -# - When repeating an object (say, a substring, or a small list) to create -# a large object, make the subobject of a length that is not a power of -# 2. That way, int-wrapping problems are more easily detected. -# -# - Despite the bigmemtest decorator, all tests will actually be called -# with a much smaller number too, in the normal test run (5Kb currently.) -# This is so the tests themselves get frequent testing. -# Consequently, always make all large allocations based on the -# passed-in 'size', and don't rely on the size being very large. Also, -# memuse-per-size should remain sane (less than a few thousand); if your -# test uses more, adjust 'size' upward, instead. - -# BEWARE: it seems that one failing test can yield other subsequent tests to -# fail as well. I do not know whether it is due to memory fragmentation -# issues, or other specifics of the platform malloc() routine. - -ascii_char_size = 1 -ucs2_char_size = 2 -ucs4_char_size = 4 -pointer_size = 4 if sys.maxsize < 2**32 else 8 - - -class BaseStrTest: - - def _test_capitalize(self, size): - _ = self.from_latin1 - SUBSTR = self.from_latin1(' abc def ghi') - s = _('-') * size + SUBSTR - caps = s.capitalize() - self.assertEqual(caps[-len(SUBSTR):], - SUBSTR.capitalize()) - self.assertEqual(caps.lstrip(_('-')), SUBSTR) - - @bigmemtest(size=_2G + 10, memuse=1) - def test_center(self, size): - SUBSTR = self.from_latin1(' abc def ghi') - s = SUBSTR.center(size) - self.assertEqual(len(s), size) - lpadsize = rpadsize = (len(s) - len(SUBSTR)) // 2 - if len(s) % 2: - lpadsize += 1 - self.assertEqual(s[lpadsize:-rpadsize], SUBSTR) - self.assertEqual(s.strip(), SUBSTR.strip()) - - @bigmemtest(size=_2G, memuse=2) - def test_count(self, size): - _ = self.from_latin1 - SUBSTR = _(' abc def ghi') - s = _('.') * size + SUBSTR - self.assertEqual(s.count(_('.')), size) - s += _('.') - self.assertEqual(s.count(_('.')), size + 1) - self.assertEqual(s.count(_(' ')), 3) - self.assertEqual(s.count(_('i')), 1) - self.assertEqual(s.count(_('j')), 0) - - @bigmemtest(size=_2G, memuse=2) - def test_endswith(self, size): - _ = self.from_latin1 - SUBSTR = _(' abc def ghi') - s = _('-') * size + SUBSTR - self.assertTrue(s.endswith(SUBSTR)) - self.assertTrue(s.endswith(s)) - s2 = _('...') + s - self.assertTrue(s2.endswith(s)) - self.assertFalse(s.endswith(_('a') + SUBSTR)) - self.assertFalse(SUBSTR.endswith(s)) - - @bigmemtest(size=_2G + 10, memuse=2) - def test_expandtabs(self, size): - _ = self.from_latin1 - s = _('-') * size - tabsize = 8 - self.assertTrue(s.expandtabs() == s) - del s - slen, remainder = divmod(size, tabsize) - s = _(' \t') * slen - s = s.expandtabs(tabsize) - self.assertEqual(len(s), size - remainder) - self.assertEqual(len(s.strip(_(' '))), 0) - - @bigmemtest(size=_2G, memuse=2) - def test_find(self, size): - _ = self.from_latin1 - SUBSTR = _(' abc def ghi') - sublen = len(SUBSTR) - s = _('').join([SUBSTR, _('-') * size, SUBSTR]) - self.assertEqual(s.find(_(' ')), 0) - self.assertEqual(s.find(SUBSTR), 0) - self.assertEqual(s.find(_(' '), sublen), sublen + size) - self.assertEqual(s.find(SUBSTR, len(SUBSTR)), sublen + size) - self.assertEqual(s.find(_('i')), SUBSTR.find(_('i'))) - self.assertEqual(s.find(_('i'), sublen), - sublen + size + SUBSTR.find(_('i'))) - self.assertEqual(s.find(_('i'), size), - sublen + size + SUBSTR.find(_('i'))) - self.assertEqual(s.find(_('j')), -1) - - @bigmemtest(size=_2G, memuse=2) - def test_index(self, size): - _ = self.from_latin1 - SUBSTR = _(' abc def ghi') - sublen = len(SUBSTR) - s = _('').join([SUBSTR, _('-') * size, SUBSTR]) - self.assertEqual(s.index(_(' ')), 0) - self.assertEqual(s.index(SUBSTR), 0) - self.assertEqual(s.index(_(' '), sublen), sublen + size) - self.assertEqual(s.index(SUBSTR, sublen), sublen + size) - self.assertEqual(s.index(_('i')), SUBSTR.index(_('i'))) - self.assertEqual(s.index(_('i'), sublen), - sublen + size + SUBSTR.index(_('i'))) - self.assertEqual(s.index(_('i'), size), - sublen + size + SUBSTR.index(_('i'))) - self.assertRaises(ValueError, s.index, _('j')) - - @bigmemtest(size=_2G, memuse=2) - def test_isalnum(self, size): - _ = self.from_latin1 - SUBSTR = _('123456') - s = _('a') * size + SUBSTR - self.assertTrue(s.isalnum()) - s += _('.') - self.assertFalse(s.isalnum()) - - @bigmemtest(size=_2G, memuse=2) - def test_isalpha(self, size): - _ = self.from_latin1 - SUBSTR = _('zzzzzzz') - s = _('a') * size + SUBSTR - self.assertTrue(s.isalpha()) - s += _('.') - self.assertFalse(s.isalpha()) - - @bigmemtest(size=_2G, memuse=2) - def test_isdigit(self, size): - _ = self.from_latin1 - SUBSTR = _('123456') - s = _('9') * size + SUBSTR - self.assertTrue(s.isdigit()) - s += _('z') - self.assertFalse(s.isdigit()) - - @bigmemtest(size=_2G, memuse=2) - def test_islower(self, size): - _ = self.from_latin1 - chars = _(''.join( - chr(c) for c in range(255) if not chr(c).isupper())) - repeats = size // len(chars) + 2 - s = chars * repeats - self.assertTrue(s.islower()) - s += _('A') - self.assertFalse(s.islower()) - - @bigmemtest(size=_2G, memuse=2) - def test_isspace(self, size): - _ = self.from_latin1 - whitespace = _(' \f\n\r\t\v') - repeats = size // len(whitespace) + 2 - s = whitespace * repeats - self.assertTrue(s.isspace()) - s += _('j') - self.assertFalse(s.isspace()) - - @bigmemtest(size=_2G, memuse=2) - def test_istitle(self, size): - _ = self.from_latin1 - SUBSTR = _('123456') - s = _('').join([_('A'), _('a') * size, SUBSTR]) - self.assertTrue(s.istitle()) - s += _('A') - self.assertTrue(s.istitle()) - s += _('aA') - self.assertFalse(s.istitle()) - - @bigmemtest(size=_2G, memuse=2) - def test_isupper(self, size): - _ = self.from_latin1 - chars = _(''.join( - chr(c) for c in range(255) if not chr(c).islower())) - repeats = size // len(chars) + 2 - s = chars * repeats - self.assertTrue(s.isupper()) - s += _('a') - self.assertFalse(s.isupper()) - - @bigmemtest(size=_2G, memuse=2) - def test_join(self, size): - _ = self.from_latin1 - s = _('A') * size - x = s.join([_('aaaaa'), _('bbbbb')]) - self.assertEqual(x.count(_('a')), 5) - self.assertEqual(x.count(_('b')), 5) - self.assertTrue(x.startswith(_('aaaaaA'))) - self.assertTrue(x.endswith(_('Abbbbb'))) - - @bigmemtest(size=_2G + 10, memuse=1) - def test_ljust(self, size): - _ = self.from_latin1 - SUBSTR = _(' abc def ghi') - s = SUBSTR.ljust(size) - self.assertTrue(s.startswith(SUBSTR + _(' '))) - self.assertEqual(len(s), size) - self.assertEqual(s.strip(), SUBSTR.strip()) - - @bigmemtest(size=_2G + 10, memuse=2) - def test_lower(self, size): - _ = self.from_latin1 - s = _('A') * size - s = s.lower() - self.assertEqual(len(s), size) - self.assertEqual(s.count(_('a')), size) - - @bigmemtest(size=_2G + 10, memuse=1) - def test_lstrip(self, size): - _ = self.from_latin1 - SUBSTR = _('abc def ghi') - s = SUBSTR.rjust(size) - self.assertEqual(len(s), size) - self.assertEqual(s.lstrip(), SUBSTR.lstrip()) - del s - s = SUBSTR.ljust(size) - self.assertEqual(len(s), size) - # Type-specific optimization - if isinstance(s, (str, bytes)): - stripped = s.lstrip() - self.assertTrue(stripped is s) - - @bigmemtest(size=_2G + 10, memuse=2) - def test_replace(self, size): - _ = self.from_latin1 - replacement = _('a') - s = _(' ') * size - s = s.replace(_(' '), replacement) - self.assertEqual(len(s), size) - self.assertEqual(s.count(replacement), size) - s = s.replace(replacement, _(' '), size - 4) - self.assertEqual(len(s), size) - self.assertEqual(s.count(replacement), 4) - self.assertEqual(s[-10:], _(' aaaa')) - - @bigmemtest(size=_2G, memuse=2) - def test_rfind(self, size): - _ = self.from_latin1 - SUBSTR = _(' abc def ghi') - sublen = len(SUBSTR) - s = _('').join([SUBSTR, _('-') * size, SUBSTR]) - self.assertEqual(s.rfind(_(' ')), sublen + size + SUBSTR.rfind(_(' '))) - self.assertEqual(s.rfind(SUBSTR), sublen + size) - self.assertEqual(s.rfind(_(' '), 0, size), SUBSTR.rfind(_(' '))) - self.assertEqual(s.rfind(SUBSTR, 0, sublen + size), 0) - self.assertEqual(s.rfind(_('i')), sublen + size + SUBSTR.rfind(_('i'))) - self.assertEqual(s.rfind(_('i'), 0, sublen), SUBSTR.rfind(_('i'))) - self.assertEqual(s.rfind(_('i'), 0, sublen + size), - SUBSTR.rfind(_('i'))) - self.assertEqual(s.rfind(_('j')), -1) - - @bigmemtest(size=_2G, memuse=2) - def test_rindex(self, size): - _ = self.from_latin1 - SUBSTR = _(' abc def ghi') - sublen = len(SUBSTR) - s = _('').join([SUBSTR, _('-') * size, SUBSTR]) - self.assertEqual(s.rindex(_(' ')), - sublen + size + SUBSTR.rindex(_(' '))) - self.assertEqual(s.rindex(SUBSTR), sublen + size) - self.assertEqual(s.rindex(_(' '), 0, sublen + size - 1), - SUBSTR.rindex(_(' '))) - self.assertEqual(s.rindex(SUBSTR, 0, sublen + size), 0) - self.assertEqual(s.rindex(_('i')), - sublen + size + SUBSTR.rindex(_('i'))) - self.assertEqual(s.rindex(_('i'), 0, sublen), SUBSTR.rindex(_('i'))) - self.assertEqual(s.rindex(_('i'), 0, sublen + size), - SUBSTR.rindex(_('i'))) - self.assertRaises(ValueError, s.rindex, _('j')) - - @bigmemtest(size=_2G + 10, memuse=1) - def test_rjust(self, size): - _ = self.from_latin1 - SUBSTR = _(' abc def ghi') - s = SUBSTR.ljust(size) - self.assertTrue(s.startswith(SUBSTR + _(' '))) - self.assertEqual(len(s), size) - self.assertEqual(s.strip(), SUBSTR.strip()) - - @bigmemtest(size=_2G + 10, memuse=1) - def test_rstrip(self, size): - _ = self.from_latin1 - SUBSTR = _(' abc def ghi') - s = SUBSTR.ljust(size) - self.assertEqual(len(s), size) - self.assertEqual(s.rstrip(), SUBSTR.rstrip()) - del s - s = SUBSTR.rjust(size) - self.assertEqual(len(s), size) - # Type-specific optimization - if isinstance(s, (str, bytes)): - stripped = s.rstrip() - self.assertTrue(stripped is s) - - # The test takes about size bytes to build a string, and then about - # sqrt(size) substrings of sqrt(size) in size and a list to - # hold sqrt(size) items. It's close but just over 2x size. - @bigmemtest(size=_2G, memuse=2.1) - def test_split_small(self, size): - _ = self.from_latin1 - # Crudely calculate an estimate so that the result of s.split won't - # take up an inordinate amount of memory - chunksize = int(size ** 0.5 + 2) - SUBSTR = _('a') + _(' ') * chunksize - s = SUBSTR * chunksize - l = s.split() - self.assertEqual(len(l), chunksize) - expected = _('a') - for item in l: - self.assertEqual(item, expected) - del l - l = s.split(_('a')) - self.assertEqual(len(l), chunksize + 1) - expected = _(' ') * chunksize - for item in filter(None, l): - self.assertEqual(item, expected) - - # Allocates a string of twice size (and briefly two) and a list of - # size. Because of internal affairs, the s.split() call produces a - # list of size times the same one-character string, so we only - # suffer for the list size. (Otherwise, it'd cost another 48 times - # size in bytes!) Nevertheless, a list of size takes - # 8*size bytes. - @bigmemtest(size=_2G + 5, memuse=ascii_char_size * 2 + pointer_size) - def test_split_large(self, size): - _ = self.from_latin1 - s = _(' a') * size + _(' ') - l = s.split() - self.assertEqual(len(l), size) - self.assertEqual(set(l), set([_('a')])) - del l - l = s.split(_('a')) - self.assertEqual(len(l), size + 1) - self.assertEqual(set(l), set([_(' ')])) - - @bigmemtest(size=_2G, memuse=2.1) - def test_splitlines(self, size): - _ = self.from_latin1 - # Crudely calculate an estimate so that the result of s.split won't - # take up an inordinate amount of memory - chunksize = int(size ** 0.5 + 2) // 2 - SUBSTR = _(' ') * chunksize + _('\n') + _(' ') * chunksize + _('\r\n') - s = SUBSTR * (chunksize * 2) - l = s.splitlines() - self.assertEqual(len(l), chunksize * 4) - expected = _(' ') * chunksize - for item in l: - self.assertEqual(item, expected) - - @bigmemtest(size=_2G, memuse=2) - def test_startswith(self, size): - _ = self.from_latin1 - SUBSTR = _(' abc def ghi') - s = _('-') * size + SUBSTR - self.assertTrue(s.startswith(s)) - self.assertTrue(s.startswith(_('-') * size)) - self.assertFalse(s.startswith(SUBSTR)) - - @bigmemtest(size=_2G, memuse=1) - def test_strip(self, size): - _ = self.from_latin1 - SUBSTR = _(' abc def ghi ') - s = SUBSTR.rjust(size) - self.assertEqual(len(s), size) - self.assertEqual(s.strip(), SUBSTR.strip()) - del s - s = SUBSTR.ljust(size) - self.assertEqual(len(s), size) - self.assertEqual(s.strip(), SUBSTR.strip()) - - def _test_swapcase(self, size): - _ = self.from_latin1 - SUBSTR = _("aBcDeFG12.'\xa9\x00") - sublen = len(SUBSTR) - repeats = size // sublen + 2 - s = SUBSTR * repeats - s = s.swapcase() - self.assertEqual(len(s), sublen * repeats) - self.assertEqual(s[:sublen * 3], SUBSTR.swapcase() * 3) - self.assertEqual(s[-sublen * 3:], SUBSTR.swapcase() * 3) - - def _test_title(self, size): - _ = self.from_latin1 - SUBSTR = _('SpaaHAaaAaham') - s = SUBSTR * (size // len(SUBSTR) + 2) - s = s.title() - self.assertTrue(s.startswith((SUBSTR * 3).title())) - self.assertTrue(s.endswith(SUBSTR.lower() * 3)) - - @bigmemtest(size=_2G, memuse=2) - def test_translate(self, size): - _ = self.from_latin1 - SUBSTR = _('aZz.z.Aaz.') - trans = bytes.maketrans(b'.aZ', b'-!$') - sublen = len(SUBSTR) - repeats = size // sublen + 2 - s = SUBSTR * repeats - s = s.translate(trans) - self.assertEqual(len(s), repeats * sublen) - self.assertEqual(s[:sublen], SUBSTR.translate(trans)) - self.assertEqual(s[-sublen:], SUBSTR.translate(trans)) - self.assertEqual(s.count(_('.')), 0) - self.assertEqual(s.count(_('!')), repeats * 2) - self.assertEqual(s.count(_('z')), repeats * 3) - - @bigmemtest(size=_2G + 5, memuse=2) - def test_upper(self, size): - _ = self.from_latin1 - s = _('a') * size - s = s.upper() - self.assertEqual(len(s), size) - self.assertEqual(s.count(_('A')), size) - - @bigmemtest(size=_2G + 20, memuse=1) - def test_zfill(self, size): - _ = self.from_latin1 - SUBSTR = _('-568324723598234') - s = SUBSTR.zfill(size) - self.assertTrue(s.endswith(_('0') + SUBSTR[1:])) - self.assertTrue(s.startswith(_('-0'))) - self.assertEqual(len(s), size) - self.assertEqual(s.count(_('0')), size - len(SUBSTR)) - - # This test is meaningful even with size < 2G, as long as the - # doubled string is > 2G (but it tests more if both are > 2G :) - @bigmemtest(size=_1G + 2, memuse=3) - def test_concat(self, size): - _ = self.from_latin1 - s = _('.') * size - self.assertEqual(len(s), size) - s = s + s - self.assertEqual(len(s), size * 2) - self.assertEqual(s.count(_('.')), size * 2) - - # This test is meaningful even with size < 2G, as long as the - # repeated string is > 2G (but it tests more if both are > 2G :) - @bigmemtest(size=_1G + 2, memuse=3) - def test_repeat(self, size): - _ = self.from_latin1 - s = _('.') * size - self.assertEqual(len(s), size) - s = s * 2 - self.assertEqual(len(s), size * 2) - self.assertEqual(s.count(_('.')), size * 2) - - @bigmemtest(size=_2G + 20, memuse=2) - def test_slice_and_getitem(self, size): - _ = self.from_latin1 - SUBSTR = _('0123456789') - sublen = len(SUBSTR) - s = SUBSTR * (size // sublen) - stepsize = len(s) // 100 - stepsize = stepsize - (stepsize % sublen) - for i in range(0, len(s) - stepsize, stepsize): - self.assertEqual(s[i], SUBSTR[0]) - self.assertEqual(s[i:i + sublen], SUBSTR) - self.assertEqual(s[i:i + sublen:2], SUBSTR[::2]) - if i > 0: - self.assertEqual(s[i + sublen - 1:i - 1:-3], - SUBSTR[sublen::-3]) - # Make sure we do some slicing and indexing near the end of the - # string, too. - self.assertEqual(s[len(s) - 1], SUBSTR[-1]) - self.assertEqual(s[-1], SUBSTR[-1]) - self.assertEqual(s[len(s) - 10], SUBSTR[0]) - self.assertEqual(s[-sublen], SUBSTR[0]) - self.assertEqual(s[len(s):], _('')) - self.assertEqual(s[len(s) - 1:], SUBSTR[-1:]) - self.assertEqual(s[-1:], SUBSTR[-1:]) - self.assertEqual(s[len(s) - sublen:], SUBSTR) - self.assertEqual(s[-sublen:], SUBSTR) - self.assertEqual(len(s[:]), len(s)) - self.assertEqual(len(s[:len(s) - 5]), len(s) - 5) - self.assertEqual(len(s[5:-5]), len(s) - 10) - - self.assertRaises(IndexError, operator.getitem, s, len(s)) - self.assertRaises(IndexError, operator.getitem, s, len(s) + 1) - self.assertRaises(IndexError, operator.getitem, s, len(s) + 1<<31) - - @bigmemtest(size=_2G, memuse=2) - def test_contains(self, size): - _ = self.from_latin1 - SUBSTR = _('0123456789') - edge = _('-') * (size // 2) - s = _('').join([edge, SUBSTR, edge]) - del edge - self.assertTrue(SUBSTR in s) - self.assertFalse(SUBSTR * 2 in s) - self.assertTrue(_('-') in s) - self.assertFalse(_('a') in s) - s += _('a') - self.assertTrue(_('a') in s) - - @bigmemtest(size=_2G + 10, memuse=2) - def test_compare(self, size): - _ = self.from_latin1 - s1 = _('-') * size - s2 = _('-') * size - self.assertTrue(s1 == s2) - del s2 - s2 = s1 + _('a') - self.assertFalse(s1 == s2) - del s2 - s2 = _('.') * size - self.assertFalse(s1 == s2) - - @bigmemtest(size=_2G + 10, memuse=1) - def test_hash(self, size): - # Not sure if we can do any meaningful tests here... Even if we - # start relying on the exact algorithm used, the result will be - # different depending on the size of the C 'long int'. Even this - # test is dodgy (there's no *guarantee* that the two things should - # have a different hash, even if they, in the current - # implementation, almost always do.) - _ = self.from_latin1 - s = _('\x00') * size - h1 = hash(s) - del s - s = _('\x00') * (size + 1) - self.assertNotEqual(h1, hash(s)) - - -class StrTest(unittest.TestCase, BaseStrTest): - - def from_latin1(self, s): - return s - - def basic_encode_test(self, size, enc, c='.', expectedsize=None): - if expectedsize is None: - expectedsize = size - try: - s = c * size - self.assertEqual(len(s.encode(enc)), expectedsize) - finally: - s = None - - def setUp(self): - # HACK: adjust memory use of tests inherited from BaseStrTest - # according to character size. - self._adjusted = {} - for name in dir(BaseStrTest): - if not name.startswith('test_'): - continue - meth = getattr(type(self), name) - try: - memuse = meth.memuse - except AttributeError: - continue - meth.memuse = ascii_char_size * memuse - self._adjusted[name] = memuse - - def tearDown(self): - for name, memuse in self._adjusted.items(): - getattr(type(self), name).memuse = memuse - - @bigmemtest(size=_2G, memuse=ucs4_char_size * 3 + ascii_char_size * 2) - def test_capitalize(self, size): - self._test_capitalize(size) - - @bigmemtest(size=_2G, memuse=ucs4_char_size * 3 + ascii_char_size * 2) - def test_title(self, size): - self._test_title(size) - - @bigmemtest(size=_2G, memuse=ucs4_char_size * 3 + ascii_char_size * 2) - def test_swapcase(self, size): - self._test_swapcase(size) - - # Many codecs convert to the legacy representation first, explaining - # why we add 'ucs4_char_size' to the 'memuse' below. - - @bigmemtest(size=_2G + 2, memuse=ascii_char_size + 1) - def test_encode(self, size): - return self.basic_encode_test(size, 'utf-8') - - @bigmemtest(size=_4G // 6 + 2, memuse=ascii_char_size + ucs4_char_size + 1) - def test_encode_raw_unicode_escape(self, size): - try: - return self.basic_encode_test(size, 'raw_unicode_escape') - except MemoryError: - pass # acceptable on 32-bit - - @bigmemtest(size=_4G // 5 + 70, memuse=ascii_char_size + 8 + 1) - def test_encode_utf7(self, size): - try: - return self.basic_encode_test(size, 'utf7') - except MemoryError: - pass # acceptable on 32-bit - - # TODO: RUSTPYTHON - @unittest.expectedFailure - @bigmemtest(size=_4G // 4 + 5, memuse=ascii_char_size + ucs4_char_size + 4) - def test_encode_utf32(self, size): - try: - return self.basic_encode_test(size, 'utf32', expectedsize=4 * size + 4) - except MemoryError: - pass # acceptable on 32-bit - - @bigmemtest(size=_2G - 1, memuse=ascii_char_size + 1) - def test_encode_ascii(self, size): - return self.basic_encode_test(size, 'ascii', c='A') - - # str % (...) uses a Py_UCS4 intermediate representation - - @bigmemtest(size=_2G + 10, memuse=ascii_char_size * 2 + ucs4_char_size) - def test_format(self, size): - s = '-' * size - sf = '%s' % (s,) - self.assertTrue(s == sf) - del sf - sf = '..%s..' % (s,) - self.assertEqual(len(sf), len(s) + 4) - self.assertTrue(sf.startswith('..-')) - self.assertTrue(sf.endswith('-..')) - del s, sf - - size //= 2 - edge = '-' * size - s = ''.join([edge, '%s', edge]) - del edge - s = s % '...' - self.assertEqual(len(s), size * 2 + 3) - self.assertEqual(s.count('.'), 3) - self.assertEqual(s.count('-'), size * 2) - - @bigmemtest(size=_2G + 10, memuse=ascii_char_size * 2) - def test_repr_small(self, size): - s = '-' * size - s = repr(s) - self.assertEqual(len(s), size + 2) - self.assertEqual(s[0], "'") - self.assertEqual(s[-1], "'") - self.assertEqual(s.count('-'), size) - del s - # repr() will create a string four times as large as this 'binary - # string', but we don't want to allocate much more than twice - # size in total. (We do extra testing in test_repr_large()) - size = size // 5 * 2 - s = '\x00' * size - s = repr(s) - self.assertEqual(len(s), size * 4 + 2) - self.assertEqual(s[0], "'") - self.assertEqual(s[-1], "'") - self.assertEqual(s.count('\\'), size) - self.assertEqual(s.count('0'), size * 2) - - @bigmemtest(size=_2G + 10, memuse=ascii_char_size * 5) - def test_repr_large(self, size): - s = '\x00' * size - s = repr(s) - self.assertEqual(len(s), size * 4 + 2) - self.assertEqual(s[0], "'") - self.assertEqual(s[-1], "'") - self.assertEqual(s.count('\\'), size) - self.assertEqual(s.count('0'), size * 2) - - # ascii() calls encode('ascii', 'backslashreplace'), which itself - # creates a temporary Py_UNICODE representation in addition to the - # original (Py_UCS2) one - # There's also some overallocation when resizing the ascii() result - # that isn't taken into account here. - # TODO: RUSTPYTHON - @unittest.expectedFailure - @bigmemtest(size=_2G // 5 + 1, memuse=ucs2_char_size + - ucs4_char_size + ascii_char_size * 6) - def test_unicode_repr(self, size): - # Use an assigned, but not printable code point. - # It is in the range of the low surrogates \uDC00-\uDFFF. - char = "\uDCBA" - s = char * size - try: - for f in (repr, ascii): - r = f(s) - self.assertEqual(len(r), 2 + (len(f(char)) - 2) * size) - self.assertTrue(r.endswith(r"\udcba'"), r[-10:]) - r = None - finally: - r = s = None - - @bigmemtest(size=_2G // 5 + 1, memuse=ucs4_char_size * 2 + ascii_char_size * 10) - def test_unicode_repr_wide(self, size): - char = "\U0001DCBA" - s = char * size - try: - for f in (repr, ascii): - r = f(s) - self.assertEqual(len(r), 2 + (len(f(char)) - 2) * size) - self.assertTrue(r.endswith(r"\U0001dcba'"), r[-12:]) - r = None - finally: - r = s = None - - # The original test_translate is overridden here, so as to get the - # correct size estimate: str.translate() uses an intermediate Py_UCS4 - # representation. - - @bigmemtest(size=_2G, memuse=ascii_char_size * 2 + ucs4_char_size) - def test_translate(self, size): - _ = self.from_latin1 - SUBSTR = _('aZz.z.Aaz.') - trans = { - ord(_('.')): _('-'), - ord(_('a')): _('!'), - ord(_('Z')): _('$'), - } - sublen = len(SUBSTR) - repeats = size // sublen + 2 - s = SUBSTR * repeats - s = s.translate(trans) - self.assertEqual(len(s), repeats * sublen) - self.assertEqual(s[:sublen], SUBSTR.translate(trans)) - self.assertEqual(s[-sublen:], SUBSTR.translate(trans)) - self.assertEqual(s.count(_('.')), 0) - self.assertEqual(s.count(_('!')), repeats * 2) - self.assertEqual(s.count(_('z')), repeats * 3) - - -class BytesTest(unittest.TestCase, BaseStrTest): - - def from_latin1(self, s): - return s.encode("latin-1") - - @bigmemtest(size=_2G + 2, memuse=1 + ascii_char_size) - def test_decode(self, size): - s = self.from_latin1('.') * size - self.assertEqual(len(s.decode('utf-8')), size) - - @bigmemtest(size=_2G, memuse=2) - def test_capitalize(self, size): - self._test_capitalize(size) - - @bigmemtest(size=_2G, memuse=2) - def test_title(self, size): - self._test_title(size) - - @bigmemtest(size=_2G, memuse=2) - def test_swapcase(self, size): - self._test_swapcase(size) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_isspace(self, size): - super().test_isspace(size) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_istitle(self, size): - super().test_istitle(size) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_lstrip(self, size): - super().test_lstrip(size) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_rstrip(self, size): - super().test_rstrip(size) - - -class BytearrayTest(unittest.TestCase, BaseStrTest): - - def from_latin1(self, s): - return bytearray(s.encode("latin-1")) - - @bigmemtest(size=_2G + 2, memuse=1 + ascii_char_size) - def test_decode(self, size): - s = self.from_latin1('.') * size - self.assertEqual(len(s.decode('utf-8')), size) - - @bigmemtest(size=_2G, memuse=2) - def test_capitalize(self, size): - self._test_capitalize(size) - - @bigmemtest(size=_2G, memuse=2) - def test_title(self, size): - self._test_title(size) - - @bigmemtest(size=_2G, memuse=2) - def test_swapcase(self, size): - self._test_swapcase(size) - - test_hash = None - test_split_large = None - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_isspace(self, size): - super().test_isspace(size) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_istitle(self, size): - super().test_istitle(size) - -class TupleTest(unittest.TestCase): - - # Tuples have a small, fixed-sized head and an array of pointers to - # data. Since we're testing 64-bit addressing, we can assume that the - # pointers are 8 bytes, and that thus that the tuples take up 8 bytes - # per size. - - # As a side-effect of testing long tuples, these tests happen to test - # having more than 2<<31 references to any given object. Hence the - # use of different types of objects as contents in different tests. - - @bigmemtest(size=_2G + 2, memuse=pointer_size * 2) - def test_compare(self, size): - t1 = ('',) * size - t2 = ('',) * size - self.assertTrue(t1 == t2) - del t2 - t2 = ('',) * (size + 1) - self.assertFalse(t1 == t2) - del t2 - t2 = (1,) * size - self.assertFalse(t1 == t2) - - # Test concatenating into a single tuple of more than 2G in length, - # and concatenating a tuple of more than 2G in length separately, so - # the smaller test still gets run even if there isn't memory for the - # larger test (but we still let the tester know the larger test is - # skipped, in verbose mode.) - def basic_concat_test(self, size): - t = ((),) * size - self.assertEqual(len(t), size) - t = t + t - self.assertEqual(len(t), size * 2) - - @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 3) - def test_concat_small(self, size): - return self.basic_concat_test(size) - - @bigmemtest(size=_2G + 2, memuse=pointer_size * 3) - def test_concat_large(self, size): - return self.basic_concat_test(size) - - @bigmemtest(size=_2G // 5 + 10, memuse=pointer_size * 5) - def test_contains(self, size): - t = (1, 2, 3, 4, 5) * size - self.assertEqual(len(t), size * 5) - self.assertTrue(5 in t) - self.assertFalse((1, 2, 3, 4, 5) in t) - self.assertFalse(0 in t) - - @bigmemtest(size=_2G + 10, memuse=pointer_size) - def test_hash(self, size): - t1 = (0,) * size - h1 = hash(t1) - del t1 - t2 = (0,) * (size + 1) - self.assertFalse(h1 == hash(t2)) - - @bigmemtest(size=_2G + 10, memuse=pointer_size) - def test_index_and_slice(self, size): - t = (None,) * size - self.assertEqual(len(t), size) - self.assertEqual(t[-1], None) - self.assertEqual(t[5], None) - self.assertEqual(t[size - 1], None) - self.assertRaises(IndexError, operator.getitem, t, size) - self.assertEqual(t[:5], (None,) * 5) - self.assertEqual(t[-5:], (None,) * 5) - self.assertEqual(t[20:25], (None,) * 5) - self.assertEqual(t[-25:-20], (None,) * 5) - self.assertEqual(t[size - 5:], (None,) * 5) - self.assertEqual(t[size - 5:size], (None,) * 5) - self.assertEqual(t[size - 6:size - 2], (None,) * 4) - self.assertEqual(t[size:size], ()) - self.assertEqual(t[size:size+5], ()) - - # Like test_concat, split in two. - def basic_test_repeat(self, size): - t = ('',) * size - self.assertEqual(len(t), size) - t = t * 2 - self.assertEqual(len(t), size * 2) - - @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 3) - def test_repeat_small(self, size): - return self.basic_test_repeat(size) - - @bigmemtest(size=_2G + 2, memuse=pointer_size * 3) - def test_repeat_large(self, size): - return self.basic_test_repeat(size) - - @bigmemtest(size=_1G - 1, memuse=12) - def test_repeat_large_2(self, size): - return self.basic_test_repeat(size) - - @bigmemtest(size=_1G - 1, memuse=pointer_size * 2) - def test_from_2G_generator(self, size): - try: - t = tuple(iter([42]*size)) - except MemoryError: - pass # acceptable on 32-bit - else: - self.assertEqual(len(t), size) - self.assertEqual(t[:10], (42,) * 10) - self.assertEqual(t[-10:], (42,) * 10) - - @bigmemtest(size=_1G - 25, memuse=pointer_size * 2) - def test_from_almost_2G_generator(self, size): - try: - t = tuple(iter([42]*size)) - except MemoryError: - pass # acceptable on 32-bit - else: - self.assertEqual(len(t), size) - self.assertEqual(t[:10], (42,) * 10) - self.assertEqual(t[-10:], (42,) * 10) - - # Like test_concat, split in two. - def basic_test_repr(self, size): - t = (False,) * size - s = repr(t) - # The repr of a tuple of Falses is exactly 7 times the tuple length. - self.assertEqual(len(s), size * 7) - self.assertEqual(s[:10], '(False, Fa') - self.assertEqual(s[-10:], 'se, False)') - - @bigmemtest(size=_2G // 7 + 2, memuse=pointer_size + ascii_char_size * 7) - def test_repr_small(self, size): - return self.basic_test_repr(size) - - @bigmemtest(size=_2G + 2, memuse=pointer_size + ascii_char_size * 7) - def test_repr_large(self, size): - return self.basic_test_repr(size) - -class ListTest(unittest.TestCase): - - # Like tuples, lists have a small, fixed-sized head and an array of - # pointers to data, so 8 bytes per size. Also like tuples, we make the - # lists hold references to various objects to test their refcount - # limits. - - @bigmemtest(size=_2G + 2, memuse=pointer_size * 2) - def test_compare(self, size): - l1 = [''] * size - l2 = [''] * size - self.assertTrue(l1 == l2) - del l2 - l2 = [''] * (size + 1) - self.assertFalse(l1 == l2) - del l2 - l2 = [2] * size - self.assertFalse(l1 == l2) - - # Test concatenating into a single list of more than 2G in length, - # and concatenating a list of more than 2G in length separately, so - # the smaller test still gets run even if there isn't memory for the - # larger test (but we still let the tester know the larger test is - # skipped, in verbose mode.) - def basic_test_concat(self, size): - l = [[]] * size - self.assertEqual(len(l), size) - l = l + l - self.assertEqual(len(l), size * 2) - - @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 3) - def test_concat_small(self, size): - return self.basic_test_concat(size) - - @bigmemtest(size=_2G + 2, memuse=pointer_size * 3) - def test_concat_large(self, size): - return self.basic_test_concat(size) - - # XXX This tests suffers from overallocation, just like test_append. - # This should be fixed in future. - def basic_test_inplace_concat(self, size): - l = [sys.stdout] * size - l += l - self.assertEqual(len(l), size * 2) - self.assertTrue(l[0] is l[-1]) - self.assertTrue(l[size - 1] is l[size + 1]) - - @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 2 * 9/8) - def test_inplace_concat_small(self, size): - return self.basic_test_inplace_concat(size) - - @bigmemtest(size=_2G + 2, memuse=pointer_size * 2 * 9/8) - def test_inplace_concat_large(self, size): - return self.basic_test_inplace_concat(size) - - @bigmemtest(size=_2G // 5 + 10, memuse=pointer_size * 5) - def test_contains(self, size): - l = [1, 2, 3, 4, 5] * size - self.assertEqual(len(l), size * 5) - self.assertTrue(5 in l) - self.assertFalse([1, 2, 3, 4, 5] in l) - self.assertFalse(0 in l) - - @bigmemtest(size=_2G + 10, memuse=pointer_size) - def test_hash(self, size): - l = [0] * size - self.assertRaises(TypeError, hash, l) - - @bigmemtest(size=_2G + 10, memuse=pointer_size) - def test_index_and_slice(self, size): - l = [None] * size - self.assertEqual(len(l), size) - self.assertEqual(l[-1], None) - self.assertEqual(l[5], None) - self.assertEqual(l[size - 1], None) - self.assertRaises(IndexError, operator.getitem, l, size) - self.assertEqual(l[:5], [None] * 5) - self.assertEqual(l[-5:], [None] * 5) - self.assertEqual(l[20:25], [None] * 5) - self.assertEqual(l[-25:-20], [None] * 5) - self.assertEqual(l[size - 5:], [None] * 5) - self.assertEqual(l[size - 5:size], [None] * 5) - self.assertEqual(l[size - 6:size - 2], [None] * 4) - self.assertEqual(l[size:size], []) - self.assertEqual(l[size:size+5], []) - - l[size - 2] = 5 - self.assertEqual(len(l), size) - self.assertEqual(l[-3:], [None, 5, None]) - self.assertEqual(l.count(5), 1) - self.assertRaises(IndexError, operator.setitem, l, size, 6) - self.assertEqual(len(l), size) - - l[size - 7:] = [1, 2, 3, 4, 5] - size -= 2 - self.assertEqual(len(l), size) - self.assertEqual(l[-7:], [None, None, 1, 2, 3, 4, 5]) - - l[:7] = [1, 2, 3, 4, 5] - size -= 2 - self.assertEqual(len(l), size) - self.assertEqual(l[:7], [1, 2, 3, 4, 5, None, None]) - - del l[size - 1] - size -= 1 - self.assertEqual(len(l), size) - self.assertEqual(l[-1], 4) - - del l[-2:] - size -= 2 - self.assertEqual(len(l), size) - self.assertEqual(l[-1], 2) - - del l[0] - size -= 1 - self.assertEqual(len(l), size) - self.assertEqual(l[0], 2) - - del l[:2] - size -= 2 - self.assertEqual(len(l), size) - self.assertEqual(l[0], 4) - - # Like test_concat, split in two. - def basic_test_repeat(self, size): - l = [] * size - self.assertFalse(l) - l = [''] * size - self.assertEqual(len(l), size) - l = l * 2 - self.assertEqual(len(l), size * 2) - - @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 3) - def test_repeat_small(self, size): - return self.basic_test_repeat(size) - - @bigmemtest(size=_2G + 2, memuse=pointer_size * 3) - def test_repeat_large(self, size): - return self.basic_test_repeat(size) - - # XXX This tests suffers from overallocation, just like test_append. - # This should be fixed in future. - def basic_test_inplace_repeat(self, size): - l = [''] - l *= size - self.assertEqual(len(l), size) - self.assertTrue(l[0] is l[-1]) - del l - - l = [''] * size - l *= 2 - self.assertEqual(len(l), size * 2) - self.assertTrue(l[size - 1] is l[-1]) - - @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 2 * 9/8) - def test_inplace_repeat_small(self, size): - return self.basic_test_inplace_repeat(size) - - @bigmemtest(size=_2G + 2, memuse=pointer_size * 2 * 9/8) - def test_inplace_repeat_large(self, size): - return self.basic_test_inplace_repeat(size) - - def basic_test_repr(self, size): - l = [False] * size - s = repr(l) - # The repr of a list of Falses is exactly 7 times the list length. - self.assertEqual(len(s), size * 7) - self.assertEqual(s[:10], '[False, Fa') - self.assertEqual(s[-10:], 'se, False]') - self.assertEqual(s.count('F'), size) - - @bigmemtest(size=_2G // 7 + 2, memuse=pointer_size + ascii_char_size * 7) - def test_repr_small(self, size): - return self.basic_test_repr(size) - - @bigmemtest(size=_2G + 2, memuse=pointer_size + ascii_char_size * 7) - def test_repr_large(self, size): - return self.basic_test_repr(size) - - # list overallocates ~1/8th of the total size (on first expansion) so - # the single list.append call puts memuse at 9 bytes per size. - @bigmemtest(size=_2G, memuse=pointer_size * 9/8) - def test_append(self, size): - l = [object()] * size - l.append(object()) - self.assertEqual(len(l), size+1) - self.assertTrue(l[-3] is l[-2]) - self.assertFalse(l[-2] is l[-1]) - - @bigmemtest(size=_2G // 5 + 2, memuse=pointer_size * 5) - def test_count(self, size): - l = [1, 2, 3, 4, 5] * size - self.assertEqual(l.count(1), size) - self.assertEqual(l.count("1"), 0) - - # XXX This tests suffers from overallocation, just like test_append. - # This should be fixed in future. - def basic_test_extend(self, size): - l = [object] * size - l.extend(l) - self.assertEqual(len(l), size * 2) - self.assertTrue(l[0] is l[-1]) - self.assertTrue(l[size - 1] is l[size + 1]) - - @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 2 * 9/8) - def test_extend_small(self, size): - return self.basic_test_extend(size) - - @bigmemtest(size=_2G + 2, memuse=pointer_size * 2 * 9/8) - def test_extend_large(self, size): - return self.basic_test_extend(size) - - @bigmemtest(size=_2G // 5 + 2, memuse=pointer_size * 5) - def test_index(self, size): - l = [1, 2, 3, 4, 5] * size - size *= 5 - self.assertEqual(l.index(1), 0) - self.assertEqual(l.index(5, size - 5), size - 1) - self.assertEqual(l.index(5, size - 5, size), size - 1) - self.assertRaises(ValueError, l.index, 1, size - 4, size) - self.assertRaises(ValueError, l.index, 6) - - # This tests suffers from overallocation, just like test_append. - @bigmemtest(size=_2G + 10, memuse=pointer_size * 9/8) - def test_insert(self, size): - l = [1.0] * size - l.insert(size - 1, "A") - size += 1 - self.assertEqual(len(l), size) - self.assertEqual(l[-3:], [1.0, "A", 1.0]) - - l.insert(size + 1, "B") - size += 1 - self.assertEqual(len(l), size) - self.assertEqual(l[-3:], ["A", 1.0, "B"]) - - l.insert(1, "C") - size += 1 - self.assertEqual(len(l), size) - self.assertEqual(l[:3], [1.0, "C", 1.0]) - self.assertEqual(l[size - 3:], ["A", 1.0, "B"]) - - @bigmemtest(size=_2G // 5 + 4, memuse=pointer_size * 5) - def test_pop(self, size): - l = ["a", "b", "c", "d", "e"] * size - size *= 5 - self.assertEqual(len(l), size) - - item = l.pop() - size -= 1 - self.assertEqual(len(l), size) - self.assertEqual(item, "e") - self.assertEqual(l[-2:], ["c", "d"]) - - item = l.pop(0) - size -= 1 - self.assertEqual(len(l), size) - self.assertEqual(item, "a") - self.assertEqual(l[:2], ["b", "c"]) - - item = l.pop(size - 2) - size -= 1 - self.assertEqual(len(l), size) - self.assertEqual(item, "c") - self.assertEqual(l[-2:], ["b", "d"]) - - @bigmemtest(size=_2G + 10, memuse=pointer_size) - def test_remove(self, size): - l = [10] * size - self.assertEqual(len(l), size) - - l.remove(10) - size -= 1 - self.assertEqual(len(l), size) - - # Because of the earlier l.remove(), this append doesn't trigger - # a resize. - l.append(5) - size += 1 - self.assertEqual(len(l), size) - self.assertEqual(l[-2:], [10, 5]) - l.remove(5) - size -= 1 - self.assertEqual(len(l), size) - self.assertEqual(l[-2:], [10, 10]) - - @bigmemtest(size=_2G // 5 + 2, memuse=pointer_size * 5) - def test_reverse(self, size): - l = [1, 2, 3, 4, 5] * size - l.reverse() - self.assertEqual(len(l), size * 5) - self.assertEqual(l[-5:], [5, 4, 3, 2, 1]) - self.assertEqual(l[:5], [5, 4, 3, 2, 1]) - - @bigmemtest(size=_2G // 5 + 2, memuse=pointer_size * 5 * 1.5) - def test_sort(self, size): - l = [1, 2, 3, 4, 5] * size - l.sort() - self.assertEqual(len(l), size * 5) - self.assertEqual(l.count(1), size) - self.assertEqual(l[:10], [1] * 10) - self.assertEqual(l[-10:], [5] * 10) - -def test_main(): - support.run_unittest(StrTest, BytesTest, BytearrayTest, - TupleTest, ListTest) - -if __name__ == '__main__': - if len(sys.argv) > 1: - support.set_memlimit(sys.argv[1]) - test_main() +"""Bigmem tests - tests for the 32-bit boundary in containers. + +These tests try to exercise the 32-bit boundary that is sometimes, if +rarely, exceeded in practice, but almost never tested. They are really only +meaningful on 64-bit builds on machines with a *lot* of memory, but the +tests are always run, usually with very low memory limits to make sure the +tests themselves don't suffer from bitrot. To run them for real, pass a +high memory limit to regrtest, with the -M option. +""" + +from test import support +from test.support import bigmemtest, _1G, _2G, _4G + +import unittest +import operator +import sys + +# These tests all use one of the bigmemtest decorators to indicate how much +# memory they use and how much memory they need to be even meaningful. The +# decorators take two arguments: a 'memuse' indicator declaring +# (approximate) bytes per size-unit the test will use (at peak usage), and a +# 'minsize' indicator declaring a minimum *useful* size. A test that +# allocates a bytestring to test various operations near the end will have a +# minsize of at least 2Gb (or it wouldn't reach the 32-bit limit, so the +# test wouldn't be very useful) and a memuse of 1 (one byte per size-unit, +# if it allocates only one big string at a time.) +# +# When run with a memory limit set, both decorators skip tests that need +# more memory than available to be meaningful. The precisionbigmemtest will +# always pass minsize as size, even if there is much more memory available. +# The bigmemtest decorator will scale size upward to fill available memory. +# +# Bigmem testing houserules: +# +# - Try not to allocate too many large objects. It's okay to rely on +# refcounting semantics, and don't forget that 's = create_largestring()' +# doesn't release the old 's' (if it exists) until well after its new +# value has been created. Use 'del s' before the create_largestring call. +# +# - Do *not* compare large objects using assertEqual, assertIn or similar. +# It's a lengthy operation and the errormessage will be utterly useless +# due to its size. To make sure whether a result has the right contents, +# better to use the strip or count methods, or compare meaningful slices. +# +# - Don't forget to test for large indices, offsets and results and such, +# in addition to large sizes. Anything that probes the 32-bit boundary. +# +# - When repeating an object (say, a substring, or a small list) to create +# a large object, make the subobject of a length that is not a power of +# 2. That way, int-wrapping problems are more easily detected. +# +# - Despite the bigmemtest decorator, all tests will actually be called +# with a much smaller number too, in the normal test run (5Kb currently.) +# This is so the tests themselves get frequent testing. +# Consequently, always make all large allocations based on the +# passed-in 'size', and don't rely on the size being very large. Also, +# memuse-per-size should remain sane (less than a few thousand); if your +# test uses more, adjust 'size' upward, instead. + +# BEWARE: it seems that one failing test can yield other subsequent tests to +# fail as well. I do not know whether it is due to memory fragmentation +# issues, or other specifics of the platform malloc() routine. + +ascii_char_size = 1 +ucs2_char_size = 2 +ucs4_char_size = 4 +pointer_size = 4 if sys.maxsize < 2**32 else 8 + + +class BaseStrTest: + + def _test_capitalize(self, size): + _ = self.from_latin1 + SUBSTR = self.from_latin1(' abc def ghi') + s = _('-') * size + SUBSTR + caps = s.capitalize() + self.assertEqual(caps[-len(SUBSTR):], + SUBSTR.capitalize()) + self.assertEqual(caps.lstrip(_('-')), SUBSTR) + + @bigmemtest(size=_2G + 10, memuse=1) + def test_center(self, size): + SUBSTR = self.from_latin1(' abc def ghi') + s = SUBSTR.center(size) + self.assertEqual(len(s), size) + lpadsize = rpadsize = (len(s) - len(SUBSTR)) // 2 + if len(s) % 2: + lpadsize += 1 + self.assertEqual(s[lpadsize:-rpadsize], SUBSTR) + self.assertEqual(s.strip(), SUBSTR.strip()) + + @bigmemtest(size=_2G, memuse=2) + def test_count(self, size): + _ = self.from_latin1 + SUBSTR = _(' abc def ghi') + s = _('.') * size + SUBSTR + self.assertEqual(s.count(_('.')), size) + s += _('.') + self.assertEqual(s.count(_('.')), size + 1) + self.assertEqual(s.count(_(' ')), 3) + self.assertEqual(s.count(_('i')), 1) + self.assertEqual(s.count(_('j')), 0) + + @bigmemtest(size=_2G, memuse=2) + def test_endswith(self, size): + _ = self.from_latin1 + SUBSTR = _(' abc def ghi') + s = _('-') * size + SUBSTR + self.assertTrue(s.endswith(SUBSTR)) + self.assertTrue(s.endswith(s)) + s2 = _('...') + s + self.assertTrue(s2.endswith(s)) + self.assertFalse(s.endswith(_('a') + SUBSTR)) + self.assertFalse(SUBSTR.endswith(s)) + + @bigmemtest(size=_2G + 10, memuse=2) + def test_expandtabs(self, size): + _ = self.from_latin1 + s = _('-') * size + tabsize = 8 + self.assertTrue(s.expandtabs() == s) + del s + slen, remainder = divmod(size, tabsize) + s = _(' \t') * slen + s = s.expandtabs(tabsize) + self.assertEqual(len(s), size - remainder) + self.assertEqual(len(s.strip(_(' '))), 0) + + @bigmemtest(size=_2G, memuse=2) + def test_find(self, size): + _ = self.from_latin1 + SUBSTR = _(' abc def ghi') + sublen = len(SUBSTR) + s = _('').join([SUBSTR, _('-') * size, SUBSTR]) + self.assertEqual(s.find(_(' ')), 0) + self.assertEqual(s.find(SUBSTR), 0) + self.assertEqual(s.find(_(' '), sublen), sublen + size) + self.assertEqual(s.find(SUBSTR, len(SUBSTR)), sublen + size) + self.assertEqual(s.find(_('i')), SUBSTR.find(_('i'))) + self.assertEqual(s.find(_('i'), sublen), + sublen + size + SUBSTR.find(_('i'))) + self.assertEqual(s.find(_('i'), size), + sublen + size + SUBSTR.find(_('i'))) + self.assertEqual(s.find(_('j')), -1) + + @bigmemtest(size=_2G, memuse=2) + def test_index(self, size): + _ = self.from_latin1 + SUBSTR = _(' abc def ghi') + sublen = len(SUBSTR) + s = _('').join([SUBSTR, _('-') * size, SUBSTR]) + self.assertEqual(s.index(_(' ')), 0) + self.assertEqual(s.index(SUBSTR), 0) + self.assertEqual(s.index(_(' '), sublen), sublen + size) + self.assertEqual(s.index(SUBSTR, sublen), sublen + size) + self.assertEqual(s.index(_('i')), SUBSTR.index(_('i'))) + self.assertEqual(s.index(_('i'), sublen), + sublen + size + SUBSTR.index(_('i'))) + self.assertEqual(s.index(_('i'), size), + sublen + size + SUBSTR.index(_('i'))) + self.assertRaises(ValueError, s.index, _('j')) + + @bigmemtest(size=_2G, memuse=2) + def test_isalnum(self, size): + _ = self.from_latin1 + SUBSTR = _('123456') + s = _('a') * size + SUBSTR + self.assertTrue(s.isalnum()) + s += _('.') + self.assertFalse(s.isalnum()) + + @bigmemtest(size=_2G, memuse=2) + def test_isalpha(self, size): + _ = self.from_latin1 + SUBSTR = _('zzzzzzz') + s = _('a') * size + SUBSTR + self.assertTrue(s.isalpha()) + s += _('.') + self.assertFalse(s.isalpha()) + + @bigmemtest(size=_2G, memuse=2) + def test_isdigit(self, size): + _ = self.from_latin1 + SUBSTR = _('123456') + s = _('9') * size + SUBSTR + self.assertTrue(s.isdigit()) + s += _('z') + self.assertFalse(s.isdigit()) + + @bigmemtest(size=_2G, memuse=2) + def test_islower(self, size): + _ = self.from_latin1 + chars = _(''.join( + chr(c) for c in range(255) if not chr(c).isupper())) + repeats = size // len(chars) + 2 + s = chars * repeats + self.assertTrue(s.islower()) + s += _('A') + self.assertFalse(s.islower()) + + @bigmemtest(size=_2G, memuse=2) + def test_isspace(self, size): + _ = self.from_latin1 + whitespace = _(' \f\n\r\t\v') + repeats = size // len(whitespace) + 2 + s = whitespace * repeats + self.assertTrue(s.isspace()) + s += _('j') + self.assertFalse(s.isspace()) + + @bigmemtest(size=_2G, memuse=2) + def test_istitle(self, size): + _ = self.from_latin1 + SUBSTR = _('123456') + s = _('').join([_('A'), _('a') * size, SUBSTR]) + self.assertTrue(s.istitle()) + s += _('A') + self.assertTrue(s.istitle()) + s += _('aA') + self.assertFalse(s.istitle()) + + @bigmemtest(size=_2G, memuse=2) + def test_isupper(self, size): + _ = self.from_latin1 + chars = _(''.join( + chr(c) for c in range(255) if not chr(c).islower())) + repeats = size // len(chars) + 2 + s = chars * repeats + self.assertTrue(s.isupper()) + s += _('a') + self.assertFalse(s.isupper()) + + @bigmemtest(size=_2G, memuse=2) + def test_join(self, size): + _ = self.from_latin1 + s = _('A') * size + x = s.join([_('aaaaa'), _('bbbbb')]) + self.assertEqual(x.count(_('a')), 5) + self.assertEqual(x.count(_('b')), 5) + self.assertTrue(x.startswith(_('aaaaaA'))) + self.assertTrue(x.endswith(_('Abbbbb'))) + + @bigmemtest(size=_2G + 10, memuse=1) + def test_ljust(self, size): + _ = self.from_latin1 + SUBSTR = _(' abc def ghi') + s = SUBSTR.ljust(size) + self.assertTrue(s.startswith(SUBSTR + _(' '))) + self.assertEqual(len(s), size) + self.assertEqual(s.strip(), SUBSTR.strip()) + + @bigmemtest(size=_2G + 10, memuse=2) + def test_lower(self, size): + _ = self.from_latin1 + s = _('A') * size + s = s.lower() + self.assertEqual(len(s), size) + self.assertEqual(s.count(_('a')), size) + + @bigmemtest(size=_2G + 10, memuse=1) + def test_lstrip(self, size): + _ = self.from_latin1 + SUBSTR = _('abc def ghi') + s = SUBSTR.rjust(size) + self.assertEqual(len(s), size) + self.assertEqual(s.lstrip(), SUBSTR.lstrip()) + del s + s = SUBSTR.ljust(size) + self.assertEqual(len(s), size) + # Type-specific optimization + if isinstance(s, (str, bytes)): + stripped = s.lstrip() + self.assertTrue(stripped is s) + + @bigmemtest(size=_2G + 10, memuse=2) + def test_replace(self, size): + _ = self.from_latin1 + replacement = _('a') + s = _(' ') * size + s = s.replace(_(' '), replacement) + self.assertEqual(len(s), size) + self.assertEqual(s.count(replacement), size) + s = s.replace(replacement, _(' '), size - 4) + self.assertEqual(len(s), size) + self.assertEqual(s.count(replacement), 4) + self.assertEqual(s[-10:], _(' aaaa')) + + @bigmemtest(size=_2G, memuse=2) + def test_rfind(self, size): + _ = self.from_latin1 + SUBSTR = _(' abc def ghi') + sublen = len(SUBSTR) + s = _('').join([SUBSTR, _('-') * size, SUBSTR]) + self.assertEqual(s.rfind(_(' ')), sublen + size + SUBSTR.rfind(_(' '))) + self.assertEqual(s.rfind(SUBSTR), sublen + size) + self.assertEqual(s.rfind(_(' '), 0, size), SUBSTR.rfind(_(' '))) + self.assertEqual(s.rfind(SUBSTR, 0, sublen + size), 0) + self.assertEqual(s.rfind(_('i')), sublen + size + SUBSTR.rfind(_('i'))) + self.assertEqual(s.rfind(_('i'), 0, sublen), SUBSTR.rfind(_('i'))) + self.assertEqual(s.rfind(_('i'), 0, sublen + size), + SUBSTR.rfind(_('i'))) + self.assertEqual(s.rfind(_('j')), -1) + + @bigmemtest(size=_2G, memuse=2) + def test_rindex(self, size): + _ = self.from_latin1 + SUBSTR = _(' abc def ghi') + sublen = len(SUBSTR) + s = _('').join([SUBSTR, _('-') * size, SUBSTR]) + self.assertEqual(s.rindex(_(' ')), + sublen + size + SUBSTR.rindex(_(' '))) + self.assertEqual(s.rindex(SUBSTR), sublen + size) + self.assertEqual(s.rindex(_(' '), 0, sublen + size - 1), + SUBSTR.rindex(_(' '))) + self.assertEqual(s.rindex(SUBSTR, 0, sublen + size), 0) + self.assertEqual(s.rindex(_('i')), + sublen + size + SUBSTR.rindex(_('i'))) + self.assertEqual(s.rindex(_('i'), 0, sublen), SUBSTR.rindex(_('i'))) + self.assertEqual(s.rindex(_('i'), 0, sublen + size), + SUBSTR.rindex(_('i'))) + self.assertRaises(ValueError, s.rindex, _('j')) + + @bigmemtest(size=_2G + 10, memuse=1) + def test_rjust(self, size): + _ = self.from_latin1 + SUBSTR = _(' abc def ghi') + s = SUBSTR.ljust(size) + self.assertTrue(s.startswith(SUBSTR + _(' '))) + self.assertEqual(len(s), size) + self.assertEqual(s.strip(), SUBSTR.strip()) + + @bigmemtest(size=_2G + 10, memuse=1) + def test_rstrip(self, size): + _ = self.from_latin1 + SUBSTR = _(' abc def ghi') + s = SUBSTR.ljust(size) + self.assertEqual(len(s), size) + self.assertEqual(s.rstrip(), SUBSTR.rstrip()) + del s + s = SUBSTR.rjust(size) + self.assertEqual(len(s), size) + # Type-specific optimization + if isinstance(s, (str, bytes)): + stripped = s.rstrip() + self.assertTrue(stripped is s) + + # The test takes about size bytes to build a string, and then about + # sqrt(size) substrings of sqrt(size) in size and a list to + # hold sqrt(size) items. It's close but just over 2x size. + @bigmemtest(size=_2G, memuse=2.1) + def test_split_small(self, size): + _ = self.from_latin1 + # Crudely calculate an estimate so that the result of s.split won't + # take up an inordinate amount of memory + chunksize = int(size ** 0.5 + 2) + SUBSTR = _('a') + _(' ') * chunksize + s = SUBSTR * chunksize + l = s.split() + self.assertEqual(len(l), chunksize) + expected = _('a') + for item in l: + self.assertEqual(item, expected) + del l + l = s.split(_('a')) + self.assertEqual(len(l), chunksize + 1) + expected = _(' ') * chunksize + for item in filter(None, l): + self.assertEqual(item, expected) + + # Allocates a string of twice size (and briefly two) and a list of + # size. Because of internal affairs, the s.split() call produces a + # list of size times the same one-character string, so we only + # suffer for the list size. (Otherwise, it'd cost another 48 times + # size in bytes!) Nevertheless, a list of size takes + # 8*size bytes. + @bigmemtest(size=_2G + 5, memuse=ascii_char_size * 2 + pointer_size) + def test_split_large(self, size): + _ = self.from_latin1 + s = _(' a') * size + _(' ') + l = s.split() + self.assertEqual(len(l), size) + self.assertEqual(set(l), set([_('a')])) + del l + l = s.split(_('a')) + self.assertEqual(len(l), size + 1) + self.assertEqual(set(l), set([_(' ')])) + + @bigmemtest(size=_2G, memuse=2.1) + def test_splitlines(self, size): + _ = self.from_latin1 + # Crudely calculate an estimate so that the result of s.split won't + # take up an inordinate amount of memory + chunksize = int(size ** 0.5 + 2) // 2 + SUBSTR = _(' ') * chunksize + _('\n') + _(' ') * chunksize + _('\r\n') + s = SUBSTR * (chunksize * 2) + l = s.splitlines() + self.assertEqual(len(l), chunksize * 4) + expected = _(' ') * chunksize + for item in l: + self.assertEqual(item, expected) + + @bigmemtest(size=_2G, memuse=2) + def test_startswith(self, size): + _ = self.from_latin1 + SUBSTR = _(' abc def ghi') + s = _('-') * size + SUBSTR + self.assertTrue(s.startswith(s)) + self.assertTrue(s.startswith(_('-') * size)) + self.assertFalse(s.startswith(SUBSTR)) + + @bigmemtest(size=_2G, memuse=1) + def test_strip(self, size): + _ = self.from_latin1 + SUBSTR = _(' abc def ghi ') + s = SUBSTR.rjust(size) + self.assertEqual(len(s), size) + self.assertEqual(s.strip(), SUBSTR.strip()) + del s + s = SUBSTR.ljust(size) + self.assertEqual(len(s), size) + self.assertEqual(s.strip(), SUBSTR.strip()) + + def _test_swapcase(self, size): + _ = self.from_latin1 + SUBSTR = _("aBcDeFG12.'\xa9\x00") + sublen = len(SUBSTR) + repeats = size // sublen + 2 + s = SUBSTR * repeats + s = s.swapcase() + self.assertEqual(len(s), sublen * repeats) + self.assertEqual(s[:sublen * 3], SUBSTR.swapcase() * 3) + self.assertEqual(s[-sublen * 3:], SUBSTR.swapcase() * 3) + + def _test_title(self, size): + _ = self.from_latin1 + SUBSTR = _('SpaaHAaaAaham') + s = SUBSTR * (size // len(SUBSTR) + 2) + s = s.title() + self.assertTrue(s.startswith((SUBSTR * 3).title())) + self.assertTrue(s.endswith(SUBSTR.lower() * 3)) + + @bigmemtest(size=_2G, memuse=2) + def test_translate(self, size): + _ = self.from_latin1 + SUBSTR = _('aZz.z.Aaz.') + trans = bytes.maketrans(b'.aZ', b'-!$') + sublen = len(SUBSTR) + repeats = size // sublen + 2 + s = SUBSTR * repeats + s = s.translate(trans) + self.assertEqual(len(s), repeats * sublen) + self.assertEqual(s[:sublen], SUBSTR.translate(trans)) + self.assertEqual(s[-sublen:], SUBSTR.translate(trans)) + self.assertEqual(s.count(_('.')), 0) + self.assertEqual(s.count(_('!')), repeats * 2) + self.assertEqual(s.count(_('z')), repeats * 3) + + @bigmemtest(size=_2G + 5, memuse=2) + def test_upper(self, size): + _ = self.from_latin1 + s = _('a') * size + s = s.upper() + self.assertEqual(len(s), size) + self.assertEqual(s.count(_('A')), size) + + @bigmemtest(size=_2G + 20, memuse=1) + def test_zfill(self, size): + _ = self.from_latin1 + SUBSTR = _('-568324723598234') + s = SUBSTR.zfill(size) + self.assertTrue(s.endswith(_('0') + SUBSTR[1:])) + self.assertTrue(s.startswith(_('-0'))) + self.assertEqual(len(s), size) + self.assertEqual(s.count(_('0')), size - len(SUBSTR)) + + # This test is meaningful even with size < 2G, as long as the + # doubled string is > 2G (but it tests more if both are > 2G :) + @bigmemtest(size=_1G + 2, memuse=3) + def test_concat(self, size): + _ = self.from_latin1 + s = _('.') * size + self.assertEqual(len(s), size) + s = s + s + self.assertEqual(len(s), size * 2) + self.assertEqual(s.count(_('.')), size * 2) + + # This test is meaningful even with size < 2G, as long as the + # repeated string is > 2G (but it tests more if both are > 2G :) + @bigmemtest(size=_1G + 2, memuse=3) + def test_repeat(self, size): + _ = self.from_latin1 + s = _('.') * size + self.assertEqual(len(s), size) + s = s * 2 + self.assertEqual(len(s), size * 2) + self.assertEqual(s.count(_('.')), size * 2) + + @bigmemtest(size=_2G + 20, memuse=2) + def test_slice_and_getitem(self, size): + _ = self.from_latin1 + SUBSTR = _('0123456789') + sublen = len(SUBSTR) + s = SUBSTR * (size // sublen) + stepsize = len(s) // 100 + stepsize = stepsize - (stepsize % sublen) + for i in range(0, len(s) - stepsize, stepsize): + self.assertEqual(s[i], SUBSTR[0]) + self.assertEqual(s[i:i + sublen], SUBSTR) + self.assertEqual(s[i:i + sublen:2], SUBSTR[::2]) + if i > 0: + self.assertEqual(s[i + sublen - 1:i - 1:-3], + SUBSTR[sublen::-3]) + # Make sure we do some slicing and indexing near the end of the + # string, too. + self.assertEqual(s[len(s) - 1], SUBSTR[-1]) + self.assertEqual(s[-1], SUBSTR[-1]) + self.assertEqual(s[len(s) - 10], SUBSTR[0]) + self.assertEqual(s[-sublen], SUBSTR[0]) + self.assertEqual(s[len(s):], _('')) + self.assertEqual(s[len(s) - 1:], SUBSTR[-1:]) + self.assertEqual(s[-1:], SUBSTR[-1:]) + self.assertEqual(s[len(s) - sublen:], SUBSTR) + self.assertEqual(s[-sublen:], SUBSTR) + self.assertEqual(len(s[:]), len(s)) + self.assertEqual(len(s[:len(s) - 5]), len(s) - 5) + self.assertEqual(len(s[5:-5]), len(s) - 10) + + self.assertRaises(IndexError, operator.getitem, s, len(s)) + self.assertRaises(IndexError, operator.getitem, s, len(s) + 1) + self.assertRaises(IndexError, operator.getitem, s, len(s) + 1<<31) + + @bigmemtest(size=_2G, memuse=2) + def test_contains(self, size): + _ = self.from_latin1 + SUBSTR = _('0123456789') + edge = _('-') * (size // 2) + s = _('').join([edge, SUBSTR, edge]) + del edge + self.assertTrue(SUBSTR in s) + self.assertFalse(SUBSTR * 2 in s) + self.assertTrue(_('-') in s) + self.assertFalse(_('a') in s) + s += _('a') + self.assertTrue(_('a') in s) + + @bigmemtest(size=_2G + 10, memuse=2) + def test_compare(self, size): + _ = self.from_latin1 + s1 = _('-') * size + s2 = _('-') * size + self.assertTrue(s1 == s2) + del s2 + s2 = s1 + _('a') + self.assertFalse(s1 == s2) + del s2 + s2 = _('.') * size + self.assertFalse(s1 == s2) + + @bigmemtest(size=_2G + 10, memuse=1) + def test_hash(self, size): + # Not sure if we can do any meaningful tests here... Even if we + # start relying on the exact algorithm used, the result will be + # different depending on the size of the C 'long int'. Even this + # test is dodgy (there's no *guarantee* that the two things should + # have a different hash, even if they, in the current + # implementation, almost always do.) + _ = self.from_latin1 + s = _('\x00') * size + h1 = hash(s) + del s + s = _('\x00') * (size + 1) + self.assertNotEqual(h1, hash(s)) + + +class StrTest(unittest.TestCase, BaseStrTest): + + def from_latin1(self, s): + return s + + def basic_encode_test(self, size, enc, c='.', expectedsize=None): + if expectedsize is None: + expectedsize = size + try: + s = c * size + self.assertEqual(len(s.encode(enc)), expectedsize) + finally: + s = None + + def setUp(self): + # HACK: adjust memory use of tests inherited from BaseStrTest + # according to character size. + self._adjusted = {} + for name in dir(BaseStrTest): + if not name.startswith('test_'): + continue + meth = getattr(type(self), name) + try: + memuse = meth.memuse + except AttributeError: + continue + meth.memuse = ascii_char_size * memuse + self._adjusted[name] = memuse + + def tearDown(self): + for name, memuse in self._adjusted.items(): + getattr(type(self), name).memuse = memuse + + @bigmemtest(size=_2G, memuse=ucs4_char_size * 3 + ascii_char_size * 2) + def test_capitalize(self, size): + self._test_capitalize(size) + + @bigmemtest(size=_2G, memuse=ucs4_char_size * 3 + ascii_char_size * 2) + def test_title(self, size): + self._test_title(size) + + @bigmemtest(size=_2G, memuse=ucs4_char_size * 3 + ascii_char_size * 2) + def test_swapcase(self, size): + self._test_swapcase(size) + + # Many codecs convert to the legacy representation first, explaining + # why we add 'ucs4_char_size' to the 'memuse' below. + + @bigmemtest(size=_2G + 2, memuse=ascii_char_size + 1) + def test_encode(self, size): + return self.basic_encode_test(size, 'utf-8') + + @bigmemtest(size=_4G // 6 + 2, memuse=ascii_char_size + ucs4_char_size + 1) + def test_encode_raw_unicode_escape(self, size): + try: + return self.basic_encode_test(size, 'raw_unicode_escape') + except MemoryError: + pass # acceptable on 32-bit + + @bigmemtest(size=_4G // 5 + 70, memuse=ascii_char_size + 8 + 1) + def test_encode_utf7(self, size): + try: + return self.basic_encode_test(size, 'utf7') + except MemoryError: + pass # acceptable on 32-bit + + # TODO: RUSTPYTHON + @unittest.expectedFailure + @bigmemtest(size=_4G // 4 + 5, memuse=ascii_char_size + ucs4_char_size + 4) + def test_encode_utf32(self, size): + try: + return self.basic_encode_test(size, 'utf32', expectedsize=4 * size + 4) + except MemoryError: + pass # acceptable on 32-bit + + @bigmemtest(size=_2G - 1, memuse=ascii_char_size + 1) + def test_encode_ascii(self, size): + return self.basic_encode_test(size, 'ascii', c='A') + + # str % (...) uses a Py_UCS4 intermediate representation + + @bigmemtest(size=_2G + 10, memuse=ascii_char_size * 2 + ucs4_char_size) + def test_format(self, size): + s = '-' * size + sf = '%s' % (s,) + self.assertTrue(s == sf) + del sf + sf = '..%s..' % (s,) + self.assertEqual(len(sf), len(s) + 4) + self.assertTrue(sf.startswith('..-')) + self.assertTrue(sf.endswith('-..')) + del s, sf + + size //= 2 + edge = '-' * size + s = ''.join([edge, '%s', edge]) + del edge + s = s % '...' + self.assertEqual(len(s), size * 2 + 3) + self.assertEqual(s.count('.'), 3) + self.assertEqual(s.count('-'), size * 2) + + @bigmemtest(size=_2G + 10, memuse=ascii_char_size * 2) + def test_repr_small(self, size): + s = '-' * size + s = repr(s) + self.assertEqual(len(s), size + 2) + self.assertEqual(s[0], "'") + self.assertEqual(s[-1], "'") + self.assertEqual(s.count('-'), size) + del s + # repr() will create a string four times as large as this 'binary + # string', but we don't want to allocate much more than twice + # size in total. (We do extra testing in test_repr_large()) + size = size // 5 * 2 + s = '\x00' * size + s = repr(s) + self.assertEqual(len(s), size * 4 + 2) + self.assertEqual(s[0], "'") + self.assertEqual(s[-1], "'") + self.assertEqual(s.count('\\'), size) + self.assertEqual(s.count('0'), size * 2) + + @bigmemtest(size=_2G + 10, memuse=ascii_char_size * 5) + def test_repr_large(self, size): + s = '\x00' * size + s = repr(s) + self.assertEqual(len(s), size * 4 + 2) + self.assertEqual(s[0], "'") + self.assertEqual(s[-1], "'") + self.assertEqual(s.count('\\'), size) + self.assertEqual(s.count('0'), size * 2) + + # ascii() calls encode('ascii', 'backslashreplace'), which itself + # creates a temporary Py_UNICODE representation in addition to the + # original (Py_UCS2) one + # There's also some overallocation when resizing the ascii() result + # that isn't taken into account here. + # TODO: RUSTPYTHON + @unittest.expectedFailure + @bigmemtest(size=_2G // 5 + 1, memuse=ucs2_char_size + + ucs4_char_size + ascii_char_size * 6) + def test_unicode_repr(self, size): + # Use an assigned, but not printable code point. + # It is in the range of the low surrogates \uDC00-\uDFFF. + char = "\uDCBA" + s = char * size + try: + for f in (repr, ascii): + r = f(s) + self.assertEqual(len(r), 2 + (len(f(char)) - 2) * size) + self.assertTrue(r.endswith(r"\udcba'"), r[-10:]) + r = None + finally: + r = s = None + + @bigmemtest(size=_2G // 5 + 1, memuse=ucs4_char_size * 2 + ascii_char_size * 10) + def test_unicode_repr_wide(self, size): + char = "\U0001DCBA" + s = char * size + try: + for f in (repr, ascii): + r = f(s) + self.assertEqual(len(r), 2 + (len(f(char)) - 2) * size) + self.assertTrue(r.endswith(r"\U0001dcba'"), r[-12:]) + r = None + finally: + r = s = None + + # The original test_translate is overridden here, so as to get the + # correct size estimate: str.translate() uses an intermediate Py_UCS4 + # representation. + + @bigmemtest(size=_2G, memuse=ascii_char_size * 2 + ucs4_char_size) + def test_translate(self, size): + _ = self.from_latin1 + SUBSTR = _('aZz.z.Aaz.') + trans = { + ord(_('.')): _('-'), + ord(_('a')): _('!'), + ord(_('Z')): _('$'), + } + sublen = len(SUBSTR) + repeats = size // sublen + 2 + s = SUBSTR * repeats + s = s.translate(trans) + self.assertEqual(len(s), repeats * sublen) + self.assertEqual(s[:sublen], SUBSTR.translate(trans)) + self.assertEqual(s[-sublen:], SUBSTR.translate(trans)) + self.assertEqual(s.count(_('.')), 0) + self.assertEqual(s.count(_('!')), repeats * 2) + self.assertEqual(s.count(_('z')), repeats * 3) + + +class BytesTest(unittest.TestCase, BaseStrTest): + + def from_latin1(self, s): + return s.encode("latin-1") + + @bigmemtest(size=_2G + 2, memuse=1 + ascii_char_size) + def test_decode(self, size): + s = self.from_latin1('.') * size + self.assertEqual(len(s.decode('utf-8')), size) + + @bigmemtest(size=_2G, memuse=2) + def test_capitalize(self, size): + self._test_capitalize(size) + + @bigmemtest(size=_2G, memuse=2) + def test_title(self, size): + self._test_title(size) + + @bigmemtest(size=_2G, memuse=2) + def test_swapcase(self, size): + self._test_swapcase(size) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_isspace(self, size): + super().test_isspace(size) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_istitle(self, size): + super().test_istitle(size) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_lstrip(self, size): + super().test_lstrip(size) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_rstrip(self, size): + super().test_rstrip(size) + + +class BytearrayTest(unittest.TestCase, BaseStrTest): + + def from_latin1(self, s): + return bytearray(s.encode("latin-1")) + + @bigmemtest(size=_2G + 2, memuse=1 + ascii_char_size) + def test_decode(self, size): + s = self.from_latin1('.') * size + self.assertEqual(len(s.decode('utf-8')), size) + + @bigmemtest(size=_2G, memuse=2) + def test_capitalize(self, size): + self._test_capitalize(size) + + @bigmemtest(size=_2G, memuse=2) + def test_title(self, size): + self._test_title(size) + + @bigmemtest(size=_2G, memuse=2) + def test_swapcase(self, size): + self._test_swapcase(size) + + test_hash = None + test_split_large = None + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_isspace(self, size): + super().test_isspace(size) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_istitle(self, size): + super().test_istitle(size) + +class TupleTest(unittest.TestCase): + + # Tuples have a small, fixed-sized head and an array of pointers to + # data. Since we're testing 64-bit addressing, we can assume that the + # pointers are 8 bytes, and that thus that the tuples take up 8 bytes + # per size. + + # As a side-effect of testing long tuples, these tests happen to test + # having more than 2<<31 references to any given object. Hence the + # use of different types of objects as contents in different tests. + + @bigmemtest(size=_2G + 2, memuse=pointer_size * 2) + def test_compare(self, size): + t1 = ('',) * size + t2 = ('',) * size + self.assertTrue(t1 == t2) + del t2 + t2 = ('',) * (size + 1) + self.assertFalse(t1 == t2) + del t2 + t2 = (1,) * size + self.assertFalse(t1 == t2) + + # Test concatenating into a single tuple of more than 2G in length, + # and concatenating a tuple of more than 2G in length separately, so + # the smaller test still gets run even if there isn't memory for the + # larger test (but we still let the tester know the larger test is + # skipped, in verbose mode.) + def basic_concat_test(self, size): + t = ((),) * size + self.assertEqual(len(t), size) + t = t + t + self.assertEqual(len(t), size * 2) + + @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 3) + def test_concat_small(self, size): + return self.basic_concat_test(size) + + @bigmemtest(size=_2G + 2, memuse=pointer_size * 3) + def test_concat_large(self, size): + return self.basic_concat_test(size) + + @bigmemtest(size=_2G // 5 + 10, memuse=pointer_size * 5) + def test_contains(self, size): + t = (1, 2, 3, 4, 5) * size + self.assertEqual(len(t), size * 5) + self.assertTrue(5 in t) + self.assertFalse((1, 2, 3, 4, 5) in t) + self.assertFalse(0 in t) + + @bigmemtest(size=_2G + 10, memuse=pointer_size) + def test_hash(self, size): + t1 = (0,) * size + h1 = hash(t1) + del t1 + t2 = (0,) * (size + 1) + self.assertFalse(h1 == hash(t2)) + + @bigmemtest(size=_2G + 10, memuse=pointer_size) + def test_index_and_slice(self, size): + t = (None,) * size + self.assertEqual(len(t), size) + self.assertEqual(t[-1], None) + self.assertEqual(t[5], None) + self.assertEqual(t[size - 1], None) + self.assertRaises(IndexError, operator.getitem, t, size) + self.assertEqual(t[:5], (None,) * 5) + self.assertEqual(t[-5:], (None,) * 5) + self.assertEqual(t[20:25], (None,) * 5) + self.assertEqual(t[-25:-20], (None,) * 5) + self.assertEqual(t[size - 5:], (None,) * 5) + self.assertEqual(t[size - 5:size], (None,) * 5) + self.assertEqual(t[size - 6:size - 2], (None,) * 4) + self.assertEqual(t[size:size], ()) + self.assertEqual(t[size:size+5], ()) + + # Like test_concat, split in two. + def basic_test_repeat(self, size): + t = ('',) * size + self.assertEqual(len(t), size) + t = t * 2 + self.assertEqual(len(t), size * 2) + + @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 3) + def test_repeat_small(self, size): + return self.basic_test_repeat(size) + + @bigmemtest(size=_2G + 2, memuse=pointer_size * 3) + def test_repeat_large(self, size): + return self.basic_test_repeat(size) + + @bigmemtest(size=_1G - 1, memuse=12) + def test_repeat_large_2(self, size): + return self.basic_test_repeat(size) + + @bigmemtest(size=_1G - 1, memuse=pointer_size * 2) + def test_from_2G_generator(self, size): + try: + t = tuple(iter([42]*size)) + except MemoryError: + pass # acceptable on 32-bit + else: + self.assertEqual(len(t), size) + self.assertEqual(t[:10], (42,) * 10) + self.assertEqual(t[-10:], (42,) * 10) + + @bigmemtest(size=_1G - 25, memuse=pointer_size * 2) + def test_from_almost_2G_generator(self, size): + try: + t = tuple(iter([42]*size)) + except MemoryError: + pass # acceptable on 32-bit + else: + self.assertEqual(len(t), size) + self.assertEqual(t[:10], (42,) * 10) + self.assertEqual(t[-10:], (42,) * 10) + + # Like test_concat, split in two. + def basic_test_repr(self, size): + t = (False,) * size + s = repr(t) + # The repr of a tuple of Falses is exactly 7 times the tuple length. + self.assertEqual(len(s), size * 7) + self.assertEqual(s[:10], '(False, Fa') + self.assertEqual(s[-10:], 'se, False)') + + @bigmemtest(size=_2G // 7 + 2, memuse=pointer_size + ascii_char_size * 7) + def test_repr_small(self, size): + return self.basic_test_repr(size) + + @bigmemtest(size=_2G + 2, memuse=pointer_size + ascii_char_size * 7) + def test_repr_large(self, size): + return self.basic_test_repr(size) + +class ListTest(unittest.TestCase): + + # Like tuples, lists have a small, fixed-sized head and an array of + # pointers to data, so 8 bytes per size. Also like tuples, we make the + # lists hold references to various objects to test their refcount + # limits. + + @bigmemtest(size=_2G + 2, memuse=pointer_size * 2) + def test_compare(self, size): + l1 = [''] * size + l2 = [''] * size + self.assertTrue(l1 == l2) + del l2 + l2 = [''] * (size + 1) + self.assertFalse(l1 == l2) + del l2 + l2 = [2] * size + self.assertFalse(l1 == l2) + + # Test concatenating into a single list of more than 2G in length, + # and concatenating a list of more than 2G in length separately, so + # the smaller test still gets run even if there isn't memory for the + # larger test (but we still let the tester know the larger test is + # skipped, in verbose mode.) + def basic_test_concat(self, size): + l = [[]] * size + self.assertEqual(len(l), size) + l = l + l + self.assertEqual(len(l), size * 2) + + @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 3) + def test_concat_small(self, size): + return self.basic_test_concat(size) + + @bigmemtest(size=_2G + 2, memuse=pointer_size * 3) + def test_concat_large(self, size): + return self.basic_test_concat(size) + + # XXX This tests suffers from overallocation, just like test_append. + # This should be fixed in future. + def basic_test_inplace_concat(self, size): + l = [sys.stdout] * size + l += l + self.assertEqual(len(l), size * 2) + self.assertTrue(l[0] is l[-1]) + self.assertTrue(l[size - 1] is l[size + 1]) + + @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 2 * 9/8) + def test_inplace_concat_small(self, size): + return self.basic_test_inplace_concat(size) + + @bigmemtest(size=_2G + 2, memuse=pointer_size * 2 * 9/8) + def test_inplace_concat_large(self, size): + return self.basic_test_inplace_concat(size) + + @bigmemtest(size=_2G // 5 + 10, memuse=pointer_size * 5) + def test_contains(self, size): + l = [1, 2, 3, 4, 5] * size + self.assertEqual(len(l), size * 5) + self.assertTrue(5 in l) + self.assertFalse([1, 2, 3, 4, 5] in l) + self.assertFalse(0 in l) + + @bigmemtest(size=_2G + 10, memuse=pointer_size) + def test_hash(self, size): + l = [0] * size + self.assertRaises(TypeError, hash, l) + + @bigmemtest(size=_2G + 10, memuse=pointer_size) + def test_index_and_slice(self, size): + l = [None] * size + self.assertEqual(len(l), size) + self.assertEqual(l[-1], None) + self.assertEqual(l[5], None) + self.assertEqual(l[size - 1], None) + self.assertRaises(IndexError, operator.getitem, l, size) + self.assertEqual(l[:5], [None] * 5) + self.assertEqual(l[-5:], [None] * 5) + self.assertEqual(l[20:25], [None] * 5) + self.assertEqual(l[-25:-20], [None] * 5) + self.assertEqual(l[size - 5:], [None] * 5) + self.assertEqual(l[size - 5:size], [None] * 5) + self.assertEqual(l[size - 6:size - 2], [None] * 4) + self.assertEqual(l[size:size], []) + self.assertEqual(l[size:size+5], []) + + l[size - 2] = 5 + self.assertEqual(len(l), size) + self.assertEqual(l[-3:], [None, 5, None]) + self.assertEqual(l.count(5), 1) + self.assertRaises(IndexError, operator.setitem, l, size, 6) + self.assertEqual(len(l), size) + + l[size - 7:] = [1, 2, 3, 4, 5] + size -= 2 + self.assertEqual(len(l), size) + self.assertEqual(l[-7:], [None, None, 1, 2, 3, 4, 5]) + + l[:7] = [1, 2, 3, 4, 5] + size -= 2 + self.assertEqual(len(l), size) + self.assertEqual(l[:7], [1, 2, 3, 4, 5, None, None]) + + del l[size - 1] + size -= 1 + self.assertEqual(len(l), size) + self.assertEqual(l[-1], 4) + + del l[-2:] + size -= 2 + self.assertEqual(len(l), size) + self.assertEqual(l[-1], 2) + + del l[0] + size -= 1 + self.assertEqual(len(l), size) + self.assertEqual(l[0], 2) + + del l[:2] + size -= 2 + self.assertEqual(len(l), size) + self.assertEqual(l[0], 4) + + # Like test_concat, split in two. + def basic_test_repeat(self, size): + l = [] * size + self.assertFalse(l) + l = [''] * size + self.assertEqual(len(l), size) + l = l * 2 + self.assertEqual(len(l), size * 2) + + @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 3) + def test_repeat_small(self, size): + return self.basic_test_repeat(size) + + @bigmemtest(size=_2G + 2, memuse=pointer_size * 3) + def test_repeat_large(self, size): + return self.basic_test_repeat(size) + + # XXX This tests suffers from overallocation, just like test_append. + # This should be fixed in future. + def basic_test_inplace_repeat(self, size): + l = [''] + l *= size + self.assertEqual(len(l), size) + self.assertTrue(l[0] is l[-1]) + del l + + l = [''] * size + l *= 2 + self.assertEqual(len(l), size * 2) + self.assertTrue(l[size - 1] is l[-1]) + + @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 2 * 9/8) + def test_inplace_repeat_small(self, size): + return self.basic_test_inplace_repeat(size) + + @bigmemtest(size=_2G + 2, memuse=pointer_size * 2 * 9/8) + def test_inplace_repeat_large(self, size): + return self.basic_test_inplace_repeat(size) + + def basic_test_repr(self, size): + l = [False] * size + s = repr(l) + # The repr of a list of Falses is exactly 7 times the list length. + self.assertEqual(len(s), size * 7) + self.assertEqual(s[:10], '[False, Fa') + self.assertEqual(s[-10:], 'se, False]') + self.assertEqual(s.count('F'), size) + + @bigmemtest(size=_2G // 7 + 2, memuse=pointer_size + ascii_char_size * 7) + def test_repr_small(self, size): + return self.basic_test_repr(size) + + @bigmemtest(size=_2G + 2, memuse=pointer_size + ascii_char_size * 7) + def test_repr_large(self, size): + return self.basic_test_repr(size) + + # list overallocates ~1/8th of the total size (on first expansion) so + # the single list.append call puts memuse at 9 bytes per size. + @bigmemtest(size=_2G, memuse=pointer_size * 9/8) + def test_append(self, size): + l = [object()] * size + l.append(object()) + self.assertEqual(len(l), size+1) + self.assertTrue(l[-3] is l[-2]) + self.assertFalse(l[-2] is l[-1]) + + @bigmemtest(size=_2G // 5 + 2, memuse=pointer_size * 5) + def test_count(self, size): + l = [1, 2, 3, 4, 5] * size + self.assertEqual(l.count(1), size) + self.assertEqual(l.count("1"), 0) + + # XXX This tests suffers from overallocation, just like test_append. + # This should be fixed in future. + def basic_test_extend(self, size): + l = [object] * size + l.extend(l) + self.assertEqual(len(l), size * 2) + self.assertTrue(l[0] is l[-1]) + self.assertTrue(l[size - 1] is l[size + 1]) + + @bigmemtest(size=_2G // 2 + 2, memuse=pointer_size * 2 * 9/8) + def test_extend_small(self, size): + return self.basic_test_extend(size) + + @bigmemtest(size=_2G + 2, memuse=pointer_size * 2 * 9/8) + def test_extend_large(self, size): + return self.basic_test_extend(size) + + @bigmemtest(size=_2G // 5 + 2, memuse=pointer_size * 5) + def test_index(self, size): + l = [1, 2, 3, 4, 5] * size + size *= 5 + self.assertEqual(l.index(1), 0) + self.assertEqual(l.index(5, size - 5), size - 1) + self.assertEqual(l.index(5, size - 5, size), size - 1) + self.assertRaises(ValueError, l.index, 1, size - 4, size) + self.assertRaises(ValueError, l.index, 6) + + # This tests suffers from overallocation, just like test_append. + @bigmemtest(size=_2G + 10, memuse=pointer_size * 9/8) + def test_insert(self, size): + l = [1.0] * size + l.insert(size - 1, "A") + size += 1 + self.assertEqual(len(l), size) + self.assertEqual(l[-3:], [1.0, "A", 1.0]) + + l.insert(size + 1, "B") + size += 1 + self.assertEqual(len(l), size) + self.assertEqual(l[-3:], ["A", 1.0, "B"]) + + l.insert(1, "C") + size += 1 + self.assertEqual(len(l), size) + self.assertEqual(l[:3], [1.0, "C", 1.0]) + self.assertEqual(l[size - 3:], ["A", 1.0, "B"]) + + @bigmemtest(size=_2G // 5 + 4, memuse=pointer_size * 5) + def test_pop(self, size): + l = ["a", "b", "c", "d", "e"] * size + size *= 5 + self.assertEqual(len(l), size) + + item = l.pop() + size -= 1 + self.assertEqual(len(l), size) + self.assertEqual(item, "e") + self.assertEqual(l[-2:], ["c", "d"]) + + item = l.pop(0) + size -= 1 + self.assertEqual(len(l), size) + self.assertEqual(item, "a") + self.assertEqual(l[:2], ["b", "c"]) + + item = l.pop(size - 2) + size -= 1 + self.assertEqual(len(l), size) + self.assertEqual(item, "c") + self.assertEqual(l[-2:], ["b", "d"]) + + @bigmemtest(size=_2G + 10, memuse=pointer_size) + def test_remove(self, size): + l = [10] * size + self.assertEqual(len(l), size) + + l.remove(10) + size -= 1 + self.assertEqual(len(l), size) + + # Because of the earlier l.remove(), this append doesn't trigger + # a resize. + l.append(5) + size += 1 + self.assertEqual(len(l), size) + self.assertEqual(l[-2:], [10, 5]) + l.remove(5) + size -= 1 + self.assertEqual(len(l), size) + self.assertEqual(l[-2:], [10, 10]) + + @bigmemtest(size=_2G // 5 + 2, memuse=pointer_size * 5) + def test_reverse(self, size): + l = [1, 2, 3, 4, 5] * size + l.reverse() + self.assertEqual(len(l), size * 5) + self.assertEqual(l[-5:], [5, 4, 3, 2, 1]) + self.assertEqual(l[:5], [5, 4, 3, 2, 1]) + + @bigmemtest(size=_2G // 5 + 2, memuse=pointer_size * 5 * 1.5) + def test_sort(self, size): + l = [1, 2, 3, 4, 5] * size + l.sort() + self.assertEqual(len(l), size * 5) + self.assertEqual(l.count(1), size) + self.assertEqual(l[:10], [1] * 10) + self.assertEqual(l[-10:], [5] * 10) + +def test_main(): + support.run_unittest(StrTest, BytesTest, BytearrayTest, + TupleTest, ListTest) + +if __name__ == '__main__': + if len(sys.argv) > 1: + support.set_memlimit(sys.argv[1]) + test_main() diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 43a7f404f0..6265bc0fc9 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -1,4424 +1,4424 @@ -# -# The ndarray object from _testbuffer.c is a complete implementation of -# a PEP-3118 buffer provider. It is independent from NumPy's ndarray -# and the tests don't require NumPy. -# -# If NumPy is present, some tests check both ndarray implementations -# against each other. -# -# Most ndarray tests also check that memoryview(ndarray) behaves in -# the same way as the original. Thus, a substantial part of the -# memoryview tests is now in this module. -# -# Written and designed by Stefan Krah for Python 3.3. -# - -import contextlib -import unittest -from test import support -from test.support import os_helper -from itertools import permutations, product -from random import randrange, sample, choice -import warnings -import sys, array, io, os -from decimal import Decimal -from fractions import Fraction - -try: - from _testbuffer import * -except ImportError: - ndarray = None - -try: - import struct -except ImportError: - struct = None - -try: - import ctypes -except ImportError: - ctypes = None - -try: - with os_helper.EnvironmentVarGuard() as os.environ, \ - warnings.catch_warnings(): - from numpy import ndarray as numpy_array -except ImportError: - numpy_array = None - - -SHORT_TEST = True - - -# ====================================================================== -# Random lists by format specifier -# ====================================================================== - -# Native format chars and their ranges. -NATIVE = { - '?':0, 'c':0, 'b':0, 'B':0, - 'h':0, 'H':0, 'i':0, 'I':0, - 'l':0, 'L':0, 'n':0, 'N':0, - 'f':0, 'd':0, 'P':0 -} - -# NumPy does not have 'n' or 'N': -if numpy_array: - del NATIVE['n'] - del NATIVE['N'] - -if struct: - try: - # Add "qQ" if present in native mode. - struct.pack('Q', 2**64-1) - NATIVE['q'] = 0 - NATIVE['Q'] = 0 - except struct.error: - pass - -# Standard format chars and their ranges. -STANDARD = { - '?':(0, 2), 'c':(0, 1<<8), - 'b':(-(1<<7), 1<<7), 'B':(0, 1<<8), - 'h':(-(1<<15), 1<<15), 'H':(0, 1<<16), - 'i':(-(1<<31), 1<<31), 'I':(0, 1<<32), - 'l':(-(1<<31), 1<<31), 'L':(0, 1<<32), - 'q':(-(1<<63), 1<<63), 'Q':(0, 1<<64), - 'f':(-(1<<63), 1<<63), 'd':(-(1<<1023), 1<<1023) -} - -def native_type_range(fmt): - """Return range of a native type.""" - if fmt == 'c': - lh = (0, 256) - elif fmt == '?': - lh = (0, 2) - elif fmt == 'f': - lh = (-(1<<63), 1<<63) - elif fmt == 'd': - lh = (-(1<<1023), 1<<1023) - else: - for exp in (128, 127, 64, 63, 32, 31, 16, 15, 8, 7): - try: - struct.pack(fmt, (1<':STANDARD, - '=':STANDARD, - '!':STANDARD -} - -if struct: - for fmt in fmtdict['@']: - fmtdict['@'][fmt] = native_type_range(fmt) - -MEMORYVIEW = NATIVE.copy() -ARRAY = NATIVE.copy() -for k in NATIVE: - if not k in "bBhHiIlLfd": - del ARRAY[k] - -BYTEFMT = NATIVE.copy() -for k in NATIVE: - if not k in "Bbc": - del BYTEFMT[k] - -fmtdict['m'] = MEMORYVIEW -fmtdict['@m'] = MEMORYVIEW -fmtdict['a'] = ARRAY -fmtdict['b'] = BYTEFMT -fmtdict['@b'] = BYTEFMT - -# Capabilities of the test objects: -MODE = 0 -MULT = 1 -cap = { # format chars # multiplier - 'ndarray': (['', '@', '<', '>', '=', '!'], ['', '1', '2', '3']), - 'array': (['a'], ['']), - 'numpy': ([''], ['']), - 'memoryview': (['@m', 'm'], ['']), - 'bytefmt': (['@b', 'b'], ['']), -} - -def randrange_fmt(mode, char, obj): - """Return random item for a type specified by a mode and a single - format character.""" - x = randrange(*fmtdict[mode][char]) - if char == 'c': - x = bytes([x]) - if obj == 'numpy' and x == b'\x00': - # http://projects.scipy.org/numpy/ticket/1925 - x = b'\x01' - if char == '?': - x = bool(x) - if char == 'f' or char == 'd': - x = struct.pack(char, x) - x = struct.unpack(char, x)[0] - return x - -def gen_item(fmt, obj): - """Return single random item.""" - mode, chars = fmt.split('#') - x = [] - for c in chars: - x.append(randrange_fmt(mode, c, obj)) - return x[0] if len(x) == 1 else tuple(x) - -def gen_items(n, fmt, obj): - """Return a list of random items (or a scalar).""" - if n == 0: - return gen_item(fmt, obj) - lst = [0] * n - for i in range(n): - lst[i] = gen_item(fmt, obj) - return lst - -def struct_items(n, obj): - mode = choice(cap[obj][MODE]) - xfmt = mode + '#' - fmt = mode.strip('amb') - nmemb = randrange(2, 10) # number of struct members - for _ in range(nmemb): - char = choice(tuple(fmtdict[mode])) - multiplier = choice(cap[obj][MULT]) - xfmt += (char * int(multiplier if multiplier else 1)) - fmt += (multiplier + char) - items = gen_items(n, xfmt, obj) - item = gen_item(xfmt, obj) - return fmt, items, item - -def randitems(n, obj='ndarray', mode=None, char=None): - """Return random format, items, item.""" - if mode is None: - mode = choice(cap[obj][MODE]) - if char is None: - char = choice(tuple(fmtdict[mode])) - multiplier = choice(cap[obj][MULT]) - fmt = mode + '#' + char * int(multiplier if multiplier else 1) - items = gen_items(n, fmt, obj) - item = gen_item(fmt, obj) - fmt = mode.strip('amb') + multiplier + char - return fmt, items, item - -def iter_mode(n, obj='ndarray'): - """Iterate through supported mode/char combinations.""" - for mode in cap[obj][MODE]: - for char in fmtdict[mode]: - yield randitems(n, obj, mode, char) - -def iter_format(nitems, testobj='ndarray'): - """Yield (format, items, item) for all possible modes and format - characters plus one random compound format string.""" - for t in iter_mode(nitems, testobj): - yield t - if testobj != 'ndarray': - return - yield struct_items(nitems, testobj) - - -def is_byte_format(fmt): - return 'c' in fmt or 'b' in fmt or 'B' in fmt - -def is_memoryview_format(fmt): - """format suitable for memoryview""" - x = len(fmt) - return ((x == 1 or (x == 2 and fmt[0] == '@')) and - fmt[x-1] in MEMORYVIEW) - -NON_BYTE_FORMAT = [c for c in fmtdict['@'] if not is_byte_format(c)] - - -# ====================================================================== -# Multi-dimensional tolist(), slicing and slice assignments -# ====================================================================== - -def atomp(lst): - """Tuple items (representing structs) are regarded as atoms.""" - return not isinstance(lst, list) - -def listp(lst): - return isinstance(lst, list) - -def prod(lst): - """Product of list elements.""" - if len(lst) == 0: - return 0 - x = lst[0] - for v in lst[1:]: - x *= v - return x - -def strides_from_shape(ndim, shape, itemsize, layout): - """Calculate strides of a contiguous array. Layout is 'C' or - 'F' (Fortran).""" - if ndim == 0: - return () - if layout == 'C': - strides = list(shape[1:]) + [itemsize] - for i in range(ndim-2, -1, -1): - strides[i] *= strides[i+1] - else: - strides = [itemsize] + list(shape[:-1]) - for i in range(1, ndim): - strides[i] *= strides[i-1] - return strides - -def _ca(items, s): - """Convert flat item list to the nested list representation of a - multidimensional C array with shape 's'.""" - if atomp(items): - return items - if len(s) == 0: - return items[0] - lst = [0] * s[0] - stride = len(items) // s[0] if s[0] else 0 - for i in range(s[0]): - start = i*stride - lst[i] = _ca(items[start:start+stride], s[1:]) - return lst - -def _fa(items, s): - """Convert flat item list to the nested list representation of a - multidimensional Fortran array with shape 's'.""" - if atomp(items): - return items - if len(s) == 0: - return items[0] - lst = [0] * s[0] - stride = s[0] - for i in range(s[0]): - lst[i] = _fa(items[i::stride], s[1:]) - return lst - -def carray(items, shape): - if listp(items) and not 0 in shape and prod(shape) != len(items): - raise ValueError("prod(shape) != len(items)") - return _ca(items, shape) - -def farray(items, shape): - if listp(items) and not 0 in shape and prod(shape) != len(items): - raise ValueError("prod(shape) != len(items)") - return _fa(items, shape) - -def indices(shape): - """Generate all possible tuples of indices.""" - iterables = [range(v) for v in shape] - return product(*iterables) - -def getindex(ndim, ind, strides): - """Convert multi-dimensional index to the position in the flat list.""" - ret = 0 - for i in range(ndim): - ret += strides[i] * ind[i] - return ret - -def transpose(src, shape): - """Transpose flat item list that is regarded as a multi-dimensional - matrix defined by shape: dest...[k][j][i] = src[i][j][k]... """ - if not shape: - return src - ndim = len(shape) - sstrides = strides_from_shape(ndim, shape, 1, 'C') - dstrides = strides_from_shape(ndim, shape[::-1], 1, 'C') - dest = [0] * len(src) - for ind in indices(shape): - fr = getindex(ndim, ind, sstrides) - to = getindex(ndim, ind[::-1], dstrides) - dest[to] = src[fr] - return dest - -def _flatten(lst): - """flatten list""" - if lst == []: - return lst - if atomp(lst): - return [lst] - return _flatten(lst[0]) + _flatten(lst[1:]) - -def flatten(lst): - """flatten list or return scalar""" - if atomp(lst): # scalar - return lst - return _flatten(lst) - -def slice_shape(lst, slices): - """Get the shape of lst after slicing: slices is a list of slice - objects.""" - if atomp(lst): - return [] - return [len(lst[slices[0]])] + slice_shape(lst[0], slices[1:]) - -def multislice(lst, slices): - """Multi-dimensional slicing: slices is a list of slice objects.""" - if atomp(lst): - return lst - return [multislice(sublst, slices[1:]) for sublst in lst[slices[0]]] - -def m_assign(llst, rlst, lslices, rslices): - """Multi-dimensional slice assignment: llst and rlst are the operands, - lslices and rslices are lists of slice objects. llst and rlst must - have the same structure. - - For a two-dimensional example, this is not implemented in Python: - - llst[0:3:2, 0:3:2] = rlst[1:3:1, 1:3:1] - - Instead we write: - - lslices = [slice(0,3,2), slice(0,3,2)] - rslices = [slice(1,3,1), slice(1,3,1)] - multislice_assign(llst, rlst, lslices, rslices) - """ - if atomp(rlst): - return rlst - rlst = [m_assign(l, r, lslices[1:], rslices[1:]) - for l, r in zip(llst[lslices[0]], rlst[rslices[0]])] - llst[lslices[0]] = rlst - return llst - -def cmp_structure(llst, rlst, lslices, rslices): - """Compare the structure of llst[lslices] and rlst[rslices].""" - lshape = slice_shape(llst, lslices) - rshape = slice_shape(rlst, rslices) - if (len(lshape) != len(rshape)): - return -1 - for i in range(len(lshape)): - if lshape[i] != rshape[i]: - return -1 - if lshape[i] == 0: - return 0 - return 0 - -def multislice_assign(llst, rlst, lslices, rslices): - """Return llst after assigning: llst[lslices] = rlst[rslices]""" - if cmp_structure(llst, rlst, lslices, rslices) < 0: - raise ValueError("lvalue and rvalue have different structures") - return m_assign(llst, rlst, lslices, rslices) - - -# ====================================================================== -# Random structures -# ====================================================================== - -# -# PEP-3118 is very permissive with respect to the contents of a -# Py_buffer. In particular: -# -# - shape can be zero -# - strides can be any integer, including zero -# - offset can point to any location in the underlying -# memory block, provided that it is a multiple of -# itemsize. -# -# The functions in this section test and verify random structures -# in full generality. A structure is valid iff it fits in the -# underlying memory block. -# -# The structure 't' (short for 'tuple') is fully defined by: -# -# t = (memlen, itemsize, ndim, shape, strides, offset) -# - -def verify_structure(memlen, itemsize, ndim, shape, strides, offset): - """Verify that the parameters represent a valid array within - the bounds of the allocated memory: - char *mem: start of the physical memory block - memlen: length of the physical memory block - offset: (char *)buf - mem - """ - if offset % itemsize: - return False - if offset < 0 or offset+itemsize > memlen: - return False - if any(v % itemsize for v in strides): - return False - - if ndim <= 0: - return ndim == 0 and not shape and not strides - if 0 in shape: - return True - - imin = sum(strides[j]*(shape[j]-1) for j in range(ndim) - if strides[j] <= 0) - imax = sum(strides[j]*(shape[j]-1) for j in range(ndim) - if strides[j] > 0) - - return 0 <= offset+imin and offset+imax+itemsize <= memlen - -def get_item(lst, indices): - for i in indices: - lst = lst[i] - return lst - -def memory_index(indices, t): - """Location of an item in the underlying memory.""" - memlen, itemsize, ndim, shape, strides, offset = t - p = offset - for i in range(ndim): - p += strides[i]*indices[i] - return p - -def is_overlapping(t): - """The structure 't' is overlapping if at least one memory location - is visited twice while iterating through all possible tuples of - indices.""" - memlen, itemsize, ndim, shape, strides, offset = t - visited = 1<= 95 and valid: - minshape = 0 - elif n >= 90: - minshape = 1 - shape = [0] * ndim - - for i in range(ndim): - shape[i] = randrange(minshape, maxshape+1) - else: - ndim = len(shape) - - maxstride = 5 - n = randrange(100) - zero_stride = True if n >= 95 and n & 1 else False - - strides = [0] * ndim - strides[ndim-1] = itemsize * randrange(-maxstride, maxstride+1) - if not zero_stride and strides[ndim-1] == 0: - strides[ndim-1] = itemsize - - for i in range(ndim-2, -1, -1): - maxstride *= shape[i+1] if shape[i+1] else 1 - if zero_stride: - strides[i] = itemsize * randrange(-maxstride, maxstride+1) - else: - strides[i] = ((1,-1)[randrange(2)] * - itemsize * randrange(1, maxstride+1)) - - imin = imax = 0 - if not 0 in shape: - imin = sum(strides[j]*(shape[j]-1) for j in range(ndim) - if strides[j] <= 0) - imax = sum(strides[j]*(shape[j]-1) for j in range(ndim) - if strides[j] > 0) - - nitems = imax - imin - if valid: - offset = -imin * itemsize - memlen = offset + (imax+1) * itemsize - else: - memlen = (-imin + imax) * itemsize - offset = -imin-itemsize if randrange(2) == 0 else memlen - return memlen, itemsize, ndim, shape, strides, offset - -def randslice_from_slicelen(slicelen, listlen): - """Create a random slice of len slicelen that fits into listlen.""" - maxstart = listlen - slicelen - start = randrange(maxstart+1) - maxstep = (listlen - start) // slicelen if slicelen else 1 - step = randrange(1, maxstep+1) - stop = start + slicelen * step - s = slice(start, stop, step) - _, _, _, control = slice_indices(s, listlen) - if control != slicelen: - raise RuntimeError - return s - -def randslice_from_shape(ndim, shape): - """Create two sets of slices for an array x with shape 'shape' - such that shapeof(x[lslices]) == shapeof(x[rslices]).""" - lslices = [0] * ndim - rslices = [0] * ndim - for n in range(ndim): - l = shape[n] - slicelen = randrange(1, l+1) if l > 0 else 0 - lslices[n] = randslice_from_slicelen(slicelen, l) - rslices[n] = randslice_from_slicelen(slicelen, l) - return tuple(lslices), tuple(rslices) - -def rand_aligned_slices(maxdim=5, maxshape=16): - """Create (lshape, rshape, tuple(lslices), tuple(rslices)) such that - shapeof(x[lslices]) == shapeof(y[rslices]), where x is an array - with shape 'lshape' and y is an array with shape 'rshape'.""" - ndim = randrange(1, maxdim+1) - minshape = 2 - n = randrange(100) - if n >= 95: - minshape = 0 - elif n >= 90: - minshape = 1 - all_random = True if randrange(100) >= 80 else False - lshape = [0]*ndim; rshape = [0]*ndim - lslices = [0]*ndim; rslices = [0]*ndim - - for n in range(ndim): - small = randrange(minshape, maxshape+1) - big = randrange(minshape, maxshape+1) - if big < small: - big, small = small, big - - # Create a slice that fits the smaller value. - if all_random: - start = randrange(-small, small+1) - stop = randrange(-small, small+1) - step = (1,-1)[randrange(2)] * randrange(1, small+2) - s_small = slice(start, stop, step) - _, _, _, slicelen = slice_indices(s_small, small) - else: - slicelen = randrange(1, small+1) if small > 0 else 0 - s_small = randslice_from_slicelen(slicelen, small) - - # Create a slice of the same length for the bigger value. - s_big = randslice_from_slicelen(slicelen, big) - if randrange(2) == 0: - rshape[n], lshape[n] = big, small - rslices[n], lslices[n] = s_big, s_small - else: - rshape[n], lshape[n] = small, big - rslices[n], lslices[n] = s_small, s_big - - return lshape, rshape, tuple(lslices), tuple(rslices) - -def randitems_from_structure(fmt, t): - """Return a list of random items for structure 't' with format - 'fmtchar'.""" - memlen, itemsize, _, _, _, _ = t - return gen_items(memlen//itemsize, '#'+fmt, 'numpy') - -def ndarray_from_structure(items, fmt, t, flags=0): - """Return ndarray from the tuple returned by rand_structure()""" - memlen, itemsize, ndim, shape, strides, offset = t - return ndarray(items, shape=shape, strides=strides, format=fmt, - offset=offset, flags=ND_WRITABLE|flags) - -def numpy_array_from_structure(items, fmt, t): - """Return numpy_array from the tuple returned by rand_structure()""" - memlen, itemsize, ndim, shape, strides, offset = t - buf = bytearray(memlen) - for j, v in enumerate(items): - struct.pack_into(fmt, buf, j*itemsize, v) - return numpy_array(buffer=buf, shape=shape, strides=strides, - dtype=fmt, offset=offset) - - -# ====================================================================== -# memoryview casts -# ====================================================================== - -def cast_items(exporter, fmt, itemsize, shape=None): - """Interpret the raw memory of 'exporter' as a list of items with - size 'itemsize'. If shape=None, the new structure is assumed to - be 1-D with n * itemsize = bytelen. If shape is given, the usual - constraint for contiguous arrays prod(shape) * itemsize = bytelen - applies. On success, return (items, shape). If the constraints - cannot be met, return (None, None). If a chunk of bytes is interpreted - as NaN as a result of float conversion, return ('nan', None).""" - bytelen = exporter.nbytes - if shape: - if prod(shape) * itemsize != bytelen: - return None, shape - elif shape == []: - if exporter.ndim == 0 or itemsize != bytelen: - return None, shape - else: - n, r = divmod(bytelen, itemsize) - shape = [n] - if r != 0: - return None, shape - - mem = exporter.tobytes() - byteitems = [mem[i:i+itemsize] for i in range(0, len(mem), itemsize)] - - items = [] - for v in byteitems: - item = struct.unpack(fmt, v)[0] - if item != item: - return 'nan', shape - items.append(item) - - return (items, shape) if shape != [] else (items[0], shape) - -def gencastshapes(): - """Generate shapes to test casting.""" - for n in range(32): - yield [n] - ndim = randrange(4, 6) - minshape = 1 if randrange(100) > 80 else 2 - yield [randrange(minshape, 5) for _ in range(ndim)] - ndim = randrange(2, 4) - minshape = 1 if randrange(100) > 80 else 2 - yield [randrange(minshape, 5) for _ in range(ndim)] - - -# ====================================================================== -# Actual tests -# ====================================================================== - -def genslices(n): - """Generate all possible slices for a single dimension.""" - return product(range(-n, n+1), range(-n, n+1), range(-n, n+1)) - -def genslices_ndim(ndim, shape): - """Generate all possible slice tuples for 'shape'.""" - iterables = [genslices(shape[n]) for n in range(ndim)] - return product(*iterables) - -def rslice(n, allow_empty=False): - """Generate random slice for a single dimension of length n. - If zero=True, the slices may be empty, otherwise they will - be non-empty.""" - minlen = 0 if allow_empty or n == 0 else 1 - slicelen = randrange(minlen, n+1) - return randslice_from_slicelen(slicelen, n) - -def rslices(n, allow_empty=False): - """Generate random slices for a single dimension.""" - for _ in range(5): - yield rslice(n, allow_empty) - -def rslices_ndim(ndim, shape, iterations=5): - """Generate random slice tuples for 'shape'.""" - # non-empty slices - for _ in range(iterations): - yield tuple(rslice(shape[n]) for n in range(ndim)) - # possibly empty slices - for _ in range(iterations): - yield tuple(rslice(shape[n], allow_empty=True) for n in range(ndim)) - # invalid slices - yield tuple(slice(0,1,0) for _ in range(ndim)) - -def rpermutation(iterable, r=None): - pool = tuple(iterable) - r = len(pool) if r is None else r - yield tuple(sample(pool, r)) - -def ndarray_print(nd): - """Print ndarray for debugging.""" - try: - x = nd.tolist() - except (TypeError, NotImplementedError): - x = nd.tobytes() - if isinstance(nd, ndarray): - offset = nd.offset - flags = nd.flags - else: - offset = 'unknown' - flags = 'unknown' - print("ndarray(%s, shape=%s, strides=%s, suboffsets=%s, offset=%s, " - "format='%s', itemsize=%s, flags=%s)" % - (x, nd.shape, nd.strides, nd.suboffsets, offset, - nd.format, nd.itemsize, flags)) - sys.stdout.flush() - - -ITERATIONS = 100 -MAXDIM = 5 -MAXSHAPE = 10 - -if SHORT_TEST: - ITERATIONS = 10 - MAXDIM = 3 - MAXSHAPE = 4 - genslices = rslices - genslices_ndim = rslices_ndim - permutations = rpermutation - - -@unittest.skipUnless(struct, 'struct module required for this test.') -@unittest.skipUnless(ndarray, 'ndarray object required for this test') -class TestBufferProtocol(unittest.TestCase): - - def setUp(self): - # The suboffsets tests need sizeof(void *). - self.sizeof_void_p = get_sizeof_void_p() - - def verify(self, result, *, obj, - itemsize, fmt, readonly, - ndim, shape, strides, - lst, sliced=False, cast=False): - # Verify buffer contents against expected values. - if shape: - expected_len = prod(shape)*itemsize - else: - if not fmt: # array has been implicitly cast to unsigned bytes - expected_len = len(lst) - else: # ndim = 0 - expected_len = itemsize - - # Reconstruct suboffsets from strides. Support for slicing - # could be added, but is currently only needed for test_getbuf(). - suboffsets = () - if result.suboffsets: - self.assertGreater(ndim, 0) - - suboffset0 = 0 - for n in range(1, ndim): - if shape[n] == 0: - break - if strides[n] <= 0: - suboffset0 += -strides[n] * (shape[n]-1) - - suboffsets = [suboffset0] + [-1 for v in range(ndim-1)] - - # Not correct if slicing has occurred in the first dimension. - stride0 = self.sizeof_void_p - if strides[0] < 0: - stride0 = -stride0 - strides = [stride0] + list(strides[1:]) - - self.assertIs(result.obj, obj) - self.assertEqual(result.nbytes, expected_len) - self.assertEqual(result.itemsize, itemsize) - self.assertEqual(result.format, fmt) - self.assertIs(result.readonly, readonly) - self.assertEqual(result.ndim, ndim) - self.assertEqual(result.shape, tuple(shape)) - if not (sliced and suboffsets): - self.assertEqual(result.strides, tuple(strides)) - self.assertEqual(result.suboffsets, tuple(suboffsets)) - - if isinstance(result, ndarray) or is_memoryview_format(fmt): - rep = result.tolist() if fmt else result.tobytes() - self.assertEqual(rep, lst) - - if not fmt: # array has been cast to unsigned bytes, - return # the remaining tests won't work. - - # PyBuffer_GetPointer() is the definition how to access an item. - # If PyBuffer_GetPointer(indices) is correct for all possible - # combinations of indices, the buffer is correct. - # - # Also test tobytes() against the flattened 'lst', with all items - # packed to bytes. - if not cast: # casts chop up 'lst' in different ways - b = bytearray() - buf_err = None - for ind in indices(shape): - try: - item1 = get_pointer(result, ind) - item2 = get_item(lst, ind) - if isinstance(item2, tuple): - x = struct.pack(fmt, *item2) - else: - x = struct.pack(fmt, item2) - b.extend(x) - except BufferError: - buf_err = True # re-exporter does not provide full buffer - break - self.assertEqual(item1, item2) - - if not buf_err: - # test tobytes() - self.assertEqual(result.tobytes(), b) - - # test hex() - m = memoryview(result) - h = "".join("%02x" % c for c in b) - self.assertEqual(m.hex(), h) - - # lst := expected multi-dimensional logical representation - # flatten(lst) := elements in C-order - ff = fmt if fmt else 'B' - flattened = flatten(lst) - - # Rules for 'A': if the array is already contiguous, return - # the array unaltered. Otherwise, return a contiguous 'C' - # representation. - for order in ['C', 'F', 'A']: - expected = result - if order == 'F': - if not is_contiguous(result, 'A') or \ - is_contiguous(result, 'C'): - # For constructing the ndarray, convert the - # flattened logical representation to Fortran order. - trans = transpose(flattened, shape) - expected = ndarray(trans, shape=shape, format=ff, - flags=ND_FORTRAN) - else: # 'C', 'A' - if not is_contiguous(result, 'A') or \ - is_contiguous(result, 'F') and order == 'C': - # The flattened list is already in C-order. - expected = ndarray(flattened, shape=shape, format=ff) - - contig = get_contiguous(result, PyBUF_READ, order) - self.assertEqual(contig.tobytes(), b) - self.assertTrue(cmp_contig(contig, expected)) - - if ndim == 0: - continue - - nmemb = len(flattened) - ro = 0 if readonly else ND_WRITABLE - - ### See comment in test_py_buffer_to_contiguous for an - ### explanation why these tests are valid. - - # To 'C' - contig = py_buffer_to_contiguous(result, 'C', PyBUF_FULL_RO) - self.assertEqual(len(contig), nmemb * itemsize) - initlst = [struct.unpack_from(fmt, contig, n*itemsize) - for n in range(nmemb)] - if len(initlst[0]) == 1: - initlst = [v[0] for v in initlst] - - y = ndarray(initlst, shape=shape, flags=ro, format=fmt) - self.assertEqual(memoryview(y), memoryview(result)) - - contig_bytes = memoryview(result).tobytes() - self.assertEqual(contig_bytes, contig) - - contig_bytes = memoryview(result).tobytes(order=None) - self.assertEqual(contig_bytes, contig) - - contig_bytes = memoryview(result).tobytes(order='C') - self.assertEqual(contig_bytes, contig) - - # To 'F' - contig = py_buffer_to_contiguous(result, 'F', PyBUF_FULL_RO) - self.assertEqual(len(contig), nmemb * itemsize) - initlst = [struct.unpack_from(fmt, contig, n*itemsize) - for n in range(nmemb)] - if len(initlst[0]) == 1: - initlst = [v[0] for v in initlst] - - y = ndarray(initlst, shape=shape, flags=ro|ND_FORTRAN, - format=fmt) - self.assertEqual(memoryview(y), memoryview(result)) - - contig_bytes = memoryview(result).tobytes(order='F') - self.assertEqual(contig_bytes, contig) - - # To 'A' - contig = py_buffer_to_contiguous(result, 'A', PyBUF_FULL_RO) - self.assertEqual(len(contig), nmemb * itemsize) - initlst = [struct.unpack_from(fmt, contig, n*itemsize) - for n in range(nmemb)] - if len(initlst[0]) == 1: - initlst = [v[0] for v in initlst] - - f = ND_FORTRAN if is_contiguous(result, 'F') else 0 - y = ndarray(initlst, shape=shape, flags=f|ro, format=fmt) - self.assertEqual(memoryview(y), memoryview(result)) - - contig_bytes = memoryview(result).tobytes(order='A') - self.assertEqual(contig_bytes, contig) - - if is_memoryview_format(fmt): - try: - m = memoryview(result) - except BufferError: # re-exporter does not provide full information - return - ex = result.obj if isinstance(result, memoryview) else result - - def check_memoryview(m, expected_readonly=readonly): - self.assertIs(m.obj, ex) - self.assertEqual(m.nbytes, expected_len) - self.assertEqual(m.itemsize, itemsize) - self.assertEqual(m.format, fmt) - self.assertEqual(m.readonly, expected_readonly) - self.assertEqual(m.ndim, ndim) - self.assertEqual(m.shape, tuple(shape)) - if not (sliced and suboffsets): - self.assertEqual(m.strides, tuple(strides)) - self.assertEqual(m.suboffsets, tuple(suboffsets)) - - n = 1 if ndim == 0 else len(lst) - self.assertEqual(len(m), n) - - rep = result.tolist() if fmt else result.tobytes() - self.assertEqual(rep, lst) - self.assertEqual(m, result) - - check_memoryview(m) - with m.toreadonly() as mm: - check_memoryview(mm, expected_readonly=True) - m.tobytes() # Releasing mm didn't release m - - def verify_getbuf(self, orig_ex, ex, req, sliced=False): - def simple_fmt(ex): - return ex.format == '' or ex.format == 'B' - def match(req, flag): - return ((req&flag) == flag) - - if (# writable request to read-only exporter - (ex.readonly and match(req, PyBUF_WRITABLE)) or - # cannot match explicit contiguity request - (match(req, PyBUF_C_CONTIGUOUS) and not ex.c_contiguous) or - (match(req, PyBUF_F_CONTIGUOUS) and not ex.f_contiguous) or - (match(req, PyBUF_ANY_CONTIGUOUS) and not ex.contiguous) or - # buffer needs suboffsets - (not match(req, PyBUF_INDIRECT) and ex.suboffsets) or - # buffer without strides must be C-contiguous - (not match(req, PyBUF_STRIDES) and not ex.c_contiguous) or - # PyBUF_SIMPLE|PyBUF_FORMAT and PyBUF_WRITABLE|PyBUF_FORMAT - (not match(req, PyBUF_ND) and match(req, PyBUF_FORMAT))): - - self.assertRaises(BufferError, ndarray, ex, getbuf=req) - return - - if isinstance(ex, ndarray) or is_memoryview_format(ex.format): - lst = ex.tolist() - else: - nd = ndarray(ex, getbuf=PyBUF_FULL_RO) - lst = nd.tolist() - - # The consumer may have requested default values or a NULL format. - ro = False if match(req, PyBUF_WRITABLE) else ex.readonly - fmt = ex.format - itemsize = ex.itemsize - ndim = ex.ndim - if not match(req, PyBUF_FORMAT): - # itemsize refers to the original itemsize before the cast. - # The equality product(shape) * itemsize = len still holds. - # The equality calcsize(format) = itemsize does _not_ hold. - fmt = '' - lst = orig_ex.tobytes() # Issue 12834 - if not match(req, PyBUF_ND): - ndim = 1 - shape = orig_ex.shape if match(req, PyBUF_ND) else () - strides = orig_ex.strides if match(req, PyBUF_STRIDES) else () - - nd = ndarray(ex, getbuf=req) - self.verify(nd, obj=ex, - itemsize=itemsize, fmt=fmt, readonly=ro, - ndim=ndim, shape=shape, strides=strides, - lst=lst, sliced=sliced) - - def test_ndarray_getbuf(self): - requests = ( - # distinct flags - PyBUF_INDIRECT, PyBUF_STRIDES, PyBUF_ND, PyBUF_SIMPLE, - PyBUF_C_CONTIGUOUS, PyBUF_F_CONTIGUOUS, PyBUF_ANY_CONTIGUOUS, - # compound requests - PyBUF_FULL, PyBUF_FULL_RO, - PyBUF_RECORDS, PyBUF_RECORDS_RO, - PyBUF_STRIDED, PyBUF_STRIDED_RO, - PyBUF_CONTIG, PyBUF_CONTIG_RO, - ) - # items and format - items_fmt = ( - ([True if x % 2 else False for x in range(12)], '?'), - ([1,2,3,4,5,6,7,8,9,10,11,12], 'b'), - ([1,2,3,4,5,6,7,8,9,10,11,12], 'B'), - ([(2**31-x) if x % 2 else (-2**31+x) for x in range(12)], 'l') - ) - # shape, strides, offset - structure = ( - ([], [], 0), - ([1,3,1], [], 0), - ([12], [], 0), - ([12], [-1], 11), - ([6], [2], 0), - ([6], [-2], 11), - ([3, 4], [], 0), - ([3, 4], [-4, -1], 11), - ([2, 2], [4, 1], 4), - ([2, 2], [-4, -1], 8) - ) - # ndarray creation flags - ndflags = ( - 0, ND_WRITABLE, ND_FORTRAN, ND_FORTRAN|ND_WRITABLE, - ND_PIL, ND_PIL|ND_WRITABLE - ) - # flags that can actually be used as flags - real_flags = (0, PyBUF_WRITABLE, PyBUF_FORMAT, - PyBUF_WRITABLE|PyBUF_FORMAT) - - for items, fmt in items_fmt: - itemsize = struct.calcsize(fmt) - for shape, strides, offset in structure: - strides = [v * itemsize for v in strides] - offset *= itemsize - for flags in ndflags: - - if strides and (flags&ND_FORTRAN): - continue - if not shape and (flags&ND_PIL): - continue - - _items = items if shape else items[0] - ex1 = ndarray(_items, format=fmt, flags=flags, - shape=shape, strides=strides, offset=offset) - ex2 = ex1[::-2] if shape else None - - m1 = memoryview(ex1) - if ex2: - m2 = memoryview(ex2) - if ex1.ndim == 0 or (ex1.ndim == 1 and shape and strides): - self.assertEqual(m1, ex1) - if ex2 and ex2.ndim == 1 and shape and strides: - self.assertEqual(m2, ex2) - - for req in requests: - for bits in real_flags: - self.verify_getbuf(ex1, ex1, req|bits) - self.verify_getbuf(ex1, m1, req|bits) - if ex2: - self.verify_getbuf(ex2, ex2, req|bits, - sliced=True) - self.verify_getbuf(ex2, m2, req|bits, - sliced=True) - - items = [1,2,3,4,5,6,7,8,9,10,11,12] - - # ND_GETBUF_FAIL - ex = ndarray(items, shape=[12], flags=ND_GETBUF_FAIL) - self.assertRaises(BufferError, ndarray, ex) - - # Request complex structure from a simple exporter. In this - # particular case the test object is not PEP-3118 compliant. - base = ndarray([9], [1]) - ex = ndarray(base, getbuf=PyBUF_SIMPLE) - self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_WRITABLE) - self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_ND) - self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_STRIDES) - self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_C_CONTIGUOUS) - self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_F_CONTIGUOUS) - self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_ANY_CONTIGUOUS) - nd = ndarray(ex, getbuf=PyBUF_SIMPLE) - - # Issue #22445: New precise contiguity definition. - for shape in [1,12,1], [7,0,7]: - for order in 0, ND_FORTRAN: - ex = ndarray(items, shape=shape, flags=order|ND_WRITABLE) - self.assertTrue(is_contiguous(ex, 'F')) - self.assertTrue(is_contiguous(ex, 'C')) - - for flags in requests: - nd = ndarray(ex, getbuf=flags) - self.assertTrue(is_contiguous(nd, 'F')) - self.assertTrue(is_contiguous(nd, 'C')) - - def test_ndarray_exceptions(self): - nd = ndarray([9], [1]) - ndm = ndarray([9], [1], flags=ND_VAREXPORT) - - # Initialization of a new ndarray or mutation of an existing array. - for c in (ndarray, nd.push, ndm.push): - # Invalid types. - self.assertRaises(TypeError, c, {1,2,3}) - self.assertRaises(TypeError, c, [1,2,'3']) - self.assertRaises(TypeError, c, [1,2,(3,4)]) - self.assertRaises(TypeError, c, [1,2,3], shape={3}) - self.assertRaises(TypeError, c, [1,2,3], shape=[3], strides={1}) - self.assertRaises(TypeError, c, [1,2,3], shape=[3], offset=[]) - self.assertRaises(TypeError, c, [1], shape=[1], format={}) - self.assertRaises(TypeError, c, [1], shape=[1], flags={}) - self.assertRaises(TypeError, c, [1], shape=[1], getbuf={}) - - # ND_FORTRAN flag is only valid without strides. - self.assertRaises(TypeError, c, [1], shape=[1], strides=[1], - flags=ND_FORTRAN) - - # ND_PIL flag is only valid with ndim > 0. - self.assertRaises(TypeError, c, [1], shape=[], flags=ND_PIL) - - # Invalid items. - self.assertRaises(ValueError, c, [], shape=[1]) - self.assertRaises(ValueError, c, ['XXX'], shape=[1], format="L") - # Invalid combination of items and format. - self.assertRaises(struct.error, c, [1000], shape=[1], format="B") - self.assertRaises(ValueError, c, [1,(2,3)], shape=[2], format="B") - self.assertRaises(ValueError, c, [1,2,3], shape=[3], format="QL") - - # Invalid ndim. - n = ND_MAX_NDIM+1 - self.assertRaises(ValueError, c, [1]*n, shape=[1]*n) - - # Invalid shape. - self.assertRaises(ValueError, c, [1], shape=[-1]) - self.assertRaises(ValueError, c, [1,2,3], shape=['3']) - self.assertRaises(OverflowError, c, [1], shape=[2**128]) - # prod(shape) * itemsize != len(items) - self.assertRaises(ValueError, c, [1,2,3,4,5], shape=[2,2], offset=3) - - # Invalid strides. - self.assertRaises(ValueError, c, [1,2,3], shape=[3], strides=['1']) - self.assertRaises(OverflowError, c, [1], shape=[1], - strides=[2**128]) - - # Invalid combination of strides and shape. - self.assertRaises(ValueError, c, [1,2], shape=[2,1], strides=[1]) - # Invalid combination of strides and format. - self.assertRaises(ValueError, c, [1,2,3,4], shape=[2], strides=[3], - format="L") - - # Invalid offset. - self.assertRaises(ValueError, c, [1,2,3], shape=[3], offset=4) - self.assertRaises(ValueError, c, [1,2,3], shape=[1], offset=3, - format="L") - - # Invalid format. - self.assertRaises(ValueError, c, [1,2,3], shape=[3], format="") - self.assertRaises(struct.error, c, [(1,2,3)], shape=[1], - format="@#$") - - # Striding out of the memory bounds. - items = [1,2,3,4,5,6,7,8,9,10] - self.assertRaises(ValueError, c, items, shape=[2,3], - strides=[-3, -2], offset=5) - - # Constructing consumer: format argument invalid. - self.assertRaises(TypeError, c, bytearray(), format="Q") - - # Constructing original base object: getbuf argument invalid. - self.assertRaises(TypeError, c, [1], shape=[1], getbuf=PyBUF_FULL) - - # Shape argument is mandatory for original base objects. - self.assertRaises(TypeError, c, [1]) - - - # PyBUF_WRITABLE request to read-only provider. - self.assertRaises(BufferError, ndarray, b'123', getbuf=PyBUF_WRITABLE) - - # ND_VAREXPORT can only be specified during construction. - nd = ndarray([9], [1], flags=ND_VAREXPORT) - self.assertRaises(ValueError, nd.push, [1], [1], flags=ND_VAREXPORT) - - # Invalid operation for consumers: push/pop - nd = ndarray(b'123') - self.assertRaises(BufferError, nd.push, [1], [1]) - self.assertRaises(BufferError, nd.pop) - - # ND_VAREXPORT not set: push/pop fail with exported buffers - nd = ndarray([9], [1]) - nd.push([1], [1]) - m = memoryview(nd) - self.assertRaises(BufferError, nd.push, [1], [1]) - self.assertRaises(BufferError, nd.pop) - m.release() - nd.pop() - - # Single remaining buffer: pop fails - self.assertRaises(BufferError, nd.pop) - del nd - - # get_pointer() - self.assertRaises(TypeError, get_pointer, {}, [1,2,3]) - self.assertRaises(TypeError, get_pointer, b'123', {}) - - nd = ndarray(list(range(100)), shape=[1]*100) - self.assertRaises(ValueError, get_pointer, nd, [5]) - - nd = ndarray(list(range(12)), shape=[3,4]) - self.assertRaises(ValueError, get_pointer, nd, [2,3,4]) - self.assertRaises(ValueError, get_pointer, nd, [3,3]) - self.assertRaises(ValueError, get_pointer, nd, [-3,3]) - self.assertRaises(OverflowError, get_pointer, nd, [1<<64,3]) - - # tolist() needs format - ex = ndarray([1,2,3], shape=[3], format='L') - nd = ndarray(ex, getbuf=PyBUF_SIMPLE) - self.assertRaises(ValueError, nd.tolist) - - # memoryview_from_buffer() - ex1 = ndarray([1,2,3], shape=[3], format='L') - ex2 = ndarray(ex1) - nd = ndarray(ex2) - self.assertRaises(TypeError, nd.memoryview_from_buffer) - - nd = ndarray([(1,)*200], shape=[1], format='L'*200) - self.assertRaises(TypeError, nd.memoryview_from_buffer) - - n = ND_MAX_NDIM - nd = ndarray(list(range(n)), shape=[1]*n) - self.assertRaises(ValueError, nd.memoryview_from_buffer) - - # get_contiguous() - nd = ndarray([1], shape=[1]) - self.assertRaises(TypeError, get_contiguous, 1, 2, 3, 4, 5) - self.assertRaises(TypeError, get_contiguous, nd, "xyz", 'C') - self.assertRaises(OverflowError, get_contiguous, nd, 2**64, 'C') - self.assertRaises(TypeError, get_contiguous, nd, PyBUF_READ, 961) - self.assertRaises(UnicodeEncodeError, get_contiguous, nd, PyBUF_READ, - '\u2007') - self.assertRaises(ValueError, get_contiguous, nd, PyBUF_READ, 'Z') - self.assertRaises(ValueError, get_contiguous, nd, 255, 'A') - - # cmp_contig() - nd = ndarray([1], shape=[1]) - self.assertRaises(TypeError, cmp_contig, 1, 2, 3, 4, 5) - self.assertRaises(TypeError, cmp_contig, {}, nd) - self.assertRaises(TypeError, cmp_contig, nd, {}) - - # is_contiguous() - nd = ndarray([1], shape=[1]) - self.assertRaises(TypeError, is_contiguous, 1, 2, 3, 4, 5) - self.assertRaises(TypeError, is_contiguous, {}, 'A') - self.assertRaises(TypeError, is_contiguous, nd, 201) - - def test_ndarray_linked_list(self): - for perm in permutations(range(5)): - m = [0]*5 - nd = ndarray([1,2,3], shape=[3], flags=ND_VAREXPORT) - m[0] = memoryview(nd) - - for i in range(1, 5): - nd.push([1,2,3], shape=[3]) - m[i] = memoryview(nd) - - for i in range(5): - m[perm[i]].release() - - self.assertRaises(BufferError, nd.pop) - del nd - - def test_ndarray_format_scalar(self): - # ndim = 0: scalar - for fmt, scalar, _ in iter_format(0): - itemsize = struct.calcsize(fmt) - nd = ndarray(scalar, shape=(), format=fmt) - self.verify(nd, obj=None, - itemsize=itemsize, fmt=fmt, readonly=True, - ndim=0, shape=(), strides=(), - lst=scalar) - - def test_ndarray_format_shape(self): - # ndim = 1, shape = [n] - nitems = randrange(1, 10) - for fmt, items, _ in iter_format(nitems): - itemsize = struct.calcsize(fmt) - for flags in (0, ND_PIL): - nd = ndarray(items, shape=[nitems], format=fmt, flags=flags) - self.verify(nd, obj=None, - itemsize=itemsize, fmt=fmt, readonly=True, - ndim=1, shape=(nitems,), strides=(itemsize,), - lst=items) - - def test_ndarray_format_strides(self): - # ndim = 1, strides - nitems = randrange(1, 30) - for fmt, items, _ in iter_format(nitems): - itemsize = struct.calcsize(fmt) - for step in range(-5, 5): - if step == 0: - continue - - shape = [len(items[::step])] - strides = [step*itemsize] - offset = itemsize*(nitems-1) if step < 0 else 0 - - for flags in (0, ND_PIL): - nd = ndarray(items, shape=shape, strides=strides, - format=fmt, offset=offset, flags=flags) - self.verify(nd, obj=None, - itemsize=itemsize, fmt=fmt, readonly=True, - ndim=1, shape=shape, strides=strides, - lst=items[::step]) - - def test_ndarray_fortran(self): - items = [1,2,3,4,5,6,7,8,9,10,11,12] - ex = ndarray(items, shape=(3, 4), strides=(1, 3)) - nd = ndarray(ex, getbuf=PyBUF_F_CONTIGUOUS|PyBUF_FORMAT) - self.assertEqual(nd.tolist(), farray(items, (3, 4))) - - def test_ndarray_multidim(self): - for ndim in range(5): - shape_t = [randrange(2, 10) for _ in range(ndim)] - nitems = prod(shape_t) - for shape in permutations(shape_t): - - fmt, items, _ = randitems(nitems) - itemsize = struct.calcsize(fmt) - - for flags in (0, ND_PIL): - if ndim == 0 and flags == ND_PIL: - continue - - # C array - nd = ndarray(items, shape=shape, format=fmt, flags=flags) - - strides = strides_from_shape(ndim, shape, itemsize, 'C') - lst = carray(items, shape) - self.verify(nd, obj=None, - itemsize=itemsize, fmt=fmt, readonly=True, - ndim=ndim, shape=shape, strides=strides, - lst=lst) - - if is_memoryview_format(fmt): - # memoryview: reconstruct strides - ex = ndarray(items, shape=shape, format=fmt) - nd = ndarray(ex, getbuf=PyBUF_CONTIG_RO|PyBUF_FORMAT) - self.assertTrue(nd.strides == ()) - mv = nd.memoryview_from_buffer() - self.verify(mv, obj=None, - itemsize=itemsize, fmt=fmt, readonly=True, - ndim=ndim, shape=shape, strides=strides, - lst=lst) - - # Fortran array - nd = ndarray(items, shape=shape, format=fmt, - flags=flags|ND_FORTRAN) - - strides = strides_from_shape(ndim, shape, itemsize, 'F') - lst = farray(items, shape) - self.verify(nd, obj=None, - itemsize=itemsize, fmt=fmt, readonly=True, - ndim=ndim, shape=shape, strides=strides, - lst=lst) - - def test_ndarray_index_invalid(self): - # not writable - nd = ndarray([1], shape=[1]) - self.assertRaises(TypeError, nd.__setitem__, 1, 8) - mv = memoryview(nd) - self.assertEqual(mv, nd) - self.assertRaises(TypeError, mv.__setitem__, 1, 8) - - # cannot be deleted - nd = ndarray([1], shape=[1], flags=ND_WRITABLE) - self.assertRaises(TypeError, nd.__delitem__, 1) - mv = memoryview(nd) - self.assertEqual(mv, nd) - self.assertRaises(TypeError, mv.__delitem__, 1) - - # overflow - nd = ndarray([1], shape=[1], flags=ND_WRITABLE) - self.assertRaises(OverflowError, nd.__getitem__, 1<<64) - self.assertRaises(OverflowError, nd.__setitem__, 1<<64, 8) - mv = memoryview(nd) - self.assertEqual(mv, nd) - self.assertRaises(IndexError, mv.__getitem__, 1<<64) - self.assertRaises(IndexError, mv.__setitem__, 1<<64, 8) - - # format - items = [1,2,3,4,5,6,7,8] - nd = ndarray(items, shape=[len(items)], format="B", flags=ND_WRITABLE) - self.assertRaises(struct.error, nd.__setitem__, 2, 300) - self.assertRaises(ValueError, nd.__setitem__, 1, (100, 200)) - mv = memoryview(nd) - self.assertEqual(mv, nd) - self.assertRaises(ValueError, mv.__setitem__, 2, 300) - self.assertRaises(TypeError, mv.__setitem__, 1, (100, 200)) - - items = [(1,2), (3,4), (5,6)] - nd = ndarray(items, shape=[len(items)], format="LQ", flags=ND_WRITABLE) - self.assertRaises(ValueError, nd.__setitem__, 2, 300) - self.assertRaises(struct.error, nd.__setitem__, 1, (b'\x001', 200)) - - def test_ndarray_index_scalar(self): - # scalar - nd = ndarray(1, shape=(), flags=ND_WRITABLE) - mv = memoryview(nd) - self.assertEqual(mv, nd) - - x = nd[()]; self.assertEqual(x, 1) - x = nd[...]; self.assertEqual(x.tolist(), nd.tolist()) - - x = mv[()]; self.assertEqual(x, 1) - x = mv[...]; self.assertEqual(x.tolist(), nd.tolist()) - - self.assertRaises(TypeError, nd.__getitem__, 0) - self.assertRaises(TypeError, mv.__getitem__, 0) - self.assertRaises(TypeError, nd.__setitem__, 0, 8) - self.assertRaises(TypeError, mv.__setitem__, 0, 8) - - self.assertEqual(nd.tolist(), 1) - self.assertEqual(mv.tolist(), 1) - - nd[()] = 9; self.assertEqual(nd.tolist(), 9) - mv[()] = 9; self.assertEqual(mv.tolist(), 9) - - nd[...] = 5; self.assertEqual(nd.tolist(), 5) - mv[...] = 5; self.assertEqual(mv.tolist(), 5) - - def test_ndarray_index_null_strides(self): - ex = ndarray(list(range(2*4)), shape=[2, 4], flags=ND_WRITABLE) - nd = ndarray(ex, getbuf=PyBUF_CONTIG) - - # Sub-views are only possible for full exporters. - self.assertRaises(BufferError, nd.__getitem__, 1) - # Same for slices. - self.assertRaises(BufferError, nd.__getitem__, slice(3,5,1)) - - def test_ndarray_index_getitem_single(self): - # getitem - for fmt, items, _ in iter_format(5): - nd = ndarray(items, shape=[5], format=fmt) - for i in range(-5, 5): - self.assertEqual(nd[i], items[i]) - - self.assertRaises(IndexError, nd.__getitem__, -6) - self.assertRaises(IndexError, nd.__getitem__, 5) - - if is_memoryview_format(fmt): - mv = memoryview(nd) - self.assertEqual(mv, nd) - for i in range(-5, 5): - self.assertEqual(mv[i], items[i]) - - self.assertRaises(IndexError, mv.__getitem__, -6) - self.assertRaises(IndexError, mv.__getitem__, 5) - - # getitem with null strides - for fmt, items, _ in iter_format(5): - ex = ndarray(items, shape=[5], flags=ND_WRITABLE, format=fmt) - nd = ndarray(ex, getbuf=PyBUF_CONTIG|PyBUF_FORMAT) - - for i in range(-5, 5): - self.assertEqual(nd[i], items[i]) - - if is_memoryview_format(fmt): - mv = nd.memoryview_from_buffer() - self.assertIs(mv.__eq__(nd), NotImplemented) - for i in range(-5, 5): - self.assertEqual(mv[i], items[i]) - - # getitem with null format - items = [1,2,3,4,5] - ex = ndarray(items, shape=[5]) - nd = ndarray(ex, getbuf=PyBUF_CONTIG_RO) - for i in range(-5, 5): - self.assertEqual(nd[i], items[i]) - - # getitem with null shape/strides/format - items = [1,2,3,4,5] - ex = ndarray(items, shape=[5]) - nd = ndarray(ex, getbuf=PyBUF_SIMPLE) - - for i in range(-5, 5): - self.assertEqual(nd[i], items[i]) - - def test_ndarray_index_setitem_single(self): - # assign single value - for fmt, items, single_item in iter_format(5): - nd = ndarray(items, shape=[5], format=fmt, flags=ND_WRITABLE) - for i in range(5): - items[i] = single_item - nd[i] = single_item - self.assertEqual(nd.tolist(), items) - - self.assertRaises(IndexError, nd.__setitem__, -6, single_item) - self.assertRaises(IndexError, nd.__setitem__, 5, single_item) - - if not is_memoryview_format(fmt): - continue - - nd = ndarray(items, shape=[5], format=fmt, flags=ND_WRITABLE) - mv = memoryview(nd) - self.assertEqual(mv, nd) - for i in range(5): - items[i] = single_item - mv[i] = single_item - self.assertEqual(mv.tolist(), items) - - self.assertRaises(IndexError, mv.__setitem__, -6, single_item) - self.assertRaises(IndexError, mv.__setitem__, 5, single_item) - - - # assign single value: lobject = robject - for fmt, items, single_item in iter_format(5): - nd = ndarray(items, shape=[5], format=fmt, flags=ND_WRITABLE) - for i in range(-5, 4): - items[i] = items[i+1] - nd[i] = nd[i+1] - self.assertEqual(nd.tolist(), items) - - if not is_memoryview_format(fmt): - continue - - nd = ndarray(items, shape=[5], format=fmt, flags=ND_WRITABLE) - mv = memoryview(nd) - self.assertEqual(mv, nd) - for i in range(-5, 4): - items[i] = items[i+1] - mv[i] = mv[i+1] - self.assertEqual(mv.tolist(), items) - - def test_ndarray_index_getitem_multidim(self): - shape_t = (2, 3, 5) - nitems = prod(shape_t) - for shape in permutations(shape_t): - - fmt, items, _ = randitems(nitems) - - for flags in (0, ND_PIL): - # C array - nd = ndarray(items, shape=shape, format=fmt, flags=flags) - lst = carray(items, shape) - - for i in range(-shape[0], shape[0]): - self.assertEqual(lst[i], nd[i].tolist()) - for j in range(-shape[1], shape[1]): - self.assertEqual(lst[i][j], nd[i][j].tolist()) - for k in range(-shape[2], shape[2]): - self.assertEqual(lst[i][j][k], nd[i][j][k]) - - # Fortran array - nd = ndarray(items, shape=shape, format=fmt, - flags=flags|ND_FORTRAN) - lst = farray(items, shape) - - for i in range(-shape[0], shape[0]): - self.assertEqual(lst[i], nd[i].tolist()) - for j in range(-shape[1], shape[1]): - self.assertEqual(lst[i][j], nd[i][j].tolist()) - for k in range(shape[2], shape[2]): - self.assertEqual(lst[i][j][k], nd[i][j][k]) - - def test_ndarray_sequence(self): - nd = ndarray(1, shape=()) - self.assertRaises(TypeError, eval, "1 in nd", locals()) - mv = memoryview(nd) - self.assertEqual(mv, nd) - self.assertRaises(TypeError, eval, "1 in mv", locals()) - - for fmt, items, _ in iter_format(5): - nd = ndarray(items, shape=[5], format=fmt) - for i, v in enumerate(nd): - self.assertEqual(v, items[i]) - self.assertTrue(v in nd) - - if is_memoryview_format(fmt): - mv = memoryview(nd) - for i, v in enumerate(mv): - self.assertEqual(v, items[i]) - self.assertTrue(v in mv) - - def test_ndarray_slice_invalid(self): - items = [1,2,3,4,5,6,7,8] - - # rvalue is not an exporter - xl = ndarray(items, shape=[8], flags=ND_WRITABLE) - ml = memoryview(xl) - self.assertRaises(TypeError, xl.__setitem__, slice(0,8,1), items) - self.assertRaises(TypeError, ml.__setitem__, slice(0,8,1), items) - - # rvalue is not a full exporter - xl = ndarray(items, shape=[8], flags=ND_WRITABLE) - ex = ndarray(items, shape=[8], flags=ND_WRITABLE) - xr = ndarray(ex, getbuf=PyBUF_ND) - self.assertRaises(BufferError, xl.__setitem__, slice(0,8,1), xr) - - # zero step - nd = ndarray(items, shape=[8], format="L", flags=ND_WRITABLE) - mv = memoryview(nd) - self.assertRaises(ValueError, nd.__getitem__, slice(0,1,0)) - self.assertRaises(ValueError, mv.__getitem__, slice(0,1,0)) - - nd = ndarray(items, shape=[2,4], format="L", flags=ND_WRITABLE) - mv = memoryview(nd) - - self.assertRaises(ValueError, nd.__getitem__, - (slice(0,1,1), slice(0,1,0))) - self.assertRaises(ValueError, nd.__getitem__, - (slice(0,1,0), slice(0,1,1))) - self.assertRaises(TypeError, nd.__getitem__, "@%$") - self.assertRaises(TypeError, nd.__getitem__, ("@%$", slice(0,1,1))) - self.assertRaises(TypeError, nd.__getitem__, (slice(0,1,1), {})) - - # memoryview: not implemented - self.assertRaises(NotImplementedError, mv.__getitem__, - (slice(0,1,1), slice(0,1,0))) - self.assertRaises(TypeError, mv.__getitem__, "@%$") - - # differing format - xl = ndarray(items, shape=[8], format="B", flags=ND_WRITABLE) - xr = ndarray(items, shape=[8], format="b") - ml = memoryview(xl) - mr = memoryview(xr) - self.assertRaises(ValueError, xl.__setitem__, slice(0,1,1), xr[7:8]) - self.assertEqual(xl.tolist(), items) - self.assertRaises(ValueError, ml.__setitem__, slice(0,1,1), mr[7:8]) - self.assertEqual(ml.tolist(), items) - - # differing itemsize - xl = ndarray(items, shape=[8], format="B", flags=ND_WRITABLE) - yr = ndarray(items, shape=[8], format="L") - ml = memoryview(xl) - mr = memoryview(xr) - self.assertRaises(ValueError, xl.__setitem__, slice(0,1,1), xr[7:8]) - self.assertEqual(xl.tolist(), items) - self.assertRaises(ValueError, ml.__setitem__, slice(0,1,1), mr[7:8]) - self.assertEqual(ml.tolist(), items) - - # differing ndim - xl = ndarray(items, shape=[2, 4], format="b", flags=ND_WRITABLE) - xr = ndarray(items, shape=[8], format="b") - ml = memoryview(xl) - mr = memoryview(xr) - self.assertRaises(ValueError, xl.__setitem__, slice(0,1,1), xr[7:8]) - self.assertEqual(xl.tolist(), [[1,2,3,4], [5,6,7,8]]) - self.assertRaises(NotImplementedError, ml.__setitem__, slice(0,1,1), - mr[7:8]) - - # differing shape - xl = ndarray(items, shape=[8], format="b", flags=ND_WRITABLE) - xr = ndarray(items, shape=[8], format="b") - ml = memoryview(xl) - mr = memoryview(xr) - self.assertRaises(ValueError, xl.__setitem__, slice(0,2,1), xr[7:8]) - self.assertEqual(xl.tolist(), items) - self.assertRaises(ValueError, ml.__setitem__, slice(0,2,1), mr[7:8]) - self.assertEqual(ml.tolist(), items) - - # _testbuffer.c module functions - self.assertRaises(TypeError, slice_indices, slice(0,1,2), {}) - self.assertRaises(TypeError, slice_indices, "###########", 1) - self.assertRaises(ValueError, slice_indices, slice(0,1,0), 4) - - x = ndarray(items, shape=[8], format="b", flags=ND_PIL) - self.assertRaises(TypeError, x.add_suboffsets) - - ex = ndarray(items, shape=[8], format="B") - x = ndarray(ex, getbuf=PyBUF_SIMPLE) - self.assertRaises(TypeError, x.add_suboffsets) - - def test_ndarray_slice_zero_shape(self): - items = [1,2,3,4,5,6,7,8,9,10,11,12] - - x = ndarray(items, shape=[12], format="L", flags=ND_WRITABLE) - y = ndarray(items, shape=[12], format="L") - x[4:4] = y[9:9] - self.assertEqual(x.tolist(), items) - - ml = memoryview(x) - mr = memoryview(y) - self.assertEqual(ml, x) - self.assertEqual(ml, y) - ml[4:4] = mr[9:9] - self.assertEqual(ml.tolist(), items) - - x = ndarray(items, shape=[3, 4], format="L", flags=ND_WRITABLE) - y = ndarray(items, shape=[4, 3], format="L") - x[1:2, 2:2] = y[1:2, 3:3] - self.assertEqual(x.tolist(), carray(items, [3, 4])) - - def test_ndarray_slice_multidim(self): - shape_t = (2, 3, 5) - ndim = len(shape_t) - nitems = prod(shape_t) - for shape in permutations(shape_t): - - fmt, items, _ = randitems(nitems) - itemsize = struct.calcsize(fmt) - - for flags in (0, ND_PIL): - nd = ndarray(items, shape=shape, format=fmt, flags=flags) - lst = carray(items, shape) - - for slices in rslices_ndim(ndim, shape): - - listerr = None - try: - sliced = multislice(lst, slices) - except Exception as e: - listerr = e.__class__ - - nderr = None - try: - ndsliced = nd[slices] - except Exception as e: - nderr = e.__class__ - - if nderr or listerr: - self.assertIs(nderr, listerr) - else: - self.assertEqual(ndsliced.tolist(), sliced) - - def test_ndarray_slice_redundant_suboffsets(self): - shape_t = (2, 3, 5, 2) - ndim = len(shape_t) - nitems = prod(shape_t) - for shape in permutations(shape_t): - - fmt, items, _ = randitems(nitems) - itemsize = struct.calcsize(fmt) - - nd = ndarray(items, shape=shape, format=fmt) - nd.add_suboffsets() - ex = ndarray(items, shape=shape, format=fmt) - ex.add_suboffsets() - mv = memoryview(ex) - lst = carray(items, shape) - - for slices in rslices_ndim(ndim, shape): - - listerr = None - try: - sliced = multislice(lst, slices) - except Exception as e: - listerr = e.__class__ - - nderr = None - try: - ndsliced = nd[slices] - except Exception as e: - nderr = e.__class__ - - if nderr or listerr: - self.assertIs(nderr, listerr) - else: - self.assertEqual(ndsliced.tolist(), sliced) - - def test_ndarray_slice_assign_single(self): - for fmt, items, _ in iter_format(5): - for lslice in genslices(5): - for rslice in genslices(5): - for flags in (0, ND_PIL): - - f = flags|ND_WRITABLE - nd = ndarray(items, shape=[5], format=fmt, flags=f) - ex = ndarray(items, shape=[5], format=fmt, flags=f) - mv = memoryview(ex) - - lsterr = None - diff_structure = None - lst = items[:] - try: - lval = lst[lslice] - rval = lst[rslice] - lst[lslice] = lst[rslice] - diff_structure = len(lval) != len(rval) - except Exception as e: - lsterr = e.__class__ - - nderr = None - try: - nd[lslice] = nd[rslice] - except Exception as e: - nderr = e.__class__ - - if diff_structure: # ndarray cannot change shape - self.assertIs(nderr, ValueError) - else: - self.assertEqual(nd.tolist(), lst) - self.assertIs(nderr, lsterr) - - if not is_memoryview_format(fmt): - continue - - mverr = None - try: - mv[lslice] = mv[rslice] - except Exception as e: - mverr = e.__class__ - - if diff_structure: # memoryview cannot change shape - self.assertIs(mverr, ValueError) - else: - self.assertEqual(mv.tolist(), lst) - self.assertEqual(mv, nd) - self.assertIs(mverr, lsterr) - self.verify(mv, obj=ex, - itemsize=nd.itemsize, fmt=fmt, readonly=False, - ndim=nd.ndim, shape=nd.shape, strides=nd.strides, - lst=nd.tolist()) - - def test_ndarray_slice_assign_multidim(self): - shape_t = (2, 3, 5) - ndim = len(shape_t) - nitems = prod(shape_t) - for shape in permutations(shape_t): - - fmt, items, _ = randitems(nitems) - - for flags in (0, ND_PIL): - for _ in range(ITERATIONS): - lslices, rslices = randslice_from_shape(ndim, shape) - - nd = ndarray(items, shape=shape, format=fmt, - flags=flags|ND_WRITABLE) - lst = carray(items, shape) - - listerr = None - try: - result = multislice_assign(lst, lst, lslices, rslices) - except Exception as e: - listerr = e.__class__ - - nderr = None - try: - nd[lslices] = nd[rslices] - except Exception as e: - nderr = e.__class__ - - if nderr or listerr: - self.assertIs(nderr, listerr) - else: - self.assertEqual(nd.tolist(), result) - - def test_ndarray_random(self): - # construction of valid arrays - for _ in range(ITERATIONS): - for fmt in fmtdict['@']: - itemsize = struct.calcsize(fmt) - - t = rand_structure(itemsize, True, maxdim=MAXDIM, - maxshape=MAXSHAPE) - self.assertTrue(verify_structure(*t)) - items = randitems_from_structure(fmt, t) - - x = ndarray_from_structure(items, fmt, t) - xlist = x.tolist() - - mv = memoryview(x) - if is_memoryview_format(fmt): - mvlist = mv.tolist() - self.assertEqual(mvlist, xlist) - - if t[2] > 0: - # ndim > 0: test against suboffsets representation. - y = ndarray_from_structure(items, fmt, t, flags=ND_PIL) - ylist = y.tolist() - self.assertEqual(xlist, ylist) - - mv = memoryview(y) - if is_memoryview_format(fmt): - self.assertEqual(mv, y) - mvlist = mv.tolist() - self.assertEqual(mvlist, ylist) - - if numpy_array: - shape = t[3] - if 0 in shape: - continue # http://projects.scipy.org/numpy/ticket/1910 - z = numpy_array_from_structure(items, fmt, t) - self.verify(x, obj=None, - itemsize=z.itemsize, fmt=fmt, readonly=False, - ndim=z.ndim, shape=z.shape, strides=z.strides, - lst=z.tolist()) - - def test_ndarray_random_invalid(self): - # exceptions during construction of invalid arrays - for _ in range(ITERATIONS): - for fmt in fmtdict['@']: - itemsize = struct.calcsize(fmt) - - t = rand_structure(itemsize, False, maxdim=MAXDIM, - maxshape=MAXSHAPE) - self.assertFalse(verify_structure(*t)) - items = randitems_from_structure(fmt, t) - - nderr = False - try: - x = ndarray_from_structure(items, fmt, t) - except Exception as e: - nderr = e.__class__ - self.assertTrue(nderr) - - if numpy_array: - numpy_err = False - try: - y = numpy_array_from_structure(items, fmt, t) - except Exception as e: - numpy_err = e.__class__ - - if 0: # http://projects.scipy.org/numpy/ticket/1910 - self.assertTrue(numpy_err) - - def test_ndarray_random_slice_assign(self): - # valid slice assignments - for _ in range(ITERATIONS): - for fmt in fmtdict['@']: - itemsize = struct.calcsize(fmt) - - lshape, rshape, lslices, rslices = \ - rand_aligned_slices(maxdim=MAXDIM, maxshape=MAXSHAPE) - tl = rand_structure(itemsize, True, shape=lshape) - tr = rand_structure(itemsize, True, shape=rshape) - self.assertTrue(verify_structure(*tl)) - self.assertTrue(verify_structure(*tr)) - litems = randitems_from_structure(fmt, tl) - ritems = randitems_from_structure(fmt, tr) - - xl = ndarray_from_structure(litems, fmt, tl) - xr = ndarray_from_structure(ritems, fmt, tr) - xl[lslices] = xr[rslices] - xllist = xl.tolist() - xrlist = xr.tolist() - - ml = memoryview(xl) - mr = memoryview(xr) - self.assertEqual(ml.tolist(), xllist) - self.assertEqual(mr.tolist(), xrlist) - - if tl[2] > 0 and tr[2] > 0: - # ndim > 0: test against suboffsets representation. - yl = ndarray_from_structure(litems, fmt, tl, flags=ND_PIL) - yr = ndarray_from_structure(ritems, fmt, tr, flags=ND_PIL) - yl[lslices] = yr[rslices] - yllist = yl.tolist() - yrlist = yr.tolist() - self.assertEqual(xllist, yllist) - self.assertEqual(xrlist, yrlist) - - ml = memoryview(yl) - mr = memoryview(yr) - self.assertEqual(ml.tolist(), yllist) - self.assertEqual(mr.tolist(), yrlist) - - if numpy_array: - if 0 in lshape or 0 in rshape: - continue # http://projects.scipy.org/numpy/ticket/1910 - - zl = numpy_array_from_structure(litems, fmt, tl) - zr = numpy_array_from_structure(ritems, fmt, tr) - zl[lslices] = zr[rslices] - - if not is_overlapping(tl) and not is_overlapping(tr): - # Slice assignment of overlapping structures - # is undefined in NumPy. - self.verify(xl, obj=None, - itemsize=zl.itemsize, fmt=fmt, readonly=False, - ndim=zl.ndim, shape=zl.shape, - strides=zl.strides, lst=zl.tolist()) - - self.verify(xr, obj=None, - itemsize=zr.itemsize, fmt=fmt, readonly=False, - ndim=zr.ndim, shape=zr.shape, - strides=zr.strides, lst=zr.tolist()) - - def test_ndarray_re_export(self): - items = [1,2,3,4,5,6,7,8,9,10,11,12] - - nd = ndarray(items, shape=[3,4], flags=ND_PIL) - ex = ndarray(nd) - - self.assertTrue(ex.flags & ND_PIL) - self.assertIs(ex.obj, nd) - self.assertEqual(ex.suboffsets, (0, -1)) - self.assertFalse(ex.c_contiguous) - self.assertFalse(ex.f_contiguous) - self.assertFalse(ex.contiguous) - - def test_ndarray_zero_shape(self): - # zeros in shape - for flags in (0, ND_PIL): - nd = ndarray([1,2,3], shape=[0], flags=flags) - mv = memoryview(nd) - self.assertEqual(mv, nd) - self.assertEqual(nd.tolist(), []) - self.assertEqual(mv.tolist(), []) - - nd = ndarray([1,2,3], shape=[0,3,3], flags=flags) - self.assertEqual(nd.tolist(), []) - - nd = ndarray([1,2,3], shape=[3,0,3], flags=flags) - self.assertEqual(nd.tolist(), [[], [], []]) - - nd = ndarray([1,2,3], shape=[3,3,0], flags=flags) - self.assertEqual(nd.tolist(), - [[[], [], []], [[], [], []], [[], [], []]]) - - def test_ndarray_zero_strides(self): - # zero strides - for flags in (0, ND_PIL): - nd = ndarray([1], shape=[5], strides=[0], flags=flags) - mv = memoryview(nd) - self.assertEqual(mv, nd) - self.assertEqual(nd.tolist(), [1, 1, 1, 1, 1]) - self.assertEqual(mv.tolist(), [1, 1, 1, 1, 1]) - - def test_ndarray_offset(self): - nd = ndarray(list(range(20)), shape=[3], offset=7) - self.assertEqual(nd.offset, 7) - self.assertEqual(nd.tolist(), [7,8,9]) - - def test_ndarray_memoryview_from_buffer(self): - for flags in (0, ND_PIL): - nd = ndarray(list(range(3)), shape=[3], flags=flags) - m = nd.memoryview_from_buffer() - self.assertEqual(m, nd) - - def test_ndarray_get_pointer(self): - for flags in (0, ND_PIL): - nd = ndarray(list(range(3)), shape=[3], flags=flags) - for i in range(3): - self.assertEqual(nd[i], get_pointer(nd, [i])) - - def test_ndarray_tolist_null_strides(self): - ex = ndarray(list(range(20)), shape=[2,2,5]) - - nd = ndarray(ex, getbuf=PyBUF_ND|PyBUF_FORMAT) - self.assertEqual(nd.tolist(), ex.tolist()) - - m = memoryview(ex) - self.assertEqual(m.tolist(), ex.tolist()) - - def test_ndarray_cmp_contig(self): - - self.assertFalse(cmp_contig(b"123", b"456")) - - x = ndarray(list(range(12)), shape=[3,4]) - y = ndarray(list(range(12)), shape=[4,3]) - self.assertFalse(cmp_contig(x, y)) - - x = ndarray([1], shape=[1], format="B") - self.assertTrue(cmp_contig(x, b'\x01')) - self.assertTrue(cmp_contig(b'\x01', x)) - - def test_ndarray_hash(self): - - a = array.array('L', [1,2,3]) - nd = ndarray(a) - self.assertRaises(ValueError, hash, nd) - - # one-dimensional - b = bytes(list(range(12))) - - nd = ndarray(list(range(12)), shape=[12]) - self.assertEqual(hash(nd), hash(b)) - - # C-contiguous - nd = ndarray(list(range(12)), shape=[3,4]) - self.assertEqual(hash(nd), hash(b)) - - nd = ndarray(list(range(12)), shape=[3,2,2]) - self.assertEqual(hash(nd), hash(b)) - - # Fortran contiguous - b = bytes(transpose(list(range(12)), shape=[4,3])) - nd = ndarray(list(range(12)), shape=[3,4], flags=ND_FORTRAN) - self.assertEqual(hash(nd), hash(b)) - - b = bytes(transpose(list(range(12)), shape=[2,3,2])) - nd = ndarray(list(range(12)), shape=[2,3,2], flags=ND_FORTRAN) - self.assertEqual(hash(nd), hash(b)) - - # suboffsets - b = bytes(list(range(12))) - nd = ndarray(list(range(12)), shape=[2,2,3], flags=ND_PIL) - self.assertEqual(hash(nd), hash(b)) - - # non-byte formats - nd = ndarray(list(range(12)), shape=[2,2,3], format='L') - self.assertEqual(hash(nd), hash(nd.tobytes())) - - def test_py_buffer_to_contiguous(self): - - # The requests are used in _testbuffer.c:py_buffer_to_contiguous - # to generate buffers without full information for testing. - requests = ( - # distinct flags - PyBUF_INDIRECT, PyBUF_STRIDES, PyBUF_ND, PyBUF_SIMPLE, - # compound requests - PyBUF_FULL, PyBUF_FULL_RO, - PyBUF_RECORDS, PyBUF_RECORDS_RO, - PyBUF_STRIDED, PyBUF_STRIDED_RO, - PyBUF_CONTIG, PyBUF_CONTIG_RO, - ) - - # no buffer interface - self.assertRaises(TypeError, py_buffer_to_contiguous, {}, 'F', - PyBUF_FULL_RO) - - # scalar, read-only request - nd = ndarray(9, shape=(), format="L", flags=ND_WRITABLE) - for order in ['C', 'F', 'A']: - for request in requests: - b = py_buffer_to_contiguous(nd, order, request) - self.assertEqual(b, nd.tobytes()) - - # zeros in shape - nd = ndarray([1], shape=[0], format="L", flags=ND_WRITABLE) - for order in ['C', 'F', 'A']: - for request in requests: - b = py_buffer_to_contiguous(nd, order, request) - self.assertEqual(b, b'') - - nd = ndarray(list(range(8)), shape=[2, 0, 7], format="L", - flags=ND_WRITABLE) - for order in ['C', 'F', 'A']: - for request in requests: - b = py_buffer_to_contiguous(nd, order, request) - self.assertEqual(b, b'') - - ### One-dimensional arrays are trivial, since Fortran and C order - ### are the same. - - # one-dimensional - for f in [0, ND_FORTRAN]: - nd = ndarray([1], shape=[1], format="h", flags=f|ND_WRITABLE) - ndbytes = nd.tobytes() - for order in ['C', 'F', 'A']: - for request in requests: - b = py_buffer_to_contiguous(nd, order, request) - self.assertEqual(b, ndbytes) - - nd = ndarray([1, 2, 3], shape=[3], format="b", flags=f|ND_WRITABLE) - ndbytes = nd.tobytes() - for order in ['C', 'F', 'A']: - for request in requests: - b = py_buffer_to_contiguous(nd, order, request) - self.assertEqual(b, ndbytes) - - # one-dimensional, non-contiguous input - nd = ndarray([1, 2, 3], shape=[2], strides=[2], flags=ND_WRITABLE) - ndbytes = nd.tobytes() - for order in ['C', 'F', 'A']: - for request in [PyBUF_STRIDES, PyBUF_FULL]: - b = py_buffer_to_contiguous(nd, order, request) - self.assertEqual(b, ndbytes) - - nd = nd[::-1] - ndbytes = nd.tobytes() - for order in ['C', 'F', 'A']: - for request in requests: - try: - b = py_buffer_to_contiguous(nd, order, request) - except BufferError: - continue - self.assertEqual(b, ndbytes) - - ### - ### Multi-dimensional arrays: - ### - ### The goal here is to preserve the logical representation of the - ### input array but change the physical representation if necessary. - ### - ### _testbuffer example: - ### ==================== - ### - ### C input array: - ### -------------- - ### >>> nd = ndarray(list(range(12)), shape=[3, 4]) - ### >>> nd.tolist() - ### [[0, 1, 2, 3], - ### [4, 5, 6, 7], - ### [8, 9, 10, 11]] - ### - ### Fortran output: - ### --------------- - ### >>> py_buffer_to_contiguous(nd, 'F', PyBUF_FULL_RO) - ### >>> b'\x00\x04\x08\x01\x05\t\x02\x06\n\x03\x07\x0b' - ### - ### The return value corresponds to this input list for - ### _testbuffer's ndarray: - ### >>> nd = ndarray([0,4,8,1,5,9,2,6,10,3,7,11], shape=[3,4], - ### flags=ND_FORTRAN) - ### >>> nd.tolist() - ### [[0, 1, 2, 3], - ### [4, 5, 6, 7], - ### [8, 9, 10, 11]] - ### - ### The logical array is the same, but the values in memory are now - ### in Fortran order. - ### - ### NumPy example: - ### ============== - ### _testbuffer's ndarray takes lists to initialize the memory. - ### Here's the same sequence in NumPy: - ### - ### C input: - ### -------- - ### >>> nd = ndarray(buffer=bytearray(list(range(12))), - ### shape=[3, 4], dtype='B') - ### >>> nd - ### array([[ 0, 1, 2, 3], - ### [ 4, 5, 6, 7], - ### [ 8, 9, 10, 11]], dtype=uint8) - ### - ### Fortran output: - ### --------------- - ### >>> fortran_buf = nd.tostring(order='F') - ### >>> fortran_buf - ### b'\x00\x04\x08\x01\x05\t\x02\x06\n\x03\x07\x0b' - ### - ### >>> nd = ndarray(buffer=fortran_buf, shape=[3, 4], - ### dtype='B', order='F') - ### - ### >>> nd - ### array([[ 0, 1, 2, 3], - ### [ 4, 5, 6, 7], - ### [ 8, 9, 10, 11]], dtype=uint8) - ### - - # multi-dimensional, contiguous input - lst = list(range(12)) - for f in [0, ND_FORTRAN]: - nd = ndarray(lst, shape=[3, 4], flags=f|ND_WRITABLE) - if numpy_array: - na = numpy_array(buffer=bytearray(lst), - shape=[3, 4], dtype='B', - order='C' if f == 0 else 'F') - - # 'C' request - if f == ND_FORTRAN: # 'F' to 'C' - x = ndarray(transpose(lst, [4, 3]), shape=[3, 4], - flags=ND_WRITABLE) - expected = x.tobytes() - else: - expected = nd.tobytes() - for request in requests: - try: - b = py_buffer_to_contiguous(nd, 'C', request) - except BufferError: - continue - - self.assertEqual(b, expected) - - # Check that output can be used as the basis for constructing - # a C array that is logically identical to the input array. - y = ndarray([v for v in b], shape=[3, 4], flags=ND_WRITABLE) - self.assertEqual(memoryview(y), memoryview(nd)) - - if numpy_array: - self.assertEqual(b, na.tostring(order='C')) - - # 'F' request - if f == 0: # 'C' to 'F' - x = ndarray(transpose(lst, [3, 4]), shape=[4, 3], - flags=ND_WRITABLE) - else: - x = ndarray(lst, shape=[3, 4], flags=ND_WRITABLE) - expected = x.tobytes() - for request in [PyBUF_FULL, PyBUF_FULL_RO, PyBUF_INDIRECT, - PyBUF_STRIDES, PyBUF_ND]: - try: - b = py_buffer_to_contiguous(nd, 'F', request) - except BufferError: - continue - self.assertEqual(b, expected) - - # Check that output can be used as the basis for constructing - # a Fortran array that is logically identical to the input array. - y = ndarray([v for v in b], shape=[3, 4], flags=ND_FORTRAN|ND_WRITABLE) - self.assertEqual(memoryview(y), memoryview(nd)) - - if numpy_array: - self.assertEqual(b, na.tostring(order='F')) - - # 'A' request - if f == ND_FORTRAN: - x = ndarray(lst, shape=[3, 4], flags=ND_WRITABLE) - expected = x.tobytes() - else: - expected = nd.tobytes() - for request in [PyBUF_FULL, PyBUF_FULL_RO, PyBUF_INDIRECT, - PyBUF_STRIDES, PyBUF_ND]: - try: - b = py_buffer_to_contiguous(nd, 'A', request) - except BufferError: - continue - - self.assertEqual(b, expected) - - # Check that output can be used as the basis for constructing - # an array with order=f that is logically identical to the input - # array. - y = ndarray([v for v in b], shape=[3, 4], flags=f|ND_WRITABLE) - self.assertEqual(memoryview(y), memoryview(nd)) - - if numpy_array: - self.assertEqual(b, na.tostring(order='A')) - - # multi-dimensional, non-contiguous input - nd = ndarray(list(range(12)), shape=[3, 4], flags=ND_WRITABLE|ND_PIL) - - # 'C' - b = py_buffer_to_contiguous(nd, 'C', PyBUF_FULL_RO) - self.assertEqual(b, nd.tobytes()) - y = ndarray([v for v in b], shape=[3, 4], flags=ND_WRITABLE) - self.assertEqual(memoryview(y), memoryview(nd)) - - # 'F' - b = py_buffer_to_contiguous(nd, 'F', PyBUF_FULL_RO) - x = ndarray(transpose(lst, [3, 4]), shape=[4, 3], flags=ND_WRITABLE) - self.assertEqual(b, x.tobytes()) - y = ndarray([v for v in b], shape=[3, 4], flags=ND_FORTRAN|ND_WRITABLE) - self.assertEqual(memoryview(y), memoryview(nd)) - - # 'A' - b = py_buffer_to_contiguous(nd, 'A', PyBUF_FULL_RO) - self.assertEqual(b, nd.tobytes()) - y = ndarray([v for v in b], shape=[3, 4], flags=ND_WRITABLE) - self.assertEqual(memoryview(y), memoryview(nd)) - - def test_memoryview_construction(self): - - items_shape = [(9, []), ([1,2,3], [3]), (list(range(2*3*5)), [2,3,5])] - - # NumPy style, C-contiguous: - for items, shape in items_shape: - - # From PEP-3118 compliant exporter: - ex = ndarray(items, shape=shape) - m = memoryview(ex) - self.assertTrue(m.c_contiguous) - self.assertTrue(m.contiguous) - - ndim = len(shape) - strides = strides_from_shape(ndim, shape, 1, 'C') - lst = carray(items, shape) - - self.verify(m, obj=ex, - itemsize=1, fmt='B', readonly=True, - ndim=ndim, shape=shape, strides=strides, - lst=lst) - - # From memoryview: - m2 = memoryview(m) - self.verify(m2, obj=ex, - itemsize=1, fmt='B', readonly=True, - ndim=ndim, shape=shape, strides=strides, - lst=lst) - - # PyMemoryView_FromBuffer(): no strides - nd = ndarray(ex, getbuf=PyBUF_CONTIG_RO|PyBUF_FORMAT) - self.assertEqual(nd.strides, ()) - m = nd.memoryview_from_buffer() - self.verify(m, obj=None, - itemsize=1, fmt='B', readonly=True, - ndim=ndim, shape=shape, strides=strides, - lst=lst) - - # PyMemoryView_FromBuffer(): no format, shape, strides - nd = ndarray(ex, getbuf=PyBUF_SIMPLE) - self.assertEqual(nd.format, '') - self.assertEqual(nd.shape, ()) - self.assertEqual(nd.strides, ()) - m = nd.memoryview_from_buffer() - - lst = [items] if ndim == 0 else items - self.verify(m, obj=None, - itemsize=1, fmt='B', readonly=True, - ndim=1, shape=[ex.nbytes], strides=(1,), - lst=lst) - - # NumPy style, Fortran contiguous: - for items, shape in items_shape: - - # From PEP-3118 compliant exporter: - ex = ndarray(items, shape=shape, flags=ND_FORTRAN) - m = memoryview(ex) - self.assertTrue(m.f_contiguous) - self.assertTrue(m.contiguous) - - ndim = len(shape) - strides = strides_from_shape(ndim, shape, 1, 'F') - lst = farray(items, shape) - - self.verify(m, obj=ex, - itemsize=1, fmt='B', readonly=True, - ndim=ndim, shape=shape, strides=strides, - lst=lst) - - # From memoryview: - m2 = memoryview(m) - self.verify(m2, obj=ex, - itemsize=1, fmt='B', readonly=True, - ndim=ndim, shape=shape, strides=strides, - lst=lst) - - # PIL style: - for items, shape in items_shape[1:]: - - # From PEP-3118 compliant exporter: - ex = ndarray(items, shape=shape, flags=ND_PIL) - m = memoryview(ex) - - ndim = len(shape) - lst = carray(items, shape) - - self.verify(m, obj=ex, - itemsize=1, fmt='B', readonly=True, - ndim=ndim, shape=shape, strides=ex.strides, - lst=lst) - - # From memoryview: - m2 = memoryview(m) - self.verify(m2, obj=ex, - itemsize=1, fmt='B', readonly=True, - ndim=ndim, shape=shape, strides=ex.strides, - lst=lst) - - # Invalid number of arguments: - self.assertRaises(TypeError, memoryview, b'9', 'x') - # Not a buffer provider: - self.assertRaises(TypeError, memoryview, {}) - # Non-compliant buffer provider: - ex = ndarray([1,2,3], shape=[3]) - nd = ndarray(ex, getbuf=PyBUF_SIMPLE) - self.assertRaises(BufferError, memoryview, nd) - nd = ndarray(ex, getbuf=PyBUF_CONTIG_RO|PyBUF_FORMAT) - self.assertRaises(BufferError, memoryview, nd) - - # ndim > 64 - nd = ndarray([1]*128, shape=[1]*128, format='L') - self.assertRaises(ValueError, memoryview, nd) - self.assertRaises(ValueError, nd.memoryview_from_buffer) - self.assertRaises(ValueError, get_contiguous, nd, PyBUF_READ, 'C') - self.assertRaises(ValueError, get_contiguous, nd, PyBUF_READ, 'F') - self.assertRaises(ValueError, get_contiguous, nd[::-1], PyBUF_READ, 'C') - - def test_memoryview_cast_zero_shape(self): - # Casts are undefined if buffer is multidimensional and shape - # contains zeros. These arrays are regarded as C-contiguous by - # Numpy and PyBuffer_GetContiguous(), so they are not caught by - # the test for C-contiguity in memory_cast(). - items = [1,2,3] - for shape in ([0,3,3], [3,0,3], [0,3,3]): - ex = ndarray(items, shape=shape) - self.assertTrue(ex.c_contiguous) - msrc = memoryview(ex) - self.assertRaises(TypeError, msrc.cast, 'c') - # Monodimensional empty view can be cast (issue #19014). - for fmt, _, _ in iter_format(1, 'memoryview'): - msrc = memoryview(b'') - m = msrc.cast(fmt) - self.assertEqual(m.tobytes(), b'') - self.assertEqual(m.tolist(), []) - - check_sizeof = support.check_sizeof - - def test_memoryview_sizeof(self): - check = self.check_sizeof - vsize = support.calcvobjsize - base_struct = 'Pnin 2P2n2i5P P' - per_dim = '3n' - - items = list(range(8)) - check(memoryview(b''), vsize(base_struct + 1 * per_dim)) - a = ndarray(items, shape=[2, 4], format="b") - check(memoryview(a), vsize(base_struct + 2 * per_dim)) - a = ndarray(items, shape=[2, 2, 2], format="b") - check(memoryview(a), vsize(base_struct + 3 * per_dim)) - - def test_memoryview_struct_module(self): - - class INT(object): - def __init__(self, val): - self.val = val - def __int__(self): - return self.val - - class IDX(object): - def __init__(self, val): - self.val = val - def __index__(self): - return self.val - - def f(): return 7 - - values = [INT(9), IDX(9), - 2.2+3j, Decimal("-21.1"), 12.2, Fraction(5, 2), - [1,2,3], {4,5,6}, {7:8}, (), (9,), - True, False, None, NotImplemented, - b'a', b'abc', bytearray(b'a'), bytearray(b'abc'), - 'a', 'abc', r'a', r'abc', - f, lambda x: x] - - for fmt, items, item in iter_format(10, 'memoryview'): - ex = ndarray(items, shape=[10], format=fmt, flags=ND_WRITABLE) - nd = ndarray(items, shape=[10], format=fmt, flags=ND_WRITABLE) - m = memoryview(ex) - - struct.pack_into(fmt, nd, 0, item) - m[0] = item - self.assertEqual(m[0], nd[0]) - - itemsize = struct.calcsize(fmt) - if 'P' in fmt: - continue - - for v in values: - struct_err = None - try: - struct.pack_into(fmt, nd, itemsize, v) - except struct.error: - struct_err = struct.error - - mv_err = None - try: - m[1] = v - except (TypeError, ValueError) as e: - mv_err = e.__class__ - - if struct_err or mv_err: - self.assertIsNot(struct_err, None) - self.assertIsNot(mv_err, None) - else: - self.assertEqual(m[1], nd[1]) - - def test_memoryview_cast_zero_strides(self): - # Casts are undefined if strides contains zeros. These arrays are - # (sometimes!) regarded as C-contiguous by Numpy, but not by - # PyBuffer_GetContiguous(). - ex = ndarray([1,2,3], shape=[3], strides=[0]) - self.assertFalse(ex.c_contiguous) - msrc = memoryview(ex) - self.assertRaises(TypeError, msrc.cast, 'c') - - def test_memoryview_cast_invalid(self): - # invalid format - for sfmt in NON_BYTE_FORMAT: - sformat = '@' + sfmt if randrange(2) else sfmt - ssize = struct.calcsize(sformat) - for dfmt in NON_BYTE_FORMAT: - dformat = '@' + dfmt if randrange(2) else dfmt - dsize = struct.calcsize(dformat) - ex = ndarray(list(range(32)), shape=[32//ssize], format=sformat) - msrc = memoryview(ex) - self.assertRaises(TypeError, msrc.cast, dfmt, [32//dsize]) - - for sfmt, sitems, _ in iter_format(1): - ex = ndarray(sitems, shape=[1], format=sfmt) - msrc = memoryview(ex) - for dfmt, _, _ in iter_format(1): - if not is_memoryview_format(dfmt): - self.assertRaises(ValueError, msrc.cast, dfmt, - [32//dsize]) - else: - if not is_byte_format(sfmt) and not is_byte_format(dfmt): - self.assertRaises(TypeError, msrc.cast, dfmt, - [32//dsize]) - - # invalid shape - size_h = struct.calcsize('h') - size_d = struct.calcsize('d') - ex = ndarray(list(range(2*2*size_d)), shape=[2,2,size_d], format='h') - msrc = memoryview(ex) - self.assertRaises(TypeError, msrc.cast, shape=[2,2,size_h], format='d') - - ex = ndarray(list(range(120)), shape=[1,2,3,4,5]) - m = memoryview(ex) - - # incorrect number of args - self.assertRaises(TypeError, m.cast) - self.assertRaises(TypeError, m.cast, 1, 2, 3) - - # incorrect dest format type - self.assertRaises(TypeError, m.cast, {}) - - # incorrect dest format - self.assertRaises(ValueError, m.cast, "X") - self.assertRaises(ValueError, m.cast, "@X") - self.assertRaises(ValueError, m.cast, "@XY") - - # dest format not implemented - self.assertRaises(ValueError, m.cast, "=B") - self.assertRaises(ValueError, m.cast, "!L") - self.assertRaises(ValueError, m.cast, "l") - self.assertRaises(ValueError, m.cast, "BI") - self.assertRaises(ValueError, m.cast, "xBI") - - # src format not implemented - ex = ndarray([(1,2), (3,4)], shape=[2], format="II") - m = memoryview(ex) - self.assertRaises(NotImplementedError, m.__getitem__, 0) - self.assertRaises(NotImplementedError, m.__setitem__, 0, 8) - self.assertRaises(NotImplementedError, m.tolist) - - # incorrect shape type - ex = ndarray(list(range(120)), shape=[1,2,3,4,5]) - m = memoryview(ex) - self.assertRaises(TypeError, m.cast, "B", shape={}) - - # incorrect shape elements - ex = ndarray(list(range(120)), shape=[2*3*4*5]) - m = memoryview(ex) - self.assertRaises(OverflowError, m.cast, "B", shape=[2**64]) - self.assertRaises(ValueError, m.cast, "B", shape=[-1]) - self.assertRaises(ValueError, m.cast, "B", shape=[2,3,4,5,6,7,-1]) - self.assertRaises(ValueError, m.cast, "B", shape=[2,3,4,5,6,7,0]) - self.assertRaises(TypeError, m.cast, "B", shape=[2,3,4,5,6,7,'x']) - - # N-D -> N-D cast - ex = ndarray(list([9 for _ in range(3*5*7*11)]), shape=[3,5,7,11]) - m = memoryview(ex) - self.assertRaises(TypeError, m.cast, "I", shape=[2,3,4,5]) - - # cast with ndim > 64 - nd = ndarray(list(range(128)), shape=[128], format='I') - m = memoryview(nd) - self.assertRaises(ValueError, m.cast, 'I', [1]*128) - - # view->len not a multiple of itemsize - ex = ndarray(list([9 for _ in range(3*5*7*11)]), shape=[3*5*7*11]) - m = memoryview(ex) - self.assertRaises(TypeError, m.cast, "I", shape=[2,3,4,5]) - - # product(shape) * itemsize != buffer size - ex = ndarray(list([9 for _ in range(3*5*7*11)]), shape=[3*5*7*11]) - m = memoryview(ex) - self.assertRaises(TypeError, m.cast, "B", shape=[2,3,4,5]) - - # product(shape) * itemsize overflow - nd = ndarray(list(range(128)), shape=[128], format='I') - m1 = memoryview(nd) - nd = ndarray(list(range(128)), shape=[128], format='B') - m2 = memoryview(nd) - if sys.maxsize == 2**63-1: - self.assertRaises(TypeError, m1.cast, 'B', - [7, 7, 73, 127, 337, 92737, 649657]) - self.assertRaises(ValueError, m1.cast, 'B', - [2**20, 2**20, 2**10, 2**10, 2**3]) - self.assertRaises(ValueError, m2.cast, 'I', - [2**20, 2**20, 2**10, 2**10, 2**1]) - else: - self.assertRaises(TypeError, m1.cast, 'B', - [1, 2147483647]) - self.assertRaises(ValueError, m1.cast, 'B', - [2**10, 2**10, 2**5, 2**5, 2**1]) - self.assertRaises(ValueError, m2.cast, 'I', - [2**10, 2**10, 2**5, 2**3, 2**1]) - - def test_memoryview_cast(self): - bytespec = ( - ('B', lambda ex: list(ex.tobytes())), - ('b', lambda ex: [x-256 if x > 127 else x for x in list(ex.tobytes())]), - ('c', lambda ex: [bytes(chr(x), 'latin-1') for x in list(ex.tobytes())]), - ) - - def iter_roundtrip(ex, m, items, fmt): - srcsize = struct.calcsize(fmt) - for bytefmt, to_bytelist in bytespec: - - m2 = m.cast(bytefmt) - lst = to_bytelist(ex) - self.verify(m2, obj=ex, - itemsize=1, fmt=bytefmt, readonly=False, - ndim=1, shape=[31*srcsize], strides=(1,), - lst=lst, cast=True) - - m3 = m2.cast(fmt) - self.assertEqual(m3, ex) - lst = ex.tolist() - self.verify(m3, obj=ex, - itemsize=srcsize, fmt=fmt, readonly=False, - ndim=1, shape=[31], strides=(srcsize,), - lst=lst, cast=True) - - # cast from ndim = 0 to ndim = 1 - srcsize = struct.calcsize('I') - ex = ndarray(9, shape=[], format='I') - destitems, destshape = cast_items(ex, 'B', 1) - m = memoryview(ex) - m2 = m.cast('B') - self.verify(m2, obj=ex, - itemsize=1, fmt='B', readonly=True, - ndim=1, shape=destshape, strides=(1,), - lst=destitems, cast=True) - - # cast from ndim = 1 to ndim = 0 - destsize = struct.calcsize('I') - ex = ndarray([9]*destsize, shape=[destsize], format='B') - destitems, destshape = cast_items(ex, 'I', destsize, shape=[]) - m = memoryview(ex) - m2 = m.cast('I', shape=[]) - self.verify(m2, obj=ex, - itemsize=destsize, fmt='I', readonly=True, - ndim=0, shape=(), strides=(), - lst=destitems, cast=True) - - # array.array: roundtrip to/from bytes - for fmt, items, _ in iter_format(31, 'array'): - ex = array.array(fmt, items) - m = memoryview(ex) - iter_roundtrip(ex, m, items, fmt) - - # ndarray: roundtrip to/from bytes - for fmt, items, _ in iter_format(31, 'memoryview'): - ex = ndarray(items, shape=[31], format=fmt, flags=ND_WRITABLE) - m = memoryview(ex) - iter_roundtrip(ex, m, items, fmt) - - def test_memoryview_cast_1D_ND(self): - # Cast between C-contiguous buffers. At least one buffer must - # be 1D, at least one format must be 'c', 'b' or 'B'. - for _tshape in gencastshapes(): - for char in fmtdict['@']: - # Casts to _Bool are undefined if the source contains values - # other than 0 or 1. - if char == "?": - continue - tfmt = ('', '@')[randrange(2)] + char - tsize = struct.calcsize(tfmt) - n = prod(_tshape) * tsize - obj = 'memoryview' if is_byte_format(tfmt) else 'bytefmt' - for fmt, items, _ in iter_format(n, obj): - size = struct.calcsize(fmt) - shape = [n] if n > 0 else [] - tshape = _tshape + [size] - - ex = ndarray(items, shape=shape, format=fmt) - m = memoryview(ex) - - titems, tshape = cast_items(ex, tfmt, tsize, shape=tshape) - - if titems is None: - self.assertRaises(TypeError, m.cast, tfmt, tshape) - continue - if titems == 'nan': - continue # NaNs in lists are a recipe for trouble. - - # 1D -> ND - nd = ndarray(titems, shape=tshape, format=tfmt) - - m2 = m.cast(tfmt, shape=tshape) - ndim = len(tshape) - strides = nd.strides - lst = nd.tolist() - self.verify(m2, obj=ex, - itemsize=tsize, fmt=tfmt, readonly=True, - ndim=ndim, shape=tshape, strides=strides, - lst=lst, cast=True) - - # ND -> 1D - m3 = m2.cast(fmt) - m4 = m2.cast(fmt, shape=shape) - ndim = len(shape) - strides = ex.strides - lst = ex.tolist() - - self.verify(m3, obj=ex, - itemsize=size, fmt=fmt, readonly=True, - ndim=ndim, shape=shape, strides=strides, - lst=lst, cast=True) - - self.verify(m4, obj=ex, - itemsize=size, fmt=fmt, readonly=True, - ndim=ndim, shape=shape, strides=strides, - lst=lst, cast=True) - - if ctypes: - # format: "T{>l:x:>d:y:}" - class BEPoint(ctypes.BigEndianStructure): - _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_double)] - point = BEPoint(100, 200.1) - m1 = memoryview(point) - m2 = m1.cast('B') - self.assertEqual(m2.obj, point) - self.assertEqual(m2.itemsize, 1) - self.assertIs(m2.readonly, False) - self.assertEqual(m2.ndim, 1) - self.assertEqual(m2.shape, (m2.nbytes,)) - self.assertEqual(m2.strides, (1,)) - self.assertEqual(m2.suboffsets, ()) - - x = ctypes.c_double(1.2) - m1 = memoryview(x) - m2 = m1.cast('c') - self.assertEqual(m2.obj, x) - self.assertEqual(m2.itemsize, 1) - self.assertIs(m2.readonly, False) - self.assertEqual(m2.ndim, 1) - self.assertEqual(m2.shape, (m2.nbytes,)) - self.assertEqual(m2.strides, (1,)) - self.assertEqual(m2.suboffsets, ()) - - def test_memoryview_tolist(self): - - # Most tolist() tests are in self.verify() etc. - - a = array.array('h', list(range(-6, 6))) - m = memoryview(a) - self.assertEqual(m, a) - self.assertEqual(m.tolist(), a.tolist()) - - a = a[2::3] - m = m[2::3] - self.assertEqual(m, a) - self.assertEqual(m.tolist(), a.tolist()) - - ex = ndarray(list(range(2*3*5*7*11)), shape=[11,2,7,3,5], format='L') - m = memoryview(ex) - self.assertEqual(m.tolist(), ex.tolist()) - - ex = ndarray([(2, 5), (7, 11)], shape=[2], format='lh') - m = memoryview(ex) - self.assertRaises(NotImplementedError, m.tolist) - - ex = ndarray([b'12345'], shape=[1], format="s") - m = memoryview(ex) - self.assertRaises(NotImplementedError, m.tolist) - - ex = ndarray([b"a",b"b",b"c",b"d",b"e",b"f"], shape=[2,3], format='s') - m = memoryview(ex) - self.assertRaises(NotImplementedError, m.tolist) - - def test_memoryview_repr(self): - m = memoryview(bytearray(9)) - r = m.__repr__() - self.assertTrue(r.startswith("l:x:>l:y:}" - class BEPoint(ctypes.BigEndianStructure): - _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] - point = BEPoint(100, 200) - a = memoryview(point) - b = memoryview(point) - self.assertNotEqual(a, b) - self.assertNotEqual(a, point) - self.assertNotEqual(point, a) - self.assertRaises(NotImplementedError, a.tolist) - - def test_memoryview_compare_ndim_zero(self): - - nd1 = ndarray(1729, shape=[], format='@L') - nd2 = ndarray(1729, shape=[], format='L', flags=ND_WRITABLE) - v = memoryview(nd1) - w = memoryview(nd2) - self.assertEqual(v, w) - self.assertEqual(w, v) - self.assertEqual(v, nd2) - self.assertEqual(nd2, v) - self.assertEqual(w, nd1) - self.assertEqual(nd1, w) - - self.assertFalse(v.__ne__(w)) - self.assertFalse(w.__ne__(v)) - - w[()] = 1728 - self.assertNotEqual(v, w) - self.assertNotEqual(w, v) - self.assertNotEqual(v, nd2) - self.assertNotEqual(nd2, v) - self.assertNotEqual(w, nd1) - self.assertNotEqual(nd1, w) - - self.assertFalse(v.__eq__(w)) - self.assertFalse(w.__eq__(v)) - - nd = ndarray(list(range(12)), shape=[12], flags=ND_WRITABLE|ND_PIL) - ex = ndarray(list(range(12)), shape=[12], flags=ND_WRITABLE|ND_PIL) - m = memoryview(ex) - - self.assertEqual(m, nd) - m[9] = 100 - self.assertNotEqual(m, nd) - - # struct module: equal - nd1 = ndarray((1729, 1.2, b'12345'), shape=[], format='Lf5s') - nd2 = ndarray((1729, 1.2, b'12345'), shape=[], format='hf5s', - flags=ND_WRITABLE) - v = memoryview(nd1) - w = memoryview(nd2) - self.assertEqual(v, w) - self.assertEqual(w, v) - self.assertEqual(v, nd2) - self.assertEqual(nd2, v) - self.assertEqual(w, nd1) - self.assertEqual(nd1, w) - - # struct module: not equal - nd1 = ndarray((1729, 1.2, b'12345'), shape=[], format='Lf5s') - nd2 = ndarray((-1729, 1.2, b'12345'), shape=[], format='hf5s', - flags=ND_WRITABLE) - v = memoryview(nd1) - w = memoryview(nd2) - self.assertNotEqual(v, w) - self.assertNotEqual(w, v) - self.assertNotEqual(v, nd2) - self.assertNotEqual(nd2, v) - self.assertNotEqual(w, nd1) - self.assertNotEqual(nd1, w) - self.assertEqual(v, nd1) - self.assertEqual(w, nd2) - - def test_memoryview_compare_ndim_one(self): - - # contiguous - nd1 = ndarray([-529, 576, -625, 676, -729], shape=[5], format='@h') - nd2 = ndarray([-529, 576, -625, 676, 729], shape=[5], format='@h') - v = memoryview(nd1) - w = memoryview(nd2) - - self.assertEqual(v, nd1) - self.assertEqual(w, nd2) - self.assertNotEqual(v, nd2) - self.assertNotEqual(w, nd1) - self.assertNotEqual(v, w) - - # contiguous, struct module - nd1 = ndarray([-529, 576, -625, 676, -729], shape=[5], format='', '!']: - x = ndarray([2**63]*120, shape=[3,5,2,2,2], format=byteorder+'Q') - y = ndarray([2**63]*120, shape=[3,5,2,2,2], format=byteorder+'Q', - flags=ND_WRITABLE|ND_FORTRAN) - y[2][3][1][1][1] = 1 - a = memoryview(x) - b = memoryview(y) - self.assertEqual(a, x) - self.assertEqual(b, y) - self.assertNotEqual(a, b) - self.assertNotEqual(a, y) - self.assertNotEqual(b, x) - - x = ndarray([(2**63, 2**31, 2**15)]*120, shape=[3,5,2,2,2], - format=byteorder+'QLH') - y = ndarray([(2**63, 2**31, 2**15)]*120, shape=[3,5,2,2,2], - format=byteorder+'QLH', flags=ND_WRITABLE|ND_FORTRAN) - y[2][3][1][1][1] = (1, 1, 1) - a = memoryview(x) - b = memoryview(y) - self.assertEqual(a, x) - self.assertEqual(b, y) - self.assertNotEqual(a, b) - self.assertNotEqual(a, y) - self.assertNotEqual(b, x) - - def test_memoryview_check_released(self): - - a = array.array('d', [1.1, 2.2, 3.3]) - - m = memoryview(a) - m.release() - - # PyMemoryView_FromObject() - self.assertRaises(ValueError, memoryview, m) - # memoryview.cast() - self.assertRaises(ValueError, m.cast, 'c') - # getbuffer() - self.assertRaises(ValueError, ndarray, m) - # memoryview.tolist() - self.assertRaises(ValueError, m.tolist) - # memoryview.tobytes() - self.assertRaises(ValueError, m.tobytes) - # sequence - self.assertRaises(ValueError, eval, "1.0 in m", locals()) - # subscript - self.assertRaises(ValueError, m.__getitem__, 0) - # assignment - self.assertRaises(ValueError, m.__setitem__, 0, 1) - - for attr in ('obj', 'nbytes', 'readonly', 'itemsize', 'format', 'ndim', - 'shape', 'strides', 'suboffsets', 'c_contiguous', - 'f_contiguous', 'contiguous'): - self.assertRaises(ValueError, m.__getattribute__, attr) - - # richcompare - b = array.array('d', [1.1, 2.2, 3.3]) - m1 = memoryview(a) - m2 = memoryview(b) - - self.assertEqual(m1, m2) - m1.release() - self.assertNotEqual(m1, m2) - self.assertNotEqual(m1, a) - self.assertEqual(m1, m1) - - def test_memoryview_tobytes(self): - # Many implicit tests are already in self.verify(). - - t = (-529, 576, -625, 676, -729) - - nd = ndarray(t, shape=[5], format='@h') - m = memoryview(nd) - self.assertEqual(m, nd) - self.assertEqual(m.tobytes(), nd.tobytes()) - - nd = ndarray([t], shape=[1], format='>hQiLl') - m = memoryview(nd) - self.assertEqual(m, nd) - self.assertEqual(m.tobytes(), nd.tobytes()) - - nd = ndarray([t for _ in range(12)], shape=[2,2,3], format='=hQiLl') - m = memoryview(nd) - self.assertEqual(m, nd) - self.assertEqual(m.tobytes(), nd.tobytes()) - - nd = ndarray([t for _ in range(120)], shape=[5,2,2,3,2], - format='l:x:>l:y:}" - class BEPoint(ctypes.BigEndianStructure): - _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] - point = BEPoint(100, 200) - a = memoryview(point) - self.assertEqual(a.tobytes(), bytes(point)) - - def test_memoryview_get_contiguous(self): - # Many implicit tests are already in self.verify(). - - # no buffer interface - self.assertRaises(TypeError, get_contiguous, {}, PyBUF_READ, 'F') - - # writable request to read-only object - self.assertRaises(BufferError, get_contiguous, b'x', PyBUF_WRITE, 'C') - - # writable request to non-contiguous object - nd = ndarray([1, 2, 3], shape=[2], strides=[2]) - self.assertRaises(BufferError, get_contiguous, nd, PyBUF_WRITE, 'A') - - # scalar, read-only request from read-only exporter - nd = ndarray(9, shape=(), format="L") - for order in ['C', 'F', 'A']: - m = get_contiguous(nd, PyBUF_READ, order) - self.assertEqual(m, nd) - self.assertEqual(m[()], 9) - - # scalar, read-only request from writable exporter - nd = ndarray(9, shape=(), format="L", flags=ND_WRITABLE) - for order in ['C', 'F', 'A']: - m = get_contiguous(nd, PyBUF_READ, order) - self.assertEqual(m, nd) - self.assertEqual(m[()], 9) - - # scalar, writable request - for order in ['C', 'F', 'A']: - nd[()] = 9 - m = get_contiguous(nd, PyBUF_WRITE, order) - self.assertEqual(m, nd) - self.assertEqual(m[()], 9) - - m[()] = 10 - self.assertEqual(m[()], 10) - self.assertEqual(nd[()], 10) - - # zeros in shape - nd = ndarray([1], shape=[0], format="L", flags=ND_WRITABLE) - for order in ['C', 'F', 'A']: - m = get_contiguous(nd, PyBUF_READ, order) - self.assertRaises(IndexError, m.__getitem__, 0) - self.assertEqual(m, nd) - self.assertEqual(m.tolist(), []) - - nd = ndarray(list(range(8)), shape=[2, 0, 7], format="L", - flags=ND_WRITABLE) - for order in ['C', 'F', 'A']: - m = get_contiguous(nd, PyBUF_READ, order) - self.assertEqual(ndarray(m).tolist(), [[], []]) - - # one-dimensional - nd = ndarray([1], shape=[1], format="h", flags=ND_WRITABLE) - for order in ['C', 'F', 'A']: - m = get_contiguous(nd, PyBUF_WRITE, order) - self.assertEqual(m, nd) - self.assertEqual(m.tolist(), nd.tolist()) - - nd = ndarray([1, 2, 3], shape=[3], format="b", flags=ND_WRITABLE) - for order in ['C', 'F', 'A']: - m = get_contiguous(nd, PyBUF_WRITE, order) - self.assertEqual(m, nd) - self.assertEqual(m.tolist(), nd.tolist()) - - # one-dimensional, non-contiguous - nd = ndarray([1, 2, 3], shape=[2], strides=[2], flags=ND_WRITABLE) - for order in ['C', 'F', 'A']: - m = get_contiguous(nd, PyBUF_READ, order) - self.assertEqual(m, nd) - self.assertEqual(m.tolist(), nd.tolist()) - self.assertRaises(TypeError, m.__setitem__, 1, 20) - self.assertEqual(m[1], 3) - self.assertEqual(nd[1], 3) - - nd = nd[::-1] - for order in ['C', 'F', 'A']: - m = get_contiguous(nd, PyBUF_READ, order) - self.assertEqual(m, nd) - self.assertEqual(m.tolist(), nd.tolist()) - self.assertRaises(TypeError, m.__setitem__, 1, 20) - self.assertEqual(m[1], 1) - self.assertEqual(nd[1], 1) - - # multi-dimensional, contiguous input - nd = ndarray(list(range(12)), shape=[3, 4], flags=ND_WRITABLE) - for order in ['C', 'A']: - m = get_contiguous(nd, PyBUF_WRITE, order) - self.assertEqual(ndarray(m).tolist(), nd.tolist()) - - self.assertRaises(BufferError, get_contiguous, nd, PyBUF_WRITE, 'F') - m = get_contiguous(nd, PyBUF_READ, order) - self.assertEqual(ndarray(m).tolist(), nd.tolist()) - - nd = ndarray(list(range(12)), shape=[3, 4], - flags=ND_WRITABLE|ND_FORTRAN) - for order in ['F', 'A']: - m = get_contiguous(nd, PyBUF_WRITE, order) - self.assertEqual(ndarray(m).tolist(), nd.tolist()) - - self.assertRaises(BufferError, get_contiguous, nd, PyBUF_WRITE, 'C') - m = get_contiguous(nd, PyBUF_READ, order) - self.assertEqual(ndarray(m).tolist(), nd.tolist()) - - # multi-dimensional, non-contiguous input - nd = ndarray(list(range(12)), shape=[3, 4], flags=ND_WRITABLE|ND_PIL) - for order in ['C', 'F', 'A']: - self.assertRaises(BufferError, get_contiguous, nd, PyBUF_WRITE, - order) - m = get_contiguous(nd, PyBUF_READ, order) - self.assertEqual(ndarray(m).tolist(), nd.tolist()) - - # flags - nd = ndarray([1,2,3,4,5], shape=[3], strides=[2]) - m = get_contiguous(nd, PyBUF_READ, 'C') - self.assertTrue(m.c_contiguous) - - def test_memoryview_serializing(self): - - # C-contiguous - size = struct.calcsize('i') - a = array.array('i', [1,2,3,4,5]) - m = memoryview(a) - buf = io.BytesIO(m) - b = bytearray(5*size) - buf.readinto(b) - self.assertEqual(m.tobytes(), b) - - # C-contiguous, multi-dimensional - size = struct.calcsize('L') - nd = ndarray(list(range(12)), shape=[2,3,2], format="L") - m = memoryview(nd) - buf = io.BytesIO(m) - b = bytearray(2*3*2*size) - buf.readinto(b) - self.assertEqual(m.tobytes(), b) - - # Fortran contiguous, multi-dimensional - #size = struct.calcsize('L') - #nd = ndarray(list(range(12)), shape=[2,3,2], format="L", - # flags=ND_FORTRAN) - #m = memoryview(nd) - #buf = io.BytesIO(m) - #b = bytearray(2*3*2*size) - #buf.readinto(b) - #self.assertEqual(m.tobytes(), b) - - def test_memoryview_hash(self): - - # bytes exporter - b = bytes(list(range(12))) - m = memoryview(b) - self.assertEqual(hash(b), hash(m)) - - # C-contiguous - mc = m.cast('c', shape=[3,4]) - self.assertEqual(hash(mc), hash(b)) - - # non-contiguous - mx = m[::-2] - b = bytes(list(range(12))[::-2]) - self.assertEqual(hash(mx), hash(b)) - - # Fortran contiguous - nd = ndarray(list(range(30)), shape=[3,2,5], flags=ND_FORTRAN) - m = memoryview(nd) - self.assertEqual(hash(m), hash(nd)) - - # multi-dimensional slice - nd = ndarray(list(range(30)), shape=[3,2,5]) - x = nd[::2, ::, ::-1] - m = memoryview(x) - self.assertEqual(hash(m), hash(x)) - - # multi-dimensional slice with suboffsets - nd = ndarray(list(range(30)), shape=[2,5,3], flags=ND_PIL) - x = nd[::2, ::, ::-1] - m = memoryview(x) - self.assertEqual(hash(m), hash(x)) - - # equality-hash invariant - x = ndarray(list(range(12)), shape=[12], format='B') - a = memoryview(x) - - y = ndarray(list(range(12)), shape=[12], format='b') - b = memoryview(y) - - self.assertEqual(a, b) - self.assertEqual(hash(a), hash(b)) - - # non-byte formats - nd = ndarray(list(range(12)), shape=[2,2,3], format='L') - m = memoryview(nd) - self.assertRaises(ValueError, m.__hash__) - - nd = ndarray(list(range(-6, 6)), shape=[2,2,3], format='h') - m = memoryview(nd) - self.assertRaises(ValueError, m.__hash__) - - nd = ndarray(list(range(12)), shape=[2,2,3], format='= L') - m = memoryview(nd) - self.assertRaises(ValueError, m.__hash__) - - nd = ndarray(list(range(-6, 6)), shape=[2,2,3], format='< h') - m = memoryview(nd) - self.assertRaises(ValueError, m.__hash__) - - def test_memoryview_release(self): - - # Create re-exporter from getbuffer(memoryview), then release the view. - a = bytearray([1,2,3]) - m = memoryview(a) - nd = ndarray(m) # re-exporter - self.assertRaises(BufferError, m.release) - del nd - m.release() - - a = bytearray([1,2,3]) - m = memoryview(a) - nd1 = ndarray(m, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) - nd2 = ndarray(nd1, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) - self.assertIs(nd2.obj, m) - self.assertRaises(BufferError, m.release) - del nd1, nd2 - m.release() - - # chained views - a = bytearray([1,2,3]) - m1 = memoryview(a) - m2 = memoryview(m1) - nd = ndarray(m2) # re-exporter - m1.release() - self.assertRaises(BufferError, m2.release) - del nd - m2.release() - - a = bytearray([1,2,3]) - m1 = memoryview(a) - m2 = memoryview(m1) - nd1 = ndarray(m2, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) - nd2 = ndarray(nd1, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) - self.assertIs(nd2.obj, m2) - m1.release() - self.assertRaises(BufferError, m2.release) - del nd1, nd2 - m2.release() - - # Allow changing layout while buffers are exported. - nd = ndarray([1,2,3], shape=[3], flags=ND_VAREXPORT) - m1 = memoryview(nd) - - nd.push([4,5,6,7,8], shape=[5]) # mutate nd - m2 = memoryview(nd) - - x = memoryview(m1) - self.assertEqual(x.tolist(), m1.tolist()) - - y = memoryview(m2) - self.assertEqual(y.tolist(), m2.tolist()) - self.assertEqual(y.tolist(), nd.tolist()) - m2.release() - y.release() - - nd.pop() # pop the current view - self.assertEqual(x.tolist(), nd.tolist()) - - del nd - m1.release() - x.release() - - # If multiple memoryviews share the same managed buffer, implicit - # release() in the context manager's __exit__() method should still - # work. - def catch22(b): - with memoryview(b) as m2: - pass - - x = bytearray(b'123') - with memoryview(x) as m1: - catch22(m1) - self.assertEqual(m1[0], ord(b'1')) - - x = ndarray(list(range(12)), shape=[2,2,3], format='l') - y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) - z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) - self.assertIs(z.obj, x) - with memoryview(z) as m: - catch22(m) - self.assertEqual(m[0:1].tolist(), [[[0, 1, 2], [3, 4, 5]]]) - - # Test garbage collection. - for flags in (0, ND_REDIRECT): - x = bytearray(b'123') - with memoryview(x) as m1: - del x - y = ndarray(m1, getbuf=PyBUF_FULL_RO, flags=flags) - with memoryview(y) as m2: - del y - z = ndarray(m2, getbuf=PyBUF_FULL_RO, flags=flags) - with memoryview(z) as m3: - del z - catch22(m3) - catch22(m2) - catch22(m1) - self.assertEqual(m1[0], ord(b'1')) - self.assertEqual(m2[1], ord(b'2')) - self.assertEqual(m3[2], ord(b'3')) - del m3 - del m2 - del m1 - - x = bytearray(b'123') - with memoryview(x) as m1: - del x - y = ndarray(m1, getbuf=PyBUF_FULL_RO, flags=flags) - with memoryview(y) as m2: - del y - z = ndarray(m2, getbuf=PyBUF_FULL_RO, flags=flags) - with memoryview(z) as m3: - del z - catch22(m1) - catch22(m2) - catch22(m3) - self.assertEqual(m1[0], ord(b'1')) - self.assertEqual(m2[1], ord(b'2')) - self.assertEqual(m3[2], ord(b'3')) - del m1, m2, m3 - - # memoryview.release() fails if the view has exported buffers. - x = bytearray(b'123') - with self.assertRaises(BufferError): - with memoryview(x) as m: - ex = ndarray(m) - m[0] == ord(b'1') - - def test_memoryview_redirect(self): - - nd = ndarray([1.0 * x for x in range(12)], shape=[12], format='d') - a = array.array('d', [1.0 * x for x in range(12)]) - - for x in (nd, a): - y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) - z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) - m = memoryview(z) - - self.assertIs(y.obj, x) - self.assertIs(z.obj, x) - self.assertIs(m.obj, x) - - self.assertEqual(m, x) - self.assertEqual(m, y) - self.assertEqual(m, z) - - self.assertEqual(m[1:3], x[1:3]) - self.assertEqual(m[1:3], y[1:3]) - self.assertEqual(m[1:3], z[1:3]) - del y, z - self.assertEqual(m[1:3], x[1:3]) - - def test_memoryview_from_static_exporter(self): - - fmt = 'B' - lst = [0,1,2,3,4,5,6,7,8,9,10,11] - - # exceptions - self.assertRaises(TypeError, staticarray, 1, 2, 3) - - # view.obj==x - x = staticarray() - y = memoryview(x) - self.verify(y, obj=x, - itemsize=1, fmt=fmt, readonly=True, - ndim=1, shape=[12], strides=[1], - lst=lst) - for i in range(12): - self.assertEqual(y[i], i) - del x - del y - - x = staticarray() - y = memoryview(x) - del y - del x - - x = staticarray() - y = ndarray(x, getbuf=PyBUF_FULL_RO) - z = ndarray(y, getbuf=PyBUF_FULL_RO) - m = memoryview(z) - self.assertIs(y.obj, x) - self.assertIs(m.obj, z) - self.verify(m, obj=z, - itemsize=1, fmt=fmt, readonly=True, - ndim=1, shape=[12], strides=[1], - lst=lst) - del x, y, z, m - - x = staticarray() - y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) - z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) - m = memoryview(z) - self.assertIs(y.obj, x) - self.assertIs(z.obj, x) - self.assertIs(m.obj, x) - self.verify(m, obj=x, - itemsize=1, fmt=fmt, readonly=True, - ndim=1, shape=[12], strides=[1], - lst=lst) - del x, y, z, m - - # view.obj==NULL - x = staticarray(legacy_mode=True) - y = memoryview(x) - self.verify(y, obj=None, - itemsize=1, fmt=fmt, readonly=True, - ndim=1, shape=[12], strides=[1], - lst=lst) - for i in range(12): - self.assertEqual(y[i], i) - del x - del y - - x = staticarray(legacy_mode=True) - y = memoryview(x) - del y - del x - - x = staticarray(legacy_mode=True) - y = ndarray(x, getbuf=PyBUF_FULL_RO) - z = ndarray(y, getbuf=PyBUF_FULL_RO) - m = memoryview(z) - self.assertIs(y.obj, None) - self.assertIs(m.obj, z) - self.verify(m, obj=z, - itemsize=1, fmt=fmt, readonly=True, - ndim=1, shape=[12], strides=[1], - lst=lst) - del x, y, z, m - - x = staticarray(legacy_mode=True) - y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) - z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) - m = memoryview(z) - # Clearly setting view.obj==NULL is inferior, since it - # messes up the redirection chain: - self.assertIs(y.obj, None) - self.assertIs(z.obj, y) - self.assertIs(m.obj, y) - self.verify(m, obj=y, - itemsize=1, fmt=fmt, readonly=True, - ndim=1, shape=[12], strides=[1], - lst=lst) - del x, y, z, m - - def test_memoryview_getbuffer_undefined(self): - - # getbufferproc does not adhere to the new documentation - nd = ndarray([1,2,3], [3], flags=ND_GETBUF_FAIL|ND_GETBUF_UNDEFINED) - self.assertRaises(BufferError, memoryview, nd) - - def test_issue_7385(self): - x = ndarray([1,2,3], shape=[3], flags=ND_GETBUF_FAIL) - self.assertRaises(BufferError, memoryview, x) - - -if __name__ == "__main__": - unittest.main() +# +# The ndarray object from _testbuffer.c is a complete implementation of +# a PEP-3118 buffer provider. It is independent from NumPy's ndarray +# and the tests don't require NumPy. +# +# If NumPy is present, some tests check both ndarray implementations +# against each other. +# +# Most ndarray tests also check that memoryview(ndarray) behaves in +# the same way as the original. Thus, a substantial part of the +# memoryview tests is now in this module. +# +# Written and designed by Stefan Krah for Python 3.3. +# + +import contextlib +import unittest +from test import support +from test.support import os_helper +from itertools import permutations, product +from random import randrange, sample, choice +import warnings +import sys, array, io, os +from decimal import Decimal +from fractions import Fraction + +try: + from _testbuffer import * +except ImportError: + ndarray = None + +try: + import struct +except ImportError: + struct = None + +try: + import ctypes +except ImportError: + ctypes = None + +try: + with os_helper.EnvironmentVarGuard() as os.environ, \ + warnings.catch_warnings(): + from numpy import ndarray as numpy_array +except ImportError: + numpy_array = None + + +SHORT_TEST = True + + +# ====================================================================== +# Random lists by format specifier +# ====================================================================== + +# Native format chars and their ranges. +NATIVE = { + '?':0, 'c':0, 'b':0, 'B':0, + 'h':0, 'H':0, 'i':0, 'I':0, + 'l':0, 'L':0, 'n':0, 'N':0, + 'f':0, 'd':0, 'P':0 +} + +# NumPy does not have 'n' or 'N': +if numpy_array: + del NATIVE['n'] + del NATIVE['N'] + +if struct: + try: + # Add "qQ" if present in native mode. + struct.pack('Q', 2**64-1) + NATIVE['q'] = 0 + NATIVE['Q'] = 0 + except struct.error: + pass + +# Standard format chars and their ranges. +STANDARD = { + '?':(0, 2), 'c':(0, 1<<8), + 'b':(-(1<<7), 1<<7), 'B':(0, 1<<8), + 'h':(-(1<<15), 1<<15), 'H':(0, 1<<16), + 'i':(-(1<<31), 1<<31), 'I':(0, 1<<32), + 'l':(-(1<<31), 1<<31), 'L':(0, 1<<32), + 'q':(-(1<<63), 1<<63), 'Q':(0, 1<<64), + 'f':(-(1<<63), 1<<63), 'd':(-(1<<1023), 1<<1023) +} + +def native_type_range(fmt): + """Return range of a native type.""" + if fmt == 'c': + lh = (0, 256) + elif fmt == '?': + lh = (0, 2) + elif fmt == 'f': + lh = (-(1<<63), 1<<63) + elif fmt == 'd': + lh = (-(1<<1023), 1<<1023) + else: + for exp in (128, 127, 64, 63, 32, 31, 16, 15, 8, 7): + try: + struct.pack(fmt, (1<':STANDARD, + '=':STANDARD, + '!':STANDARD +} + +if struct: + for fmt in fmtdict['@']: + fmtdict['@'][fmt] = native_type_range(fmt) + +MEMORYVIEW = NATIVE.copy() +ARRAY = NATIVE.copy() +for k in NATIVE: + if not k in "bBhHiIlLfd": + del ARRAY[k] + +BYTEFMT = NATIVE.copy() +for k in NATIVE: + if not k in "Bbc": + del BYTEFMT[k] + +fmtdict['m'] = MEMORYVIEW +fmtdict['@m'] = MEMORYVIEW +fmtdict['a'] = ARRAY +fmtdict['b'] = BYTEFMT +fmtdict['@b'] = BYTEFMT + +# Capabilities of the test objects: +MODE = 0 +MULT = 1 +cap = { # format chars # multiplier + 'ndarray': (['', '@', '<', '>', '=', '!'], ['', '1', '2', '3']), + 'array': (['a'], ['']), + 'numpy': ([''], ['']), + 'memoryview': (['@m', 'm'], ['']), + 'bytefmt': (['@b', 'b'], ['']), +} + +def randrange_fmt(mode, char, obj): + """Return random item for a type specified by a mode and a single + format character.""" + x = randrange(*fmtdict[mode][char]) + if char == 'c': + x = bytes([x]) + if obj == 'numpy' and x == b'\x00': + # http://projects.scipy.org/numpy/ticket/1925 + x = b'\x01' + if char == '?': + x = bool(x) + if char == 'f' or char == 'd': + x = struct.pack(char, x) + x = struct.unpack(char, x)[0] + return x + +def gen_item(fmt, obj): + """Return single random item.""" + mode, chars = fmt.split('#') + x = [] + for c in chars: + x.append(randrange_fmt(mode, c, obj)) + return x[0] if len(x) == 1 else tuple(x) + +def gen_items(n, fmt, obj): + """Return a list of random items (or a scalar).""" + if n == 0: + return gen_item(fmt, obj) + lst = [0] * n + for i in range(n): + lst[i] = gen_item(fmt, obj) + return lst + +def struct_items(n, obj): + mode = choice(cap[obj][MODE]) + xfmt = mode + '#' + fmt = mode.strip('amb') + nmemb = randrange(2, 10) # number of struct members + for _ in range(nmemb): + char = choice(tuple(fmtdict[mode])) + multiplier = choice(cap[obj][MULT]) + xfmt += (char * int(multiplier if multiplier else 1)) + fmt += (multiplier + char) + items = gen_items(n, xfmt, obj) + item = gen_item(xfmt, obj) + return fmt, items, item + +def randitems(n, obj='ndarray', mode=None, char=None): + """Return random format, items, item.""" + if mode is None: + mode = choice(cap[obj][MODE]) + if char is None: + char = choice(tuple(fmtdict[mode])) + multiplier = choice(cap[obj][MULT]) + fmt = mode + '#' + char * int(multiplier if multiplier else 1) + items = gen_items(n, fmt, obj) + item = gen_item(fmt, obj) + fmt = mode.strip('amb') + multiplier + char + return fmt, items, item + +def iter_mode(n, obj='ndarray'): + """Iterate through supported mode/char combinations.""" + for mode in cap[obj][MODE]: + for char in fmtdict[mode]: + yield randitems(n, obj, mode, char) + +def iter_format(nitems, testobj='ndarray'): + """Yield (format, items, item) for all possible modes and format + characters plus one random compound format string.""" + for t in iter_mode(nitems, testobj): + yield t + if testobj != 'ndarray': + return + yield struct_items(nitems, testobj) + + +def is_byte_format(fmt): + return 'c' in fmt or 'b' in fmt or 'B' in fmt + +def is_memoryview_format(fmt): + """format suitable for memoryview""" + x = len(fmt) + return ((x == 1 or (x == 2 and fmt[0] == '@')) and + fmt[x-1] in MEMORYVIEW) + +NON_BYTE_FORMAT = [c for c in fmtdict['@'] if not is_byte_format(c)] + + +# ====================================================================== +# Multi-dimensional tolist(), slicing and slice assignments +# ====================================================================== + +def atomp(lst): + """Tuple items (representing structs) are regarded as atoms.""" + return not isinstance(lst, list) + +def listp(lst): + return isinstance(lst, list) + +def prod(lst): + """Product of list elements.""" + if len(lst) == 0: + return 0 + x = lst[0] + for v in lst[1:]: + x *= v + return x + +def strides_from_shape(ndim, shape, itemsize, layout): + """Calculate strides of a contiguous array. Layout is 'C' or + 'F' (Fortran).""" + if ndim == 0: + return () + if layout == 'C': + strides = list(shape[1:]) + [itemsize] + for i in range(ndim-2, -1, -1): + strides[i] *= strides[i+1] + else: + strides = [itemsize] + list(shape[:-1]) + for i in range(1, ndim): + strides[i] *= strides[i-1] + return strides + +def _ca(items, s): + """Convert flat item list to the nested list representation of a + multidimensional C array with shape 's'.""" + if atomp(items): + return items + if len(s) == 0: + return items[0] + lst = [0] * s[0] + stride = len(items) // s[0] if s[0] else 0 + for i in range(s[0]): + start = i*stride + lst[i] = _ca(items[start:start+stride], s[1:]) + return lst + +def _fa(items, s): + """Convert flat item list to the nested list representation of a + multidimensional Fortran array with shape 's'.""" + if atomp(items): + return items + if len(s) == 0: + return items[0] + lst = [0] * s[0] + stride = s[0] + for i in range(s[0]): + lst[i] = _fa(items[i::stride], s[1:]) + return lst + +def carray(items, shape): + if listp(items) and not 0 in shape and prod(shape) != len(items): + raise ValueError("prod(shape) != len(items)") + return _ca(items, shape) + +def farray(items, shape): + if listp(items) and not 0 in shape and prod(shape) != len(items): + raise ValueError("prod(shape) != len(items)") + return _fa(items, shape) + +def indices(shape): + """Generate all possible tuples of indices.""" + iterables = [range(v) for v in shape] + return product(*iterables) + +def getindex(ndim, ind, strides): + """Convert multi-dimensional index to the position in the flat list.""" + ret = 0 + for i in range(ndim): + ret += strides[i] * ind[i] + return ret + +def transpose(src, shape): + """Transpose flat item list that is regarded as a multi-dimensional + matrix defined by shape: dest...[k][j][i] = src[i][j][k]... """ + if not shape: + return src + ndim = len(shape) + sstrides = strides_from_shape(ndim, shape, 1, 'C') + dstrides = strides_from_shape(ndim, shape[::-1], 1, 'C') + dest = [0] * len(src) + for ind in indices(shape): + fr = getindex(ndim, ind, sstrides) + to = getindex(ndim, ind[::-1], dstrides) + dest[to] = src[fr] + return dest + +def _flatten(lst): + """flatten list""" + if lst == []: + return lst + if atomp(lst): + return [lst] + return _flatten(lst[0]) + _flatten(lst[1:]) + +def flatten(lst): + """flatten list or return scalar""" + if atomp(lst): # scalar + return lst + return _flatten(lst) + +def slice_shape(lst, slices): + """Get the shape of lst after slicing: slices is a list of slice + objects.""" + if atomp(lst): + return [] + return [len(lst[slices[0]])] + slice_shape(lst[0], slices[1:]) + +def multislice(lst, slices): + """Multi-dimensional slicing: slices is a list of slice objects.""" + if atomp(lst): + return lst + return [multislice(sublst, slices[1:]) for sublst in lst[slices[0]]] + +def m_assign(llst, rlst, lslices, rslices): + """Multi-dimensional slice assignment: llst and rlst are the operands, + lslices and rslices are lists of slice objects. llst and rlst must + have the same structure. + + For a two-dimensional example, this is not implemented in Python: + + llst[0:3:2, 0:3:2] = rlst[1:3:1, 1:3:1] + + Instead we write: + + lslices = [slice(0,3,2), slice(0,3,2)] + rslices = [slice(1,3,1), slice(1,3,1)] + multislice_assign(llst, rlst, lslices, rslices) + """ + if atomp(rlst): + return rlst + rlst = [m_assign(l, r, lslices[1:], rslices[1:]) + for l, r in zip(llst[lslices[0]], rlst[rslices[0]])] + llst[lslices[0]] = rlst + return llst + +def cmp_structure(llst, rlst, lslices, rslices): + """Compare the structure of llst[lslices] and rlst[rslices].""" + lshape = slice_shape(llst, lslices) + rshape = slice_shape(rlst, rslices) + if (len(lshape) != len(rshape)): + return -1 + for i in range(len(lshape)): + if lshape[i] != rshape[i]: + return -1 + if lshape[i] == 0: + return 0 + return 0 + +def multislice_assign(llst, rlst, lslices, rslices): + """Return llst after assigning: llst[lslices] = rlst[rslices]""" + if cmp_structure(llst, rlst, lslices, rslices) < 0: + raise ValueError("lvalue and rvalue have different structures") + return m_assign(llst, rlst, lslices, rslices) + + +# ====================================================================== +# Random structures +# ====================================================================== + +# +# PEP-3118 is very permissive with respect to the contents of a +# Py_buffer. In particular: +# +# - shape can be zero +# - strides can be any integer, including zero +# - offset can point to any location in the underlying +# memory block, provided that it is a multiple of +# itemsize. +# +# The functions in this section test and verify random structures +# in full generality. A structure is valid iff it fits in the +# underlying memory block. +# +# The structure 't' (short for 'tuple') is fully defined by: +# +# t = (memlen, itemsize, ndim, shape, strides, offset) +# + +def verify_structure(memlen, itemsize, ndim, shape, strides, offset): + """Verify that the parameters represent a valid array within + the bounds of the allocated memory: + char *mem: start of the physical memory block + memlen: length of the physical memory block + offset: (char *)buf - mem + """ + if offset % itemsize: + return False + if offset < 0 or offset+itemsize > memlen: + return False + if any(v % itemsize for v in strides): + return False + + if ndim <= 0: + return ndim == 0 and not shape and not strides + if 0 in shape: + return True + + imin = sum(strides[j]*(shape[j]-1) for j in range(ndim) + if strides[j] <= 0) + imax = sum(strides[j]*(shape[j]-1) for j in range(ndim) + if strides[j] > 0) + + return 0 <= offset+imin and offset+imax+itemsize <= memlen + +def get_item(lst, indices): + for i in indices: + lst = lst[i] + return lst + +def memory_index(indices, t): + """Location of an item in the underlying memory.""" + memlen, itemsize, ndim, shape, strides, offset = t + p = offset + for i in range(ndim): + p += strides[i]*indices[i] + return p + +def is_overlapping(t): + """The structure 't' is overlapping if at least one memory location + is visited twice while iterating through all possible tuples of + indices.""" + memlen, itemsize, ndim, shape, strides, offset = t + visited = 1<= 95 and valid: + minshape = 0 + elif n >= 90: + minshape = 1 + shape = [0] * ndim + + for i in range(ndim): + shape[i] = randrange(minshape, maxshape+1) + else: + ndim = len(shape) + + maxstride = 5 + n = randrange(100) + zero_stride = True if n >= 95 and n & 1 else False + + strides = [0] * ndim + strides[ndim-1] = itemsize * randrange(-maxstride, maxstride+1) + if not zero_stride and strides[ndim-1] == 0: + strides[ndim-1] = itemsize + + for i in range(ndim-2, -1, -1): + maxstride *= shape[i+1] if shape[i+1] else 1 + if zero_stride: + strides[i] = itemsize * randrange(-maxstride, maxstride+1) + else: + strides[i] = ((1,-1)[randrange(2)] * + itemsize * randrange(1, maxstride+1)) + + imin = imax = 0 + if not 0 in shape: + imin = sum(strides[j]*(shape[j]-1) for j in range(ndim) + if strides[j] <= 0) + imax = sum(strides[j]*(shape[j]-1) for j in range(ndim) + if strides[j] > 0) + + nitems = imax - imin + if valid: + offset = -imin * itemsize + memlen = offset + (imax+1) * itemsize + else: + memlen = (-imin + imax) * itemsize + offset = -imin-itemsize if randrange(2) == 0 else memlen + return memlen, itemsize, ndim, shape, strides, offset + +def randslice_from_slicelen(slicelen, listlen): + """Create a random slice of len slicelen that fits into listlen.""" + maxstart = listlen - slicelen + start = randrange(maxstart+1) + maxstep = (listlen - start) // slicelen if slicelen else 1 + step = randrange(1, maxstep+1) + stop = start + slicelen * step + s = slice(start, stop, step) + _, _, _, control = slice_indices(s, listlen) + if control != slicelen: + raise RuntimeError + return s + +def randslice_from_shape(ndim, shape): + """Create two sets of slices for an array x with shape 'shape' + such that shapeof(x[lslices]) == shapeof(x[rslices]).""" + lslices = [0] * ndim + rslices = [0] * ndim + for n in range(ndim): + l = shape[n] + slicelen = randrange(1, l+1) if l > 0 else 0 + lslices[n] = randslice_from_slicelen(slicelen, l) + rslices[n] = randslice_from_slicelen(slicelen, l) + return tuple(lslices), tuple(rslices) + +def rand_aligned_slices(maxdim=5, maxshape=16): + """Create (lshape, rshape, tuple(lslices), tuple(rslices)) such that + shapeof(x[lslices]) == shapeof(y[rslices]), where x is an array + with shape 'lshape' and y is an array with shape 'rshape'.""" + ndim = randrange(1, maxdim+1) + minshape = 2 + n = randrange(100) + if n >= 95: + minshape = 0 + elif n >= 90: + minshape = 1 + all_random = True if randrange(100) >= 80 else False + lshape = [0]*ndim; rshape = [0]*ndim + lslices = [0]*ndim; rslices = [0]*ndim + + for n in range(ndim): + small = randrange(minshape, maxshape+1) + big = randrange(minshape, maxshape+1) + if big < small: + big, small = small, big + + # Create a slice that fits the smaller value. + if all_random: + start = randrange(-small, small+1) + stop = randrange(-small, small+1) + step = (1,-1)[randrange(2)] * randrange(1, small+2) + s_small = slice(start, stop, step) + _, _, _, slicelen = slice_indices(s_small, small) + else: + slicelen = randrange(1, small+1) if small > 0 else 0 + s_small = randslice_from_slicelen(slicelen, small) + + # Create a slice of the same length for the bigger value. + s_big = randslice_from_slicelen(slicelen, big) + if randrange(2) == 0: + rshape[n], lshape[n] = big, small + rslices[n], lslices[n] = s_big, s_small + else: + rshape[n], lshape[n] = small, big + rslices[n], lslices[n] = s_small, s_big + + return lshape, rshape, tuple(lslices), tuple(rslices) + +def randitems_from_structure(fmt, t): + """Return a list of random items for structure 't' with format + 'fmtchar'.""" + memlen, itemsize, _, _, _, _ = t + return gen_items(memlen//itemsize, '#'+fmt, 'numpy') + +def ndarray_from_structure(items, fmt, t, flags=0): + """Return ndarray from the tuple returned by rand_structure()""" + memlen, itemsize, ndim, shape, strides, offset = t + return ndarray(items, shape=shape, strides=strides, format=fmt, + offset=offset, flags=ND_WRITABLE|flags) + +def numpy_array_from_structure(items, fmt, t): + """Return numpy_array from the tuple returned by rand_structure()""" + memlen, itemsize, ndim, shape, strides, offset = t + buf = bytearray(memlen) + for j, v in enumerate(items): + struct.pack_into(fmt, buf, j*itemsize, v) + return numpy_array(buffer=buf, shape=shape, strides=strides, + dtype=fmt, offset=offset) + + +# ====================================================================== +# memoryview casts +# ====================================================================== + +def cast_items(exporter, fmt, itemsize, shape=None): + """Interpret the raw memory of 'exporter' as a list of items with + size 'itemsize'. If shape=None, the new structure is assumed to + be 1-D with n * itemsize = bytelen. If shape is given, the usual + constraint for contiguous arrays prod(shape) * itemsize = bytelen + applies. On success, return (items, shape). If the constraints + cannot be met, return (None, None). If a chunk of bytes is interpreted + as NaN as a result of float conversion, return ('nan', None).""" + bytelen = exporter.nbytes + if shape: + if prod(shape) * itemsize != bytelen: + return None, shape + elif shape == []: + if exporter.ndim == 0 or itemsize != bytelen: + return None, shape + else: + n, r = divmod(bytelen, itemsize) + shape = [n] + if r != 0: + return None, shape + + mem = exporter.tobytes() + byteitems = [mem[i:i+itemsize] for i in range(0, len(mem), itemsize)] + + items = [] + for v in byteitems: + item = struct.unpack(fmt, v)[0] + if item != item: + return 'nan', shape + items.append(item) + + return (items, shape) if shape != [] else (items[0], shape) + +def gencastshapes(): + """Generate shapes to test casting.""" + for n in range(32): + yield [n] + ndim = randrange(4, 6) + minshape = 1 if randrange(100) > 80 else 2 + yield [randrange(minshape, 5) for _ in range(ndim)] + ndim = randrange(2, 4) + minshape = 1 if randrange(100) > 80 else 2 + yield [randrange(minshape, 5) for _ in range(ndim)] + + +# ====================================================================== +# Actual tests +# ====================================================================== + +def genslices(n): + """Generate all possible slices for a single dimension.""" + return product(range(-n, n+1), range(-n, n+1), range(-n, n+1)) + +def genslices_ndim(ndim, shape): + """Generate all possible slice tuples for 'shape'.""" + iterables = [genslices(shape[n]) for n in range(ndim)] + return product(*iterables) + +def rslice(n, allow_empty=False): + """Generate random slice for a single dimension of length n. + If zero=True, the slices may be empty, otherwise they will + be non-empty.""" + minlen = 0 if allow_empty or n == 0 else 1 + slicelen = randrange(minlen, n+1) + return randslice_from_slicelen(slicelen, n) + +def rslices(n, allow_empty=False): + """Generate random slices for a single dimension.""" + for _ in range(5): + yield rslice(n, allow_empty) + +def rslices_ndim(ndim, shape, iterations=5): + """Generate random slice tuples for 'shape'.""" + # non-empty slices + for _ in range(iterations): + yield tuple(rslice(shape[n]) for n in range(ndim)) + # possibly empty slices + for _ in range(iterations): + yield tuple(rslice(shape[n], allow_empty=True) for n in range(ndim)) + # invalid slices + yield tuple(slice(0,1,0) for _ in range(ndim)) + +def rpermutation(iterable, r=None): + pool = tuple(iterable) + r = len(pool) if r is None else r + yield tuple(sample(pool, r)) + +def ndarray_print(nd): + """Print ndarray for debugging.""" + try: + x = nd.tolist() + except (TypeError, NotImplementedError): + x = nd.tobytes() + if isinstance(nd, ndarray): + offset = nd.offset + flags = nd.flags + else: + offset = 'unknown' + flags = 'unknown' + print("ndarray(%s, shape=%s, strides=%s, suboffsets=%s, offset=%s, " + "format='%s', itemsize=%s, flags=%s)" % + (x, nd.shape, nd.strides, nd.suboffsets, offset, + nd.format, nd.itemsize, flags)) + sys.stdout.flush() + + +ITERATIONS = 100 +MAXDIM = 5 +MAXSHAPE = 10 + +if SHORT_TEST: + ITERATIONS = 10 + MAXDIM = 3 + MAXSHAPE = 4 + genslices = rslices + genslices_ndim = rslices_ndim + permutations = rpermutation + + +@unittest.skipUnless(struct, 'struct module required for this test.') +@unittest.skipUnless(ndarray, 'ndarray object required for this test') +class TestBufferProtocol(unittest.TestCase): + + def setUp(self): + # The suboffsets tests need sizeof(void *). + self.sizeof_void_p = get_sizeof_void_p() + + def verify(self, result, *, obj, + itemsize, fmt, readonly, + ndim, shape, strides, + lst, sliced=False, cast=False): + # Verify buffer contents against expected values. + if shape: + expected_len = prod(shape)*itemsize + else: + if not fmt: # array has been implicitly cast to unsigned bytes + expected_len = len(lst) + else: # ndim = 0 + expected_len = itemsize + + # Reconstruct suboffsets from strides. Support for slicing + # could be added, but is currently only needed for test_getbuf(). + suboffsets = () + if result.suboffsets: + self.assertGreater(ndim, 0) + + suboffset0 = 0 + for n in range(1, ndim): + if shape[n] == 0: + break + if strides[n] <= 0: + suboffset0 += -strides[n] * (shape[n]-1) + + suboffsets = [suboffset0] + [-1 for v in range(ndim-1)] + + # Not correct if slicing has occurred in the first dimension. + stride0 = self.sizeof_void_p + if strides[0] < 0: + stride0 = -stride0 + strides = [stride0] + list(strides[1:]) + + self.assertIs(result.obj, obj) + self.assertEqual(result.nbytes, expected_len) + self.assertEqual(result.itemsize, itemsize) + self.assertEqual(result.format, fmt) + self.assertIs(result.readonly, readonly) + self.assertEqual(result.ndim, ndim) + self.assertEqual(result.shape, tuple(shape)) + if not (sliced and suboffsets): + self.assertEqual(result.strides, tuple(strides)) + self.assertEqual(result.suboffsets, tuple(suboffsets)) + + if isinstance(result, ndarray) or is_memoryview_format(fmt): + rep = result.tolist() if fmt else result.tobytes() + self.assertEqual(rep, lst) + + if not fmt: # array has been cast to unsigned bytes, + return # the remaining tests won't work. + + # PyBuffer_GetPointer() is the definition how to access an item. + # If PyBuffer_GetPointer(indices) is correct for all possible + # combinations of indices, the buffer is correct. + # + # Also test tobytes() against the flattened 'lst', with all items + # packed to bytes. + if not cast: # casts chop up 'lst' in different ways + b = bytearray() + buf_err = None + for ind in indices(shape): + try: + item1 = get_pointer(result, ind) + item2 = get_item(lst, ind) + if isinstance(item2, tuple): + x = struct.pack(fmt, *item2) + else: + x = struct.pack(fmt, item2) + b.extend(x) + except BufferError: + buf_err = True # re-exporter does not provide full buffer + break + self.assertEqual(item1, item2) + + if not buf_err: + # test tobytes() + self.assertEqual(result.tobytes(), b) + + # test hex() + m = memoryview(result) + h = "".join("%02x" % c for c in b) + self.assertEqual(m.hex(), h) + + # lst := expected multi-dimensional logical representation + # flatten(lst) := elements in C-order + ff = fmt if fmt else 'B' + flattened = flatten(lst) + + # Rules for 'A': if the array is already contiguous, return + # the array unaltered. Otherwise, return a contiguous 'C' + # representation. + for order in ['C', 'F', 'A']: + expected = result + if order == 'F': + if not is_contiguous(result, 'A') or \ + is_contiguous(result, 'C'): + # For constructing the ndarray, convert the + # flattened logical representation to Fortran order. + trans = transpose(flattened, shape) + expected = ndarray(trans, shape=shape, format=ff, + flags=ND_FORTRAN) + else: # 'C', 'A' + if not is_contiguous(result, 'A') or \ + is_contiguous(result, 'F') and order == 'C': + # The flattened list is already in C-order. + expected = ndarray(flattened, shape=shape, format=ff) + + contig = get_contiguous(result, PyBUF_READ, order) + self.assertEqual(contig.tobytes(), b) + self.assertTrue(cmp_contig(contig, expected)) + + if ndim == 0: + continue + + nmemb = len(flattened) + ro = 0 if readonly else ND_WRITABLE + + ### See comment in test_py_buffer_to_contiguous for an + ### explanation why these tests are valid. + + # To 'C' + contig = py_buffer_to_contiguous(result, 'C', PyBUF_FULL_RO) + self.assertEqual(len(contig), nmemb * itemsize) + initlst = [struct.unpack_from(fmt, contig, n*itemsize) + for n in range(nmemb)] + if len(initlst[0]) == 1: + initlst = [v[0] for v in initlst] + + y = ndarray(initlst, shape=shape, flags=ro, format=fmt) + self.assertEqual(memoryview(y), memoryview(result)) + + contig_bytes = memoryview(result).tobytes() + self.assertEqual(contig_bytes, contig) + + contig_bytes = memoryview(result).tobytes(order=None) + self.assertEqual(contig_bytes, contig) + + contig_bytes = memoryview(result).tobytes(order='C') + self.assertEqual(contig_bytes, contig) + + # To 'F' + contig = py_buffer_to_contiguous(result, 'F', PyBUF_FULL_RO) + self.assertEqual(len(contig), nmemb * itemsize) + initlst = [struct.unpack_from(fmt, contig, n*itemsize) + for n in range(nmemb)] + if len(initlst[0]) == 1: + initlst = [v[0] for v in initlst] + + y = ndarray(initlst, shape=shape, flags=ro|ND_FORTRAN, + format=fmt) + self.assertEqual(memoryview(y), memoryview(result)) + + contig_bytes = memoryview(result).tobytes(order='F') + self.assertEqual(contig_bytes, contig) + + # To 'A' + contig = py_buffer_to_contiguous(result, 'A', PyBUF_FULL_RO) + self.assertEqual(len(contig), nmemb * itemsize) + initlst = [struct.unpack_from(fmt, contig, n*itemsize) + for n in range(nmemb)] + if len(initlst[0]) == 1: + initlst = [v[0] for v in initlst] + + f = ND_FORTRAN if is_contiguous(result, 'F') else 0 + y = ndarray(initlst, shape=shape, flags=f|ro, format=fmt) + self.assertEqual(memoryview(y), memoryview(result)) + + contig_bytes = memoryview(result).tobytes(order='A') + self.assertEqual(contig_bytes, contig) + + if is_memoryview_format(fmt): + try: + m = memoryview(result) + except BufferError: # re-exporter does not provide full information + return + ex = result.obj if isinstance(result, memoryview) else result + + def check_memoryview(m, expected_readonly=readonly): + self.assertIs(m.obj, ex) + self.assertEqual(m.nbytes, expected_len) + self.assertEqual(m.itemsize, itemsize) + self.assertEqual(m.format, fmt) + self.assertEqual(m.readonly, expected_readonly) + self.assertEqual(m.ndim, ndim) + self.assertEqual(m.shape, tuple(shape)) + if not (sliced and suboffsets): + self.assertEqual(m.strides, tuple(strides)) + self.assertEqual(m.suboffsets, tuple(suboffsets)) + + n = 1 if ndim == 0 else len(lst) + self.assertEqual(len(m), n) + + rep = result.tolist() if fmt else result.tobytes() + self.assertEqual(rep, lst) + self.assertEqual(m, result) + + check_memoryview(m) + with m.toreadonly() as mm: + check_memoryview(mm, expected_readonly=True) + m.tobytes() # Releasing mm didn't release m + + def verify_getbuf(self, orig_ex, ex, req, sliced=False): + def simple_fmt(ex): + return ex.format == '' or ex.format == 'B' + def match(req, flag): + return ((req&flag) == flag) + + if (# writable request to read-only exporter + (ex.readonly and match(req, PyBUF_WRITABLE)) or + # cannot match explicit contiguity request + (match(req, PyBUF_C_CONTIGUOUS) and not ex.c_contiguous) or + (match(req, PyBUF_F_CONTIGUOUS) and not ex.f_contiguous) or + (match(req, PyBUF_ANY_CONTIGUOUS) and not ex.contiguous) or + # buffer needs suboffsets + (not match(req, PyBUF_INDIRECT) and ex.suboffsets) or + # buffer without strides must be C-contiguous + (not match(req, PyBUF_STRIDES) and not ex.c_contiguous) or + # PyBUF_SIMPLE|PyBUF_FORMAT and PyBUF_WRITABLE|PyBUF_FORMAT + (not match(req, PyBUF_ND) and match(req, PyBUF_FORMAT))): + + self.assertRaises(BufferError, ndarray, ex, getbuf=req) + return + + if isinstance(ex, ndarray) or is_memoryview_format(ex.format): + lst = ex.tolist() + else: + nd = ndarray(ex, getbuf=PyBUF_FULL_RO) + lst = nd.tolist() + + # The consumer may have requested default values or a NULL format. + ro = False if match(req, PyBUF_WRITABLE) else ex.readonly + fmt = ex.format + itemsize = ex.itemsize + ndim = ex.ndim + if not match(req, PyBUF_FORMAT): + # itemsize refers to the original itemsize before the cast. + # The equality product(shape) * itemsize = len still holds. + # The equality calcsize(format) = itemsize does _not_ hold. + fmt = '' + lst = orig_ex.tobytes() # Issue 12834 + if not match(req, PyBUF_ND): + ndim = 1 + shape = orig_ex.shape if match(req, PyBUF_ND) else () + strides = orig_ex.strides if match(req, PyBUF_STRIDES) else () + + nd = ndarray(ex, getbuf=req) + self.verify(nd, obj=ex, + itemsize=itemsize, fmt=fmt, readonly=ro, + ndim=ndim, shape=shape, strides=strides, + lst=lst, sliced=sliced) + + def test_ndarray_getbuf(self): + requests = ( + # distinct flags + PyBUF_INDIRECT, PyBUF_STRIDES, PyBUF_ND, PyBUF_SIMPLE, + PyBUF_C_CONTIGUOUS, PyBUF_F_CONTIGUOUS, PyBUF_ANY_CONTIGUOUS, + # compound requests + PyBUF_FULL, PyBUF_FULL_RO, + PyBUF_RECORDS, PyBUF_RECORDS_RO, + PyBUF_STRIDED, PyBUF_STRIDED_RO, + PyBUF_CONTIG, PyBUF_CONTIG_RO, + ) + # items and format + items_fmt = ( + ([True if x % 2 else False for x in range(12)], '?'), + ([1,2,3,4,5,6,7,8,9,10,11,12], 'b'), + ([1,2,3,4,5,6,7,8,9,10,11,12], 'B'), + ([(2**31-x) if x % 2 else (-2**31+x) for x in range(12)], 'l') + ) + # shape, strides, offset + structure = ( + ([], [], 0), + ([1,3,1], [], 0), + ([12], [], 0), + ([12], [-1], 11), + ([6], [2], 0), + ([6], [-2], 11), + ([3, 4], [], 0), + ([3, 4], [-4, -1], 11), + ([2, 2], [4, 1], 4), + ([2, 2], [-4, -1], 8) + ) + # ndarray creation flags + ndflags = ( + 0, ND_WRITABLE, ND_FORTRAN, ND_FORTRAN|ND_WRITABLE, + ND_PIL, ND_PIL|ND_WRITABLE + ) + # flags that can actually be used as flags + real_flags = (0, PyBUF_WRITABLE, PyBUF_FORMAT, + PyBUF_WRITABLE|PyBUF_FORMAT) + + for items, fmt in items_fmt: + itemsize = struct.calcsize(fmt) + for shape, strides, offset in structure: + strides = [v * itemsize for v in strides] + offset *= itemsize + for flags in ndflags: + + if strides and (flags&ND_FORTRAN): + continue + if not shape and (flags&ND_PIL): + continue + + _items = items if shape else items[0] + ex1 = ndarray(_items, format=fmt, flags=flags, + shape=shape, strides=strides, offset=offset) + ex2 = ex1[::-2] if shape else None + + m1 = memoryview(ex1) + if ex2: + m2 = memoryview(ex2) + if ex1.ndim == 0 or (ex1.ndim == 1 and shape and strides): + self.assertEqual(m1, ex1) + if ex2 and ex2.ndim == 1 and shape and strides: + self.assertEqual(m2, ex2) + + for req in requests: + for bits in real_flags: + self.verify_getbuf(ex1, ex1, req|bits) + self.verify_getbuf(ex1, m1, req|bits) + if ex2: + self.verify_getbuf(ex2, ex2, req|bits, + sliced=True) + self.verify_getbuf(ex2, m2, req|bits, + sliced=True) + + items = [1,2,3,4,5,6,7,8,9,10,11,12] + + # ND_GETBUF_FAIL + ex = ndarray(items, shape=[12], flags=ND_GETBUF_FAIL) + self.assertRaises(BufferError, ndarray, ex) + + # Request complex structure from a simple exporter. In this + # particular case the test object is not PEP-3118 compliant. + base = ndarray([9], [1]) + ex = ndarray(base, getbuf=PyBUF_SIMPLE) + self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_WRITABLE) + self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_ND) + self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_STRIDES) + self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_C_CONTIGUOUS) + self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_F_CONTIGUOUS) + self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_ANY_CONTIGUOUS) + nd = ndarray(ex, getbuf=PyBUF_SIMPLE) + + # Issue #22445: New precise contiguity definition. + for shape in [1,12,1], [7,0,7]: + for order in 0, ND_FORTRAN: + ex = ndarray(items, shape=shape, flags=order|ND_WRITABLE) + self.assertTrue(is_contiguous(ex, 'F')) + self.assertTrue(is_contiguous(ex, 'C')) + + for flags in requests: + nd = ndarray(ex, getbuf=flags) + self.assertTrue(is_contiguous(nd, 'F')) + self.assertTrue(is_contiguous(nd, 'C')) + + def test_ndarray_exceptions(self): + nd = ndarray([9], [1]) + ndm = ndarray([9], [1], flags=ND_VAREXPORT) + + # Initialization of a new ndarray or mutation of an existing array. + for c in (ndarray, nd.push, ndm.push): + # Invalid types. + self.assertRaises(TypeError, c, {1,2,3}) + self.assertRaises(TypeError, c, [1,2,'3']) + self.assertRaises(TypeError, c, [1,2,(3,4)]) + self.assertRaises(TypeError, c, [1,2,3], shape={3}) + self.assertRaises(TypeError, c, [1,2,3], shape=[3], strides={1}) + self.assertRaises(TypeError, c, [1,2,3], shape=[3], offset=[]) + self.assertRaises(TypeError, c, [1], shape=[1], format={}) + self.assertRaises(TypeError, c, [1], shape=[1], flags={}) + self.assertRaises(TypeError, c, [1], shape=[1], getbuf={}) + + # ND_FORTRAN flag is only valid without strides. + self.assertRaises(TypeError, c, [1], shape=[1], strides=[1], + flags=ND_FORTRAN) + + # ND_PIL flag is only valid with ndim > 0. + self.assertRaises(TypeError, c, [1], shape=[], flags=ND_PIL) + + # Invalid items. + self.assertRaises(ValueError, c, [], shape=[1]) + self.assertRaises(ValueError, c, ['XXX'], shape=[1], format="L") + # Invalid combination of items and format. + self.assertRaises(struct.error, c, [1000], shape=[1], format="B") + self.assertRaises(ValueError, c, [1,(2,3)], shape=[2], format="B") + self.assertRaises(ValueError, c, [1,2,3], shape=[3], format="QL") + + # Invalid ndim. + n = ND_MAX_NDIM+1 + self.assertRaises(ValueError, c, [1]*n, shape=[1]*n) + + # Invalid shape. + self.assertRaises(ValueError, c, [1], shape=[-1]) + self.assertRaises(ValueError, c, [1,2,3], shape=['3']) + self.assertRaises(OverflowError, c, [1], shape=[2**128]) + # prod(shape) * itemsize != len(items) + self.assertRaises(ValueError, c, [1,2,3,4,5], shape=[2,2], offset=3) + + # Invalid strides. + self.assertRaises(ValueError, c, [1,2,3], shape=[3], strides=['1']) + self.assertRaises(OverflowError, c, [1], shape=[1], + strides=[2**128]) + + # Invalid combination of strides and shape. + self.assertRaises(ValueError, c, [1,2], shape=[2,1], strides=[1]) + # Invalid combination of strides and format. + self.assertRaises(ValueError, c, [1,2,3,4], shape=[2], strides=[3], + format="L") + + # Invalid offset. + self.assertRaises(ValueError, c, [1,2,3], shape=[3], offset=4) + self.assertRaises(ValueError, c, [1,2,3], shape=[1], offset=3, + format="L") + + # Invalid format. + self.assertRaises(ValueError, c, [1,2,3], shape=[3], format="") + self.assertRaises(struct.error, c, [(1,2,3)], shape=[1], + format="@#$") + + # Striding out of the memory bounds. + items = [1,2,3,4,5,6,7,8,9,10] + self.assertRaises(ValueError, c, items, shape=[2,3], + strides=[-3, -2], offset=5) + + # Constructing consumer: format argument invalid. + self.assertRaises(TypeError, c, bytearray(), format="Q") + + # Constructing original base object: getbuf argument invalid. + self.assertRaises(TypeError, c, [1], shape=[1], getbuf=PyBUF_FULL) + + # Shape argument is mandatory for original base objects. + self.assertRaises(TypeError, c, [1]) + + + # PyBUF_WRITABLE request to read-only provider. + self.assertRaises(BufferError, ndarray, b'123', getbuf=PyBUF_WRITABLE) + + # ND_VAREXPORT can only be specified during construction. + nd = ndarray([9], [1], flags=ND_VAREXPORT) + self.assertRaises(ValueError, nd.push, [1], [1], flags=ND_VAREXPORT) + + # Invalid operation for consumers: push/pop + nd = ndarray(b'123') + self.assertRaises(BufferError, nd.push, [1], [1]) + self.assertRaises(BufferError, nd.pop) + + # ND_VAREXPORT not set: push/pop fail with exported buffers + nd = ndarray([9], [1]) + nd.push([1], [1]) + m = memoryview(nd) + self.assertRaises(BufferError, nd.push, [1], [1]) + self.assertRaises(BufferError, nd.pop) + m.release() + nd.pop() + + # Single remaining buffer: pop fails + self.assertRaises(BufferError, nd.pop) + del nd + + # get_pointer() + self.assertRaises(TypeError, get_pointer, {}, [1,2,3]) + self.assertRaises(TypeError, get_pointer, b'123', {}) + + nd = ndarray(list(range(100)), shape=[1]*100) + self.assertRaises(ValueError, get_pointer, nd, [5]) + + nd = ndarray(list(range(12)), shape=[3,4]) + self.assertRaises(ValueError, get_pointer, nd, [2,3,4]) + self.assertRaises(ValueError, get_pointer, nd, [3,3]) + self.assertRaises(ValueError, get_pointer, nd, [-3,3]) + self.assertRaises(OverflowError, get_pointer, nd, [1<<64,3]) + + # tolist() needs format + ex = ndarray([1,2,3], shape=[3], format='L') + nd = ndarray(ex, getbuf=PyBUF_SIMPLE) + self.assertRaises(ValueError, nd.tolist) + + # memoryview_from_buffer() + ex1 = ndarray([1,2,3], shape=[3], format='L') + ex2 = ndarray(ex1) + nd = ndarray(ex2) + self.assertRaises(TypeError, nd.memoryview_from_buffer) + + nd = ndarray([(1,)*200], shape=[1], format='L'*200) + self.assertRaises(TypeError, nd.memoryview_from_buffer) + + n = ND_MAX_NDIM + nd = ndarray(list(range(n)), shape=[1]*n) + self.assertRaises(ValueError, nd.memoryview_from_buffer) + + # get_contiguous() + nd = ndarray([1], shape=[1]) + self.assertRaises(TypeError, get_contiguous, 1, 2, 3, 4, 5) + self.assertRaises(TypeError, get_contiguous, nd, "xyz", 'C') + self.assertRaises(OverflowError, get_contiguous, nd, 2**64, 'C') + self.assertRaises(TypeError, get_contiguous, nd, PyBUF_READ, 961) + self.assertRaises(UnicodeEncodeError, get_contiguous, nd, PyBUF_READ, + '\u2007') + self.assertRaises(ValueError, get_contiguous, nd, PyBUF_READ, 'Z') + self.assertRaises(ValueError, get_contiguous, nd, 255, 'A') + + # cmp_contig() + nd = ndarray([1], shape=[1]) + self.assertRaises(TypeError, cmp_contig, 1, 2, 3, 4, 5) + self.assertRaises(TypeError, cmp_contig, {}, nd) + self.assertRaises(TypeError, cmp_contig, nd, {}) + + # is_contiguous() + nd = ndarray([1], shape=[1]) + self.assertRaises(TypeError, is_contiguous, 1, 2, 3, 4, 5) + self.assertRaises(TypeError, is_contiguous, {}, 'A') + self.assertRaises(TypeError, is_contiguous, nd, 201) + + def test_ndarray_linked_list(self): + for perm in permutations(range(5)): + m = [0]*5 + nd = ndarray([1,2,3], shape=[3], flags=ND_VAREXPORT) + m[0] = memoryview(nd) + + for i in range(1, 5): + nd.push([1,2,3], shape=[3]) + m[i] = memoryview(nd) + + for i in range(5): + m[perm[i]].release() + + self.assertRaises(BufferError, nd.pop) + del nd + + def test_ndarray_format_scalar(self): + # ndim = 0: scalar + for fmt, scalar, _ in iter_format(0): + itemsize = struct.calcsize(fmt) + nd = ndarray(scalar, shape=(), format=fmt) + self.verify(nd, obj=None, + itemsize=itemsize, fmt=fmt, readonly=True, + ndim=0, shape=(), strides=(), + lst=scalar) + + def test_ndarray_format_shape(self): + # ndim = 1, shape = [n] + nitems = randrange(1, 10) + for fmt, items, _ in iter_format(nitems): + itemsize = struct.calcsize(fmt) + for flags in (0, ND_PIL): + nd = ndarray(items, shape=[nitems], format=fmt, flags=flags) + self.verify(nd, obj=None, + itemsize=itemsize, fmt=fmt, readonly=True, + ndim=1, shape=(nitems,), strides=(itemsize,), + lst=items) + + def test_ndarray_format_strides(self): + # ndim = 1, strides + nitems = randrange(1, 30) + for fmt, items, _ in iter_format(nitems): + itemsize = struct.calcsize(fmt) + for step in range(-5, 5): + if step == 0: + continue + + shape = [len(items[::step])] + strides = [step*itemsize] + offset = itemsize*(nitems-1) if step < 0 else 0 + + for flags in (0, ND_PIL): + nd = ndarray(items, shape=shape, strides=strides, + format=fmt, offset=offset, flags=flags) + self.verify(nd, obj=None, + itemsize=itemsize, fmt=fmt, readonly=True, + ndim=1, shape=shape, strides=strides, + lst=items[::step]) + + def test_ndarray_fortran(self): + items = [1,2,3,4,5,6,7,8,9,10,11,12] + ex = ndarray(items, shape=(3, 4), strides=(1, 3)) + nd = ndarray(ex, getbuf=PyBUF_F_CONTIGUOUS|PyBUF_FORMAT) + self.assertEqual(nd.tolist(), farray(items, (3, 4))) + + def test_ndarray_multidim(self): + for ndim in range(5): + shape_t = [randrange(2, 10) for _ in range(ndim)] + nitems = prod(shape_t) + for shape in permutations(shape_t): + + fmt, items, _ = randitems(nitems) + itemsize = struct.calcsize(fmt) + + for flags in (0, ND_PIL): + if ndim == 0 and flags == ND_PIL: + continue + + # C array + nd = ndarray(items, shape=shape, format=fmt, flags=flags) + + strides = strides_from_shape(ndim, shape, itemsize, 'C') + lst = carray(items, shape) + self.verify(nd, obj=None, + itemsize=itemsize, fmt=fmt, readonly=True, + ndim=ndim, shape=shape, strides=strides, + lst=lst) + + if is_memoryview_format(fmt): + # memoryview: reconstruct strides + ex = ndarray(items, shape=shape, format=fmt) + nd = ndarray(ex, getbuf=PyBUF_CONTIG_RO|PyBUF_FORMAT) + self.assertTrue(nd.strides == ()) + mv = nd.memoryview_from_buffer() + self.verify(mv, obj=None, + itemsize=itemsize, fmt=fmt, readonly=True, + ndim=ndim, shape=shape, strides=strides, + lst=lst) + + # Fortran array + nd = ndarray(items, shape=shape, format=fmt, + flags=flags|ND_FORTRAN) + + strides = strides_from_shape(ndim, shape, itemsize, 'F') + lst = farray(items, shape) + self.verify(nd, obj=None, + itemsize=itemsize, fmt=fmt, readonly=True, + ndim=ndim, shape=shape, strides=strides, + lst=lst) + + def test_ndarray_index_invalid(self): + # not writable + nd = ndarray([1], shape=[1]) + self.assertRaises(TypeError, nd.__setitem__, 1, 8) + mv = memoryview(nd) + self.assertEqual(mv, nd) + self.assertRaises(TypeError, mv.__setitem__, 1, 8) + + # cannot be deleted + nd = ndarray([1], shape=[1], flags=ND_WRITABLE) + self.assertRaises(TypeError, nd.__delitem__, 1) + mv = memoryview(nd) + self.assertEqual(mv, nd) + self.assertRaises(TypeError, mv.__delitem__, 1) + + # overflow + nd = ndarray([1], shape=[1], flags=ND_WRITABLE) + self.assertRaises(OverflowError, nd.__getitem__, 1<<64) + self.assertRaises(OverflowError, nd.__setitem__, 1<<64, 8) + mv = memoryview(nd) + self.assertEqual(mv, nd) + self.assertRaises(IndexError, mv.__getitem__, 1<<64) + self.assertRaises(IndexError, mv.__setitem__, 1<<64, 8) + + # format + items = [1,2,3,4,5,6,7,8] + nd = ndarray(items, shape=[len(items)], format="B", flags=ND_WRITABLE) + self.assertRaises(struct.error, nd.__setitem__, 2, 300) + self.assertRaises(ValueError, nd.__setitem__, 1, (100, 200)) + mv = memoryview(nd) + self.assertEqual(mv, nd) + self.assertRaises(ValueError, mv.__setitem__, 2, 300) + self.assertRaises(TypeError, mv.__setitem__, 1, (100, 200)) + + items = [(1,2), (3,4), (5,6)] + nd = ndarray(items, shape=[len(items)], format="LQ", flags=ND_WRITABLE) + self.assertRaises(ValueError, nd.__setitem__, 2, 300) + self.assertRaises(struct.error, nd.__setitem__, 1, (b'\x001', 200)) + + def test_ndarray_index_scalar(self): + # scalar + nd = ndarray(1, shape=(), flags=ND_WRITABLE) + mv = memoryview(nd) + self.assertEqual(mv, nd) + + x = nd[()]; self.assertEqual(x, 1) + x = nd[...]; self.assertEqual(x.tolist(), nd.tolist()) + + x = mv[()]; self.assertEqual(x, 1) + x = mv[...]; self.assertEqual(x.tolist(), nd.tolist()) + + self.assertRaises(TypeError, nd.__getitem__, 0) + self.assertRaises(TypeError, mv.__getitem__, 0) + self.assertRaises(TypeError, nd.__setitem__, 0, 8) + self.assertRaises(TypeError, mv.__setitem__, 0, 8) + + self.assertEqual(nd.tolist(), 1) + self.assertEqual(mv.tolist(), 1) + + nd[()] = 9; self.assertEqual(nd.tolist(), 9) + mv[()] = 9; self.assertEqual(mv.tolist(), 9) + + nd[...] = 5; self.assertEqual(nd.tolist(), 5) + mv[...] = 5; self.assertEqual(mv.tolist(), 5) + + def test_ndarray_index_null_strides(self): + ex = ndarray(list(range(2*4)), shape=[2, 4], flags=ND_WRITABLE) + nd = ndarray(ex, getbuf=PyBUF_CONTIG) + + # Sub-views are only possible for full exporters. + self.assertRaises(BufferError, nd.__getitem__, 1) + # Same for slices. + self.assertRaises(BufferError, nd.__getitem__, slice(3,5,1)) + + def test_ndarray_index_getitem_single(self): + # getitem + for fmt, items, _ in iter_format(5): + nd = ndarray(items, shape=[5], format=fmt) + for i in range(-5, 5): + self.assertEqual(nd[i], items[i]) + + self.assertRaises(IndexError, nd.__getitem__, -6) + self.assertRaises(IndexError, nd.__getitem__, 5) + + if is_memoryview_format(fmt): + mv = memoryview(nd) + self.assertEqual(mv, nd) + for i in range(-5, 5): + self.assertEqual(mv[i], items[i]) + + self.assertRaises(IndexError, mv.__getitem__, -6) + self.assertRaises(IndexError, mv.__getitem__, 5) + + # getitem with null strides + for fmt, items, _ in iter_format(5): + ex = ndarray(items, shape=[5], flags=ND_WRITABLE, format=fmt) + nd = ndarray(ex, getbuf=PyBUF_CONTIG|PyBUF_FORMAT) + + for i in range(-5, 5): + self.assertEqual(nd[i], items[i]) + + if is_memoryview_format(fmt): + mv = nd.memoryview_from_buffer() + self.assertIs(mv.__eq__(nd), NotImplemented) + for i in range(-5, 5): + self.assertEqual(mv[i], items[i]) + + # getitem with null format + items = [1,2,3,4,5] + ex = ndarray(items, shape=[5]) + nd = ndarray(ex, getbuf=PyBUF_CONTIG_RO) + for i in range(-5, 5): + self.assertEqual(nd[i], items[i]) + + # getitem with null shape/strides/format + items = [1,2,3,4,5] + ex = ndarray(items, shape=[5]) + nd = ndarray(ex, getbuf=PyBUF_SIMPLE) + + for i in range(-5, 5): + self.assertEqual(nd[i], items[i]) + + def test_ndarray_index_setitem_single(self): + # assign single value + for fmt, items, single_item in iter_format(5): + nd = ndarray(items, shape=[5], format=fmt, flags=ND_WRITABLE) + for i in range(5): + items[i] = single_item + nd[i] = single_item + self.assertEqual(nd.tolist(), items) + + self.assertRaises(IndexError, nd.__setitem__, -6, single_item) + self.assertRaises(IndexError, nd.__setitem__, 5, single_item) + + if not is_memoryview_format(fmt): + continue + + nd = ndarray(items, shape=[5], format=fmt, flags=ND_WRITABLE) + mv = memoryview(nd) + self.assertEqual(mv, nd) + for i in range(5): + items[i] = single_item + mv[i] = single_item + self.assertEqual(mv.tolist(), items) + + self.assertRaises(IndexError, mv.__setitem__, -6, single_item) + self.assertRaises(IndexError, mv.__setitem__, 5, single_item) + + + # assign single value: lobject = robject + for fmt, items, single_item in iter_format(5): + nd = ndarray(items, shape=[5], format=fmt, flags=ND_WRITABLE) + for i in range(-5, 4): + items[i] = items[i+1] + nd[i] = nd[i+1] + self.assertEqual(nd.tolist(), items) + + if not is_memoryview_format(fmt): + continue + + nd = ndarray(items, shape=[5], format=fmt, flags=ND_WRITABLE) + mv = memoryview(nd) + self.assertEqual(mv, nd) + for i in range(-5, 4): + items[i] = items[i+1] + mv[i] = mv[i+1] + self.assertEqual(mv.tolist(), items) + + def test_ndarray_index_getitem_multidim(self): + shape_t = (2, 3, 5) + nitems = prod(shape_t) + for shape in permutations(shape_t): + + fmt, items, _ = randitems(nitems) + + for flags in (0, ND_PIL): + # C array + nd = ndarray(items, shape=shape, format=fmt, flags=flags) + lst = carray(items, shape) + + for i in range(-shape[0], shape[0]): + self.assertEqual(lst[i], nd[i].tolist()) + for j in range(-shape[1], shape[1]): + self.assertEqual(lst[i][j], nd[i][j].tolist()) + for k in range(-shape[2], shape[2]): + self.assertEqual(lst[i][j][k], nd[i][j][k]) + + # Fortran array + nd = ndarray(items, shape=shape, format=fmt, + flags=flags|ND_FORTRAN) + lst = farray(items, shape) + + for i in range(-shape[0], shape[0]): + self.assertEqual(lst[i], nd[i].tolist()) + for j in range(-shape[1], shape[1]): + self.assertEqual(lst[i][j], nd[i][j].tolist()) + for k in range(shape[2], shape[2]): + self.assertEqual(lst[i][j][k], nd[i][j][k]) + + def test_ndarray_sequence(self): + nd = ndarray(1, shape=()) + self.assertRaises(TypeError, eval, "1 in nd", locals()) + mv = memoryview(nd) + self.assertEqual(mv, nd) + self.assertRaises(TypeError, eval, "1 in mv", locals()) + + for fmt, items, _ in iter_format(5): + nd = ndarray(items, shape=[5], format=fmt) + for i, v in enumerate(nd): + self.assertEqual(v, items[i]) + self.assertTrue(v in nd) + + if is_memoryview_format(fmt): + mv = memoryview(nd) + for i, v in enumerate(mv): + self.assertEqual(v, items[i]) + self.assertTrue(v in mv) + + def test_ndarray_slice_invalid(self): + items = [1,2,3,4,5,6,7,8] + + # rvalue is not an exporter + xl = ndarray(items, shape=[8], flags=ND_WRITABLE) + ml = memoryview(xl) + self.assertRaises(TypeError, xl.__setitem__, slice(0,8,1), items) + self.assertRaises(TypeError, ml.__setitem__, slice(0,8,1), items) + + # rvalue is not a full exporter + xl = ndarray(items, shape=[8], flags=ND_WRITABLE) + ex = ndarray(items, shape=[8], flags=ND_WRITABLE) + xr = ndarray(ex, getbuf=PyBUF_ND) + self.assertRaises(BufferError, xl.__setitem__, slice(0,8,1), xr) + + # zero step + nd = ndarray(items, shape=[8], format="L", flags=ND_WRITABLE) + mv = memoryview(nd) + self.assertRaises(ValueError, nd.__getitem__, slice(0,1,0)) + self.assertRaises(ValueError, mv.__getitem__, slice(0,1,0)) + + nd = ndarray(items, shape=[2,4], format="L", flags=ND_WRITABLE) + mv = memoryview(nd) + + self.assertRaises(ValueError, nd.__getitem__, + (slice(0,1,1), slice(0,1,0))) + self.assertRaises(ValueError, nd.__getitem__, + (slice(0,1,0), slice(0,1,1))) + self.assertRaises(TypeError, nd.__getitem__, "@%$") + self.assertRaises(TypeError, nd.__getitem__, ("@%$", slice(0,1,1))) + self.assertRaises(TypeError, nd.__getitem__, (slice(0,1,1), {})) + + # memoryview: not implemented + self.assertRaises(NotImplementedError, mv.__getitem__, + (slice(0,1,1), slice(0,1,0))) + self.assertRaises(TypeError, mv.__getitem__, "@%$") + + # differing format + xl = ndarray(items, shape=[8], format="B", flags=ND_WRITABLE) + xr = ndarray(items, shape=[8], format="b") + ml = memoryview(xl) + mr = memoryview(xr) + self.assertRaises(ValueError, xl.__setitem__, slice(0,1,1), xr[7:8]) + self.assertEqual(xl.tolist(), items) + self.assertRaises(ValueError, ml.__setitem__, slice(0,1,1), mr[7:8]) + self.assertEqual(ml.tolist(), items) + + # differing itemsize + xl = ndarray(items, shape=[8], format="B", flags=ND_WRITABLE) + yr = ndarray(items, shape=[8], format="L") + ml = memoryview(xl) + mr = memoryview(xr) + self.assertRaises(ValueError, xl.__setitem__, slice(0,1,1), xr[7:8]) + self.assertEqual(xl.tolist(), items) + self.assertRaises(ValueError, ml.__setitem__, slice(0,1,1), mr[7:8]) + self.assertEqual(ml.tolist(), items) + + # differing ndim + xl = ndarray(items, shape=[2, 4], format="b", flags=ND_WRITABLE) + xr = ndarray(items, shape=[8], format="b") + ml = memoryview(xl) + mr = memoryview(xr) + self.assertRaises(ValueError, xl.__setitem__, slice(0,1,1), xr[7:8]) + self.assertEqual(xl.tolist(), [[1,2,3,4], [5,6,7,8]]) + self.assertRaises(NotImplementedError, ml.__setitem__, slice(0,1,1), + mr[7:8]) + + # differing shape + xl = ndarray(items, shape=[8], format="b", flags=ND_WRITABLE) + xr = ndarray(items, shape=[8], format="b") + ml = memoryview(xl) + mr = memoryview(xr) + self.assertRaises(ValueError, xl.__setitem__, slice(0,2,1), xr[7:8]) + self.assertEqual(xl.tolist(), items) + self.assertRaises(ValueError, ml.__setitem__, slice(0,2,1), mr[7:8]) + self.assertEqual(ml.tolist(), items) + + # _testbuffer.c module functions + self.assertRaises(TypeError, slice_indices, slice(0,1,2), {}) + self.assertRaises(TypeError, slice_indices, "###########", 1) + self.assertRaises(ValueError, slice_indices, slice(0,1,0), 4) + + x = ndarray(items, shape=[8], format="b", flags=ND_PIL) + self.assertRaises(TypeError, x.add_suboffsets) + + ex = ndarray(items, shape=[8], format="B") + x = ndarray(ex, getbuf=PyBUF_SIMPLE) + self.assertRaises(TypeError, x.add_suboffsets) + + def test_ndarray_slice_zero_shape(self): + items = [1,2,3,4,5,6,7,8,9,10,11,12] + + x = ndarray(items, shape=[12], format="L", flags=ND_WRITABLE) + y = ndarray(items, shape=[12], format="L") + x[4:4] = y[9:9] + self.assertEqual(x.tolist(), items) + + ml = memoryview(x) + mr = memoryview(y) + self.assertEqual(ml, x) + self.assertEqual(ml, y) + ml[4:4] = mr[9:9] + self.assertEqual(ml.tolist(), items) + + x = ndarray(items, shape=[3, 4], format="L", flags=ND_WRITABLE) + y = ndarray(items, shape=[4, 3], format="L") + x[1:2, 2:2] = y[1:2, 3:3] + self.assertEqual(x.tolist(), carray(items, [3, 4])) + + def test_ndarray_slice_multidim(self): + shape_t = (2, 3, 5) + ndim = len(shape_t) + nitems = prod(shape_t) + for shape in permutations(shape_t): + + fmt, items, _ = randitems(nitems) + itemsize = struct.calcsize(fmt) + + for flags in (0, ND_PIL): + nd = ndarray(items, shape=shape, format=fmt, flags=flags) + lst = carray(items, shape) + + for slices in rslices_ndim(ndim, shape): + + listerr = None + try: + sliced = multislice(lst, slices) + except Exception as e: + listerr = e.__class__ + + nderr = None + try: + ndsliced = nd[slices] + except Exception as e: + nderr = e.__class__ + + if nderr or listerr: + self.assertIs(nderr, listerr) + else: + self.assertEqual(ndsliced.tolist(), sliced) + + def test_ndarray_slice_redundant_suboffsets(self): + shape_t = (2, 3, 5, 2) + ndim = len(shape_t) + nitems = prod(shape_t) + for shape in permutations(shape_t): + + fmt, items, _ = randitems(nitems) + itemsize = struct.calcsize(fmt) + + nd = ndarray(items, shape=shape, format=fmt) + nd.add_suboffsets() + ex = ndarray(items, shape=shape, format=fmt) + ex.add_suboffsets() + mv = memoryview(ex) + lst = carray(items, shape) + + for slices in rslices_ndim(ndim, shape): + + listerr = None + try: + sliced = multislice(lst, slices) + except Exception as e: + listerr = e.__class__ + + nderr = None + try: + ndsliced = nd[slices] + except Exception as e: + nderr = e.__class__ + + if nderr or listerr: + self.assertIs(nderr, listerr) + else: + self.assertEqual(ndsliced.tolist(), sliced) + + def test_ndarray_slice_assign_single(self): + for fmt, items, _ in iter_format(5): + for lslice in genslices(5): + for rslice in genslices(5): + for flags in (0, ND_PIL): + + f = flags|ND_WRITABLE + nd = ndarray(items, shape=[5], format=fmt, flags=f) + ex = ndarray(items, shape=[5], format=fmt, flags=f) + mv = memoryview(ex) + + lsterr = None + diff_structure = None + lst = items[:] + try: + lval = lst[lslice] + rval = lst[rslice] + lst[lslice] = lst[rslice] + diff_structure = len(lval) != len(rval) + except Exception as e: + lsterr = e.__class__ + + nderr = None + try: + nd[lslice] = nd[rslice] + except Exception as e: + nderr = e.__class__ + + if diff_structure: # ndarray cannot change shape + self.assertIs(nderr, ValueError) + else: + self.assertEqual(nd.tolist(), lst) + self.assertIs(nderr, lsterr) + + if not is_memoryview_format(fmt): + continue + + mverr = None + try: + mv[lslice] = mv[rslice] + except Exception as e: + mverr = e.__class__ + + if diff_structure: # memoryview cannot change shape + self.assertIs(mverr, ValueError) + else: + self.assertEqual(mv.tolist(), lst) + self.assertEqual(mv, nd) + self.assertIs(mverr, lsterr) + self.verify(mv, obj=ex, + itemsize=nd.itemsize, fmt=fmt, readonly=False, + ndim=nd.ndim, shape=nd.shape, strides=nd.strides, + lst=nd.tolist()) + + def test_ndarray_slice_assign_multidim(self): + shape_t = (2, 3, 5) + ndim = len(shape_t) + nitems = prod(shape_t) + for shape in permutations(shape_t): + + fmt, items, _ = randitems(nitems) + + for flags in (0, ND_PIL): + for _ in range(ITERATIONS): + lslices, rslices = randslice_from_shape(ndim, shape) + + nd = ndarray(items, shape=shape, format=fmt, + flags=flags|ND_WRITABLE) + lst = carray(items, shape) + + listerr = None + try: + result = multislice_assign(lst, lst, lslices, rslices) + except Exception as e: + listerr = e.__class__ + + nderr = None + try: + nd[lslices] = nd[rslices] + except Exception as e: + nderr = e.__class__ + + if nderr or listerr: + self.assertIs(nderr, listerr) + else: + self.assertEqual(nd.tolist(), result) + + def test_ndarray_random(self): + # construction of valid arrays + for _ in range(ITERATIONS): + for fmt in fmtdict['@']: + itemsize = struct.calcsize(fmt) + + t = rand_structure(itemsize, True, maxdim=MAXDIM, + maxshape=MAXSHAPE) + self.assertTrue(verify_structure(*t)) + items = randitems_from_structure(fmt, t) + + x = ndarray_from_structure(items, fmt, t) + xlist = x.tolist() + + mv = memoryview(x) + if is_memoryview_format(fmt): + mvlist = mv.tolist() + self.assertEqual(mvlist, xlist) + + if t[2] > 0: + # ndim > 0: test against suboffsets representation. + y = ndarray_from_structure(items, fmt, t, flags=ND_PIL) + ylist = y.tolist() + self.assertEqual(xlist, ylist) + + mv = memoryview(y) + if is_memoryview_format(fmt): + self.assertEqual(mv, y) + mvlist = mv.tolist() + self.assertEqual(mvlist, ylist) + + if numpy_array: + shape = t[3] + if 0 in shape: + continue # http://projects.scipy.org/numpy/ticket/1910 + z = numpy_array_from_structure(items, fmt, t) + self.verify(x, obj=None, + itemsize=z.itemsize, fmt=fmt, readonly=False, + ndim=z.ndim, shape=z.shape, strides=z.strides, + lst=z.tolist()) + + def test_ndarray_random_invalid(self): + # exceptions during construction of invalid arrays + for _ in range(ITERATIONS): + for fmt in fmtdict['@']: + itemsize = struct.calcsize(fmt) + + t = rand_structure(itemsize, False, maxdim=MAXDIM, + maxshape=MAXSHAPE) + self.assertFalse(verify_structure(*t)) + items = randitems_from_structure(fmt, t) + + nderr = False + try: + x = ndarray_from_structure(items, fmt, t) + except Exception as e: + nderr = e.__class__ + self.assertTrue(nderr) + + if numpy_array: + numpy_err = False + try: + y = numpy_array_from_structure(items, fmt, t) + except Exception as e: + numpy_err = e.__class__ + + if 0: # http://projects.scipy.org/numpy/ticket/1910 + self.assertTrue(numpy_err) + + def test_ndarray_random_slice_assign(self): + # valid slice assignments + for _ in range(ITERATIONS): + for fmt in fmtdict['@']: + itemsize = struct.calcsize(fmt) + + lshape, rshape, lslices, rslices = \ + rand_aligned_slices(maxdim=MAXDIM, maxshape=MAXSHAPE) + tl = rand_structure(itemsize, True, shape=lshape) + tr = rand_structure(itemsize, True, shape=rshape) + self.assertTrue(verify_structure(*tl)) + self.assertTrue(verify_structure(*tr)) + litems = randitems_from_structure(fmt, tl) + ritems = randitems_from_structure(fmt, tr) + + xl = ndarray_from_structure(litems, fmt, tl) + xr = ndarray_from_structure(ritems, fmt, tr) + xl[lslices] = xr[rslices] + xllist = xl.tolist() + xrlist = xr.tolist() + + ml = memoryview(xl) + mr = memoryview(xr) + self.assertEqual(ml.tolist(), xllist) + self.assertEqual(mr.tolist(), xrlist) + + if tl[2] > 0 and tr[2] > 0: + # ndim > 0: test against suboffsets representation. + yl = ndarray_from_structure(litems, fmt, tl, flags=ND_PIL) + yr = ndarray_from_structure(ritems, fmt, tr, flags=ND_PIL) + yl[lslices] = yr[rslices] + yllist = yl.tolist() + yrlist = yr.tolist() + self.assertEqual(xllist, yllist) + self.assertEqual(xrlist, yrlist) + + ml = memoryview(yl) + mr = memoryview(yr) + self.assertEqual(ml.tolist(), yllist) + self.assertEqual(mr.tolist(), yrlist) + + if numpy_array: + if 0 in lshape or 0 in rshape: + continue # http://projects.scipy.org/numpy/ticket/1910 + + zl = numpy_array_from_structure(litems, fmt, tl) + zr = numpy_array_from_structure(ritems, fmt, tr) + zl[lslices] = zr[rslices] + + if not is_overlapping(tl) and not is_overlapping(tr): + # Slice assignment of overlapping structures + # is undefined in NumPy. + self.verify(xl, obj=None, + itemsize=zl.itemsize, fmt=fmt, readonly=False, + ndim=zl.ndim, shape=zl.shape, + strides=zl.strides, lst=zl.tolist()) + + self.verify(xr, obj=None, + itemsize=zr.itemsize, fmt=fmt, readonly=False, + ndim=zr.ndim, shape=zr.shape, + strides=zr.strides, lst=zr.tolist()) + + def test_ndarray_re_export(self): + items = [1,2,3,4,5,6,7,8,9,10,11,12] + + nd = ndarray(items, shape=[3,4], flags=ND_PIL) + ex = ndarray(nd) + + self.assertTrue(ex.flags & ND_PIL) + self.assertIs(ex.obj, nd) + self.assertEqual(ex.suboffsets, (0, -1)) + self.assertFalse(ex.c_contiguous) + self.assertFalse(ex.f_contiguous) + self.assertFalse(ex.contiguous) + + def test_ndarray_zero_shape(self): + # zeros in shape + for flags in (0, ND_PIL): + nd = ndarray([1,2,3], shape=[0], flags=flags) + mv = memoryview(nd) + self.assertEqual(mv, nd) + self.assertEqual(nd.tolist(), []) + self.assertEqual(mv.tolist(), []) + + nd = ndarray([1,2,3], shape=[0,3,3], flags=flags) + self.assertEqual(nd.tolist(), []) + + nd = ndarray([1,2,3], shape=[3,0,3], flags=flags) + self.assertEqual(nd.tolist(), [[], [], []]) + + nd = ndarray([1,2,3], shape=[3,3,0], flags=flags) + self.assertEqual(nd.tolist(), + [[[], [], []], [[], [], []], [[], [], []]]) + + def test_ndarray_zero_strides(self): + # zero strides + for flags in (0, ND_PIL): + nd = ndarray([1], shape=[5], strides=[0], flags=flags) + mv = memoryview(nd) + self.assertEqual(mv, nd) + self.assertEqual(nd.tolist(), [1, 1, 1, 1, 1]) + self.assertEqual(mv.tolist(), [1, 1, 1, 1, 1]) + + def test_ndarray_offset(self): + nd = ndarray(list(range(20)), shape=[3], offset=7) + self.assertEqual(nd.offset, 7) + self.assertEqual(nd.tolist(), [7,8,9]) + + def test_ndarray_memoryview_from_buffer(self): + for flags in (0, ND_PIL): + nd = ndarray(list(range(3)), shape=[3], flags=flags) + m = nd.memoryview_from_buffer() + self.assertEqual(m, nd) + + def test_ndarray_get_pointer(self): + for flags in (0, ND_PIL): + nd = ndarray(list(range(3)), shape=[3], flags=flags) + for i in range(3): + self.assertEqual(nd[i], get_pointer(nd, [i])) + + def test_ndarray_tolist_null_strides(self): + ex = ndarray(list(range(20)), shape=[2,2,5]) + + nd = ndarray(ex, getbuf=PyBUF_ND|PyBUF_FORMAT) + self.assertEqual(nd.tolist(), ex.tolist()) + + m = memoryview(ex) + self.assertEqual(m.tolist(), ex.tolist()) + + def test_ndarray_cmp_contig(self): + + self.assertFalse(cmp_contig(b"123", b"456")) + + x = ndarray(list(range(12)), shape=[3,4]) + y = ndarray(list(range(12)), shape=[4,3]) + self.assertFalse(cmp_contig(x, y)) + + x = ndarray([1], shape=[1], format="B") + self.assertTrue(cmp_contig(x, b'\x01')) + self.assertTrue(cmp_contig(b'\x01', x)) + + def test_ndarray_hash(self): + + a = array.array('L', [1,2,3]) + nd = ndarray(a) + self.assertRaises(ValueError, hash, nd) + + # one-dimensional + b = bytes(list(range(12))) + + nd = ndarray(list(range(12)), shape=[12]) + self.assertEqual(hash(nd), hash(b)) + + # C-contiguous + nd = ndarray(list(range(12)), shape=[3,4]) + self.assertEqual(hash(nd), hash(b)) + + nd = ndarray(list(range(12)), shape=[3,2,2]) + self.assertEqual(hash(nd), hash(b)) + + # Fortran contiguous + b = bytes(transpose(list(range(12)), shape=[4,3])) + nd = ndarray(list(range(12)), shape=[3,4], flags=ND_FORTRAN) + self.assertEqual(hash(nd), hash(b)) + + b = bytes(transpose(list(range(12)), shape=[2,3,2])) + nd = ndarray(list(range(12)), shape=[2,3,2], flags=ND_FORTRAN) + self.assertEqual(hash(nd), hash(b)) + + # suboffsets + b = bytes(list(range(12))) + nd = ndarray(list(range(12)), shape=[2,2,3], flags=ND_PIL) + self.assertEqual(hash(nd), hash(b)) + + # non-byte formats + nd = ndarray(list(range(12)), shape=[2,2,3], format='L') + self.assertEqual(hash(nd), hash(nd.tobytes())) + + def test_py_buffer_to_contiguous(self): + + # The requests are used in _testbuffer.c:py_buffer_to_contiguous + # to generate buffers without full information for testing. + requests = ( + # distinct flags + PyBUF_INDIRECT, PyBUF_STRIDES, PyBUF_ND, PyBUF_SIMPLE, + # compound requests + PyBUF_FULL, PyBUF_FULL_RO, + PyBUF_RECORDS, PyBUF_RECORDS_RO, + PyBUF_STRIDED, PyBUF_STRIDED_RO, + PyBUF_CONTIG, PyBUF_CONTIG_RO, + ) + + # no buffer interface + self.assertRaises(TypeError, py_buffer_to_contiguous, {}, 'F', + PyBUF_FULL_RO) + + # scalar, read-only request + nd = ndarray(9, shape=(), format="L", flags=ND_WRITABLE) + for order in ['C', 'F', 'A']: + for request in requests: + b = py_buffer_to_contiguous(nd, order, request) + self.assertEqual(b, nd.tobytes()) + + # zeros in shape + nd = ndarray([1], shape=[0], format="L", flags=ND_WRITABLE) + for order in ['C', 'F', 'A']: + for request in requests: + b = py_buffer_to_contiguous(nd, order, request) + self.assertEqual(b, b'') + + nd = ndarray(list(range(8)), shape=[2, 0, 7], format="L", + flags=ND_WRITABLE) + for order in ['C', 'F', 'A']: + for request in requests: + b = py_buffer_to_contiguous(nd, order, request) + self.assertEqual(b, b'') + + ### One-dimensional arrays are trivial, since Fortran and C order + ### are the same. + + # one-dimensional + for f in [0, ND_FORTRAN]: + nd = ndarray([1], shape=[1], format="h", flags=f|ND_WRITABLE) + ndbytes = nd.tobytes() + for order in ['C', 'F', 'A']: + for request in requests: + b = py_buffer_to_contiguous(nd, order, request) + self.assertEqual(b, ndbytes) + + nd = ndarray([1, 2, 3], shape=[3], format="b", flags=f|ND_WRITABLE) + ndbytes = nd.tobytes() + for order in ['C', 'F', 'A']: + for request in requests: + b = py_buffer_to_contiguous(nd, order, request) + self.assertEqual(b, ndbytes) + + # one-dimensional, non-contiguous input + nd = ndarray([1, 2, 3], shape=[2], strides=[2], flags=ND_WRITABLE) + ndbytes = nd.tobytes() + for order in ['C', 'F', 'A']: + for request in [PyBUF_STRIDES, PyBUF_FULL]: + b = py_buffer_to_contiguous(nd, order, request) + self.assertEqual(b, ndbytes) + + nd = nd[::-1] + ndbytes = nd.tobytes() + for order in ['C', 'F', 'A']: + for request in requests: + try: + b = py_buffer_to_contiguous(nd, order, request) + except BufferError: + continue + self.assertEqual(b, ndbytes) + + ### + ### Multi-dimensional arrays: + ### + ### The goal here is to preserve the logical representation of the + ### input array but change the physical representation if necessary. + ### + ### _testbuffer example: + ### ==================== + ### + ### C input array: + ### -------------- + ### >>> nd = ndarray(list(range(12)), shape=[3, 4]) + ### >>> nd.tolist() + ### [[0, 1, 2, 3], + ### [4, 5, 6, 7], + ### [8, 9, 10, 11]] + ### + ### Fortran output: + ### --------------- + ### >>> py_buffer_to_contiguous(nd, 'F', PyBUF_FULL_RO) + ### >>> b'\x00\x04\x08\x01\x05\t\x02\x06\n\x03\x07\x0b' + ### + ### The return value corresponds to this input list for + ### _testbuffer's ndarray: + ### >>> nd = ndarray([0,4,8,1,5,9,2,6,10,3,7,11], shape=[3,4], + ### flags=ND_FORTRAN) + ### >>> nd.tolist() + ### [[0, 1, 2, 3], + ### [4, 5, 6, 7], + ### [8, 9, 10, 11]] + ### + ### The logical array is the same, but the values in memory are now + ### in Fortran order. + ### + ### NumPy example: + ### ============== + ### _testbuffer's ndarray takes lists to initialize the memory. + ### Here's the same sequence in NumPy: + ### + ### C input: + ### -------- + ### >>> nd = ndarray(buffer=bytearray(list(range(12))), + ### shape=[3, 4], dtype='B') + ### >>> nd + ### array([[ 0, 1, 2, 3], + ### [ 4, 5, 6, 7], + ### [ 8, 9, 10, 11]], dtype=uint8) + ### + ### Fortran output: + ### --------------- + ### >>> fortran_buf = nd.tostring(order='F') + ### >>> fortran_buf + ### b'\x00\x04\x08\x01\x05\t\x02\x06\n\x03\x07\x0b' + ### + ### >>> nd = ndarray(buffer=fortran_buf, shape=[3, 4], + ### dtype='B', order='F') + ### + ### >>> nd + ### array([[ 0, 1, 2, 3], + ### [ 4, 5, 6, 7], + ### [ 8, 9, 10, 11]], dtype=uint8) + ### + + # multi-dimensional, contiguous input + lst = list(range(12)) + for f in [0, ND_FORTRAN]: + nd = ndarray(lst, shape=[3, 4], flags=f|ND_WRITABLE) + if numpy_array: + na = numpy_array(buffer=bytearray(lst), + shape=[3, 4], dtype='B', + order='C' if f == 0 else 'F') + + # 'C' request + if f == ND_FORTRAN: # 'F' to 'C' + x = ndarray(transpose(lst, [4, 3]), shape=[3, 4], + flags=ND_WRITABLE) + expected = x.tobytes() + else: + expected = nd.tobytes() + for request in requests: + try: + b = py_buffer_to_contiguous(nd, 'C', request) + except BufferError: + continue + + self.assertEqual(b, expected) + + # Check that output can be used as the basis for constructing + # a C array that is logically identical to the input array. + y = ndarray([v for v in b], shape=[3, 4], flags=ND_WRITABLE) + self.assertEqual(memoryview(y), memoryview(nd)) + + if numpy_array: + self.assertEqual(b, na.tostring(order='C')) + + # 'F' request + if f == 0: # 'C' to 'F' + x = ndarray(transpose(lst, [3, 4]), shape=[4, 3], + flags=ND_WRITABLE) + else: + x = ndarray(lst, shape=[3, 4], flags=ND_WRITABLE) + expected = x.tobytes() + for request in [PyBUF_FULL, PyBUF_FULL_RO, PyBUF_INDIRECT, + PyBUF_STRIDES, PyBUF_ND]: + try: + b = py_buffer_to_contiguous(nd, 'F', request) + except BufferError: + continue + self.assertEqual(b, expected) + + # Check that output can be used as the basis for constructing + # a Fortran array that is logically identical to the input array. + y = ndarray([v for v in b], shape=[3, 4], flags=ND_FORTRAN|ND_WRITABLE) + self.assertEqual(memoryview(y), memoryview(nd)) + + if numpy_array: + self.assertEqual(b, na.tostring(order='F')) + + # 'A' request + if f == ND_FORTRAN: + x = ndarray(lst, shape=[3, 4], flags=ND_WRITABLE) + expected = x.tobytes() + else: + expected = nd.tobytes() + for request in [PyBUF_FULL, PyBUF_FULL_RO, PyBUF_INDIRECT, + PyBUF_STRIDES, PyBUF_ND]: + try: + b = py_buffer_to_contiguous(nd, 'A', request) + except BufferError: + continue + + self.assertEqual(b, expected) + + # Check that output can be used as the basis for constructing + # an array with order=f that is logically identical to the input + # array. + y = ndarray([v for v in b], shape=[3, 4], flags=f|ND_WRITABLE) + self.assertEqual(memoryview(y), memoryview(nd)) + + if numpy_array: + self.assertEqual(b, na.tostring(order='A')) + + # multi-dimensional, non-contiguous input + nd = ndarray(list(range(12)), shape=[3, 4], flags=ND_WRITABLE|ND_PIL) + + # 'C' + b = py_buffer_to_contiguous(nd, 'C', PyBUF_FULL_RO) + self.assertEqual(b, nd.tobytes()) + y = ndarray([v for v in b], shape=[3, 4], flags=ND_WRITABLE) + self.assertEqual(memoryview(y), memoryview(nd)) + + # 'F' + b = py_buffer_to_contiguous(nd, 'F', PyBUF_FULL_RO) + x = ndarray(transpose(lst, [3, 4]), shape=[4, 3], flags=ND_WRITABLE) + self.assertEqual(b, x.tobytes()) + y = ndarray([v for v in b], shape=[3, 4], flags=ND_FORTRAN|ND_WRITABLE) + self.assertEqual(memoryview(y), memoryview(nd)) + + # 'A' + b = py_buffer_to_contiguous(nd, 'A', PyBUF_FULL_RO) + self.assertEqual(b, nd.tobytes()) + y = ndarray([v for v in b], shape=[3, 4], flags=ND_WRITABLE) + self.assertEqual(memoryview(y), memoryview(nd)) + + def test_memoryview_construction(self): + + items_shape = [(9, []), ([1,2,3], [3]), (list(range(2*3*5)), [2,3,5])] + + # NumPy style, C-contiguous: + for items, shape in items_shape: + + # From PEP-3118 compliant exporter: + ex = ndarray(items, shape=shape) + m = memoryview(ex) + self.assertTrue(m.c_contiguous) + self.assertTrue(m.contiguous) + + ndim = len(shape) + strides = strides_from_shape(ndim, shape, 1, 'C') + lst = carray(items, shape) + + self.verify(m, obj=ex, + itemsize=1, fmt='B', readonly=True, + ndim=ndim, shape=shape, strides=strides, + lst=lst) + + # From memoryview: + m2 = memoryview(m) + self.verify(m2, obj=ex, + itemsize=1, fmt='B', readonly=True, + ndim=ndim, shape=shape, strides=strides, + lst=lst) + + # PyMemoryView_FromBuffer(): no strides + nd = ndarray(ex, getbuf=PyBUF_CONTIG_RO|PyBUF_FORMAT) + self.assertEqual(nd.strides, ()) + m = nd.memoryview_from_buffer() + self.verify(m, obj=None, + itemsize=1, fmt='B', readonly=True, + ndim=ndim, shape=shape, strides=strides, + lst=lst) + + # PyMemoryView_FromBuffer(): no format, shape, strides + nd = ndarray(ex, getbuf=PyBUF_SIMPLE) + self.assertEqual(nd.format, '') + self.assertEqual(nd.shape, ()) + self.assertEqual(nd.strides, ()) + m = nd.memoryview_from_buffer() + + lst = [items] if ndim == 0 else items + self.verify(m, obj=None, + itemsize=1, fmt='B', readonly=True, + ndim=1, shape=[ex.nbytes], strides=(1,), + lst=lst) + + # NumPy style, Fortran contiguous: + for items, shape in items_shape: + + # From PEP-3118 compliant exporter: + ex = ndarray(items, shape=shape, flags=ND_FORTRAN) + m = memoryview(ex) + self.assertTrue(m.f_contiguous) + self.assertTrue(m.contiguous) + + ndim = len(shape) + strides = strides_from_shape(ndim, shape, 1, 'F') + lst = farray(items, shape) + + self.verify(m, obj=ex, + itemsize=1, fmt='B', readonly=True, + ndim=ndim, shape=shape, strides=strides, + lst=lst) + + # From memoryview: + m2 = memoryview(m) + self.verify(m2, obj=ex, + itemsize=1, fmt='B', readonly=True, + ndim=ndim, shape=shape, strides=strides, + lst=lst) + + # PIL style: + for items, shape in items_shape[1:]: + + # From PEP-3118 compliant exporter: + ex = ndarray(items, shape=shape, flags=ND_PIL) + m = memoryview(ex) + + ndim = len(shape) + lst = carray(items, shape) + + self.verify(m, obj=ex, + itemsize=1, fmt='B', readonly=True, + ndim=ndim, shape=shape, strides=ex.strides, + lst=lst) + + # From memoryview: + m2 = memoryview(m) + self.verify(m2, obj=ex, + itemsize=1, fmt='B', readonly=True, + ndim=ndim, shape=shape, strides=ex.strides, + lst=lst) + + # Invalid number of arguments: + self.assertRaises(TypeError, memoryview, b'9', 'x') + # Not a buffer provider: + self.assertRaises(TypeError, memoryview, {}) + # Non-compliant buffer provider: + ex = ndarray([1,2,3], shape=[3]) + nd = ndarray(ex, getbuf=PyBUF_SIMPLE) + self.assertRaises(BufferError, memoryview, nd) + nd = ndarray(ex, getbuf=PyBUF_CONTIG_RO|PyBUF_FORMAT) + self.assertRaises(BufferError, memoryview, nd) + + # ndim > 64 + nd = ndarray([1]*128, shape=[1]*128, format='L') + self.assertRaises(ValueError, memoryview, nd) + self.assertRaises(ValueError, nd.memoryview_from_buffer) + self.assertRaises(ValueError, get_contiguous, nd, PyBUF_READ, 'C') + self.assertRaises(ValueError, get_contiguous, nd, PyBUF_READ, 'F') + self.assertRaises(ValueError, get_contiguous, nd[::-1], PyBUF_READ, 'C') + + def test_memoryview_cast_zero_shape(self): + # Casts are undefined if buffer is multidimensional and shape + # contains zeros. These arrays are regarded as C-contiguous by + # Numpy and PyBuffer_GetContiguous(), so they are not caught by + # the test for C-contiguity in memory_cast(). + items = [1,2,3] + for shape in ([0,3,3], [3,0,3], [0,3,3]): + ex = ndarray(items, shape=shape) + self.assertTrue(ex.c_contiguous) + msrc = memoryview(ex) + self.assertRaises(TypeError, msrc.cast, 'c') + # Monodimensional empty view can be cast (issue #19014). + for fmt, _, _ in iter_format(1, 'memoryview'): + msrc = memoryview(b'') + m = msrc.cast(fmt) + self.assertEqual(m.tobytes(), b'') + self.assertEqual(m.tolist(), []) + + check_sizeof = support.check_sizeof + + def test_memoryview_sizeof(self): + check = self.check_sizeof + vsize = support.calcvobjsize + base_struct = 'Pnin 2P2n2i5P P' + per_dim = '3n' + + items = list(range(8)) + check(memoryview(b''), vsize(base_struct + 1 * per_dim)) + a = ndarray(items, shape=[2, 4], format="b") + check(memoryview(a), vsize(base_struct + 2 * per_dim)) + a = ndarray(items, shape=[2, 2, 2], format="b") + check(memoryview(a), vsize(base_struct + 3 * per_dim)) + + def test_memoryview_struct_module(self): + + class INT(object): + def __init__(self, val): + self.val = val + def __int__(self): + return self.val + + class IDX(object): + def __init__(self, val): + self.val = val + def __index__(self): + return self.val + + def f(): return 7 + + values = [INT(9), IDX(9), + 2.2+3j, Decimal("-21.1"), 12.2, Fraction(5, 2), + [1,2,3], {4,5,6}, {7:8}, (), (9,), + True, False, None, NotImplemented, + b'a', b'abc', bytearray(b'a'), bytearray(b'abc'), + 'a', 'abc', r'a', r'abc', + f, lambda x: x] + + for fmt, items, item in iter_format(10, 'memoryview'): + ex = ndarray(items, shape=[10], format=fmt, flags=ND_WRITABLE) + nd = ndarray(items, shape=[10], format=fmt, flags=ND_WRITABLE) + m = memoryview(ex) + + struct.pack_into(fmt, nd, 0, item) + m[0] = item + self.assertEqual(m[0], nd[0]) + + itemsize = struct.calcsize(fmt) + if 'P' in fmt: + continue + + for v in values: + struct_err = None + try: + struct.pack_into(fmt, nd, itemsize, v) + except struct.error: + struct_err = struct.error + + mv_err = None + try: + m[1] = v + except (TypeError, ValueError) as e: + mv_err = e.__class__ + + if struct_err or mv_err: + self.assertIsNot(struct_err, None) + self.assertIsNot(mv_err, None) + else: + self.assertEqual(m[1], nd[1]) + + def test_memoryview_cast_zero_strides(self): + # Casts are undefined if strides contains zeros. These arrays are + # (sometimes!) regarded as C-contiguous by Numpy, but not by + # PyBuffer_GetContiguous(). + ex = ndarray([1,2,3], shape=[3], strides=[0]) + self.assertFalse(ex.c_contiguous) + msrc = memoryview(ex) + self.assertRaises(TypeError, msrc.cast, 'c') + + def test_memoryview_cast_invalid(self): + # invalid format + for sfmt in NON_BYTE_FORMAT: + sformat = '@' + sfmt if randrange(2) else sfmt + ssize = struct.calcsize(sformat) + for dfmt in NON_BYTE_FORMAT: + dformat = '@' + dfmt if randrange(2) else dfmt + dsize = struct.calcsize(dformat) + ex = ndarray(list(range(32)), shape=[32//ssize], format=sformat) + msrc = memoryview(ex) + self.assertRaises(TypeError, msrc.cast, dfmt, [32//dsize]) + + for sfmt, sitems, _ in iter_format(1): + ex = ndarray(sitems, shape=[1], format=sfmt) + msrc = memoryview(ex) + for dfmt, _, _ in iter_format(1): + if not is_memoryview_format(dfmt): + self.assertRaises(ValueError, msrc.cast, dfmt, + [32//dsize]) + else: + if not is_byte_format(sfmt) and not is_byte_format(dfmt): + self.assertRaises(TypeError, msrc.cast, dfmt, + [32//dsize]) + + # invalid shape + size_h = struct.calcsize('h') + size_d = struct.calcsize('d') + ex = ndarray(list(range(2*2*size_d)), shape=[2,2,size_d], format='h') + msrc = memoryview(ex) + self.assertRaises(TypeError, msrc.cast, shape=[2,2,size_h], format='d') + + ex = ndarray(list(range(120)), shape=[1,2,3,4,5]) + m = memoryview(ex) + + # incorrect number of args + self.assertRaises(TypeError, m.cast) + self.assertRaises(TypeError, m.cast, 1, 2, 3) + + # incorrect dest format type + self.assertRaises(TypeError, m.cast, {}) + + # incorrect dest format + self.assertRaises(ValueError, m.cast, "X") + self.assertRaises(ValueError, m.cast, "@X") + self.assertRaises(ValueError, m.cast, "@XY") + + # dest format not implemented + self.assertRaises(ValueError, m.cast, "=B") + self.assertRaises(ValueError, m.cast, "!L") + self.assertRaises(ValueError, m.cast, "l") + self.assertRaises(ValueError, m.cast, "BI") + self.assertRaises(ValueError, m.cast, "xBI") + + # src format not implemented + ex = ndarray([(1,2), (3,4)], shape=[2], format="II") + m = memoryview(ex) + self.assertRaises(NotImplementedError, m.__getitem__, 0) + self.assertRaises(NotImplementedError, m.__setitem__, 0, 8) + self.assertRaises(NotImplementedError, m.tolist) + + # incorrect shape type + ex = ndarray(list(range(120)), shape=[1,2,3,4,5]) + m = memoryview(ex) + self.assertRaises(TypeError, m.cast, "B", shape={}) + + # incorrect shape elements + ex = ndarray(list(range(120)), shape=[2*3*4*5]) + m = memoryview(ex) + self.assertRaises(OverflowError, m.cast, "B", shape=[2**64]) + self.assertRaises(ValueError, m.cast, "B", shape=[-1]) + self.assertRaises(ValueError, m.cast, "B", shape=[2,3,4,5,6,7,-1]) + self.assertRaises(ValueError, m.cast, "B", shape=[2,3,4,5,6,7,0]) + self.assertRaises(TypeError, m.cast, "B", shape=[2,3,4,5,6,7,'x']) + + # N-D -> N-D cast + ex = ndarray(list([9 for _ in range(3*5*7*11)]), shape=[3,5,7,11]) + m = memoryview(ex) + self.assertRaises(TypeError, m.cast, "I", shape=[2,3,4,5]) + + # cast with ndim > 64 + nd = ndarray(list(range(128)), shape=[128], format='I') + m = memoryview(nd) + self.assertRaises(ValueError, m.cast, 'I', [1]*128) + + # view->len not a multiple of itemsize + ex = ndarray(list([9 for _ in range(3*5*7*11)]), shape=[3*5*7*11]) + m = memoryview(ex) + self.assertRaises(TypeError, m.cast, "I", shape=[2,3,4,5]) + + # product(shape) * itemsize != buffer size + ex = ndarray(list([9 for _ in range(3*5*7*11)]), shape=[3*5*7*11]) + m = memoryview(ex) + self.assertRaises(TypeError, m.cast, "B", shape=[2,3,4,5]) + + # product(shape) * itemsize overflow + nd = ndarray(list(range(128)), shape=[128], format='I') + m1 = memoryview(nd) + nd = ndarray(list(range(128)), shape=[128], format='B') + m2 = memoryview(nd) + if sys.maxsize == 2**63-1: + self.assertRaises(TypeError, m1.cast, 'B', + [7, 7, 73, 127, 337, 92737, 649657]) + self.assertRaises(ValueError, m1.cast, 'B', + [2**20, 2**20, 2**10, 2**10, 2**3]) + self.assertRaises(ValueError, m2.cast, 'I', + [2**20, 2**20, 2**10, 2**10, 2**1]) + else: + self.assertRaises(TypeError, m1.cast, 'B', + [1, 2147483647]) + self.assertRaises(ValueError, m1.cast, 'B', + [2**10, 2**10, 2**5, 2**5, 2**1]) + self.assertRaises(ValueError, m2.cast, 'I', + [2**10, 2**10, 2**5, 2**3, 2**1]) + + def test_memoryview_cast(self): + bytespec = ( + ('B', lambda ex: list(ex.tobytes())), + ('b', lambda ex: [x-256 if x > 127 else x for x in list(ex.tobytes())]), + ('c', lambda ex: [bytes(chr(x), 'latin-1') for x in list(ex.tobytes())]), + ) + + def iter_roundtrip(ex, m, items, fmt): + srcsize = struct.calcsize(fmt) + for bytefmt, to_bytelist in bytespec: + + m2 = m.cast(bytefmt) + lst = to_bytelist(ex) + self.verify(m2, obj=ex, + itemsize=1, fmt=bytefmt, readonly=False, + ndim=1, shape=[31*srcsize], strides=(1,), + lst=lst, cast=True) + + m3 = m2.cast(fmt) + self.assertEqual(m3, ex) + lst = ex.tolist() + self.verify(m3, obj=ex, + itemsize=srcsize, fmt=fmt, readonly=False, + ndim=1, shape=[31], strides=(srcsize,), + lst=lst, cast=True) + + # cast from ndim = 0 to ndim = 1 + srcsize = struct.calcsize('I') + ex = ndarray(9, shape=[], format='I') + destitems, destshape = cast_items(ex, 'B', 1) + m = memoryview(ex) + m2 = m.cast('B') + self.verify(m2, obj=ex, + itemsize=1, fmt='B', readonly=True, + ndim=1, shape=destshape, strides=(1,), + lst=destitems, cast=True) + + # cast from ndim = 1 to ndim = 0 + destsize = struct.calcsize('I') + ex = ndarray([9]*destsize, shape=[destsize], format='B') + destitems, destshape = cast_items(ex, 'I', destsize, shape=[]) + m = memoryview(ex) + m2 = m.cast('I', shape=[]) + self.verify(m2, obj=ex, + itemsize=destsize, fmt='I', readonly=True, + ndim=0, shape=(), strides=(), + lst=destitems, cast=True) + + # array.array: roundtrip to/from bytes + for fmt, items, _ in iter_format(31, 'array'): + ex = array.array(fmt, items) + m = memoryview(ex) + iter_roundtrip(ex, m, items, fmt) + + # ndarray: roundtrip to/from bytes + for fmt, items, _ in iter_format(31, 'memoryview'): + ex = ndarray(items, shape=[31], format=fmt, flags=ND_WRITABLE) + m = memoryview(ex) + iter_roundtrip(ex, m, items, fmt) + + def test_memoryview_cast_1D_ND(self): + # Cast between C-contiguous buffers. At least one buffer must + # be 1D, at least one format must be 'c', 'b' or 'B'. + for _tshape in gencastshapes(): + for char in fmtdict['@']: + # Casts to _Bool are undefined if the source contains values + # other than 0 or 1. + if char == "?": + continue + tfmt = ('', '@')[randrange(2)] + char + tsize = struct.calcsize(tfmt) + n = prod(_tshape) * tsize + obj = 'memoryview' if is_byte_format(tfmt) else 'bytefmt' + for fmt, items, _ in iter_format(n, obj): + size = struct.calcsize(fmt) + shape = [n] if n > 0 else [] + tshape = _tshape + [size] + + ex = ndarray(items, shape=shape, format=fmt) + m = memoryview(ex) + + titems, tshape = cast_items(ex, tfmt, tsize, shape=tshape) + + if titems is None: + self.assertRaises(TypeError, m.cast, tfmt, tshape) + continue + if titems == 'nan': + continue # NaNs in lists are a recipe for trouble. + + # 1D -> ND + nd = ndarray(titems, shape=tshape, format=tfmt) + + m2 = m.cast(tfmt, shape=tshape) + ndim = len(tshape) + strides = nd.strides + lst = nd.tolist() + self.verify(m2, obj=ex, + itemsize=tsize, fmt=tfmt, readonly=True, + ndim=ndim, shape=tshape, strides=strides, + lst=lst, cast=True) + + # ND -> 1D + m3 = m2.cast(fmt) + m4 = m2.cast(fmt, shape=shape) + ndim = len(shape) + strides = ex.strides + lst = ex.tolist() + + self.verify(m3, obj=ex, + itemsize=size, fmt=fmt, readonly=True, + ndim=ndim, shape=shape, strides=strides, + lst=lst, cast=True) + + self.verify(m4, obj=ex, + itemsize=size, fmt=fmt, readonly=True, + ndim=ndim, shape=shape, strides=strides, + lst=lst, cast=True) + + if ctypes: + # format: "T{>l:x:>d:y:}" + class BEPoint(ctypes.BigEndianStructure): + _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_double)] + point = BEPoint(100, 200.1) + m1 = memoryview(point) + m2 = m1.cast('B') + self.assertEqual(m2.obj, point) + self.assertEqual(m2.itemsize, 1) + self.assertIs(m2.readonly, False) + self.assertEqual(m2.ndim, 1) + self.assertEqual(m2.shape, (m2.nbytes,)) + self.assertEqual(m2.strides, (1,)) + self.assertEqual(m2.suboffsets, ()) + + x = ctypes.c_double(1.2) + m1 = memoryview(x) + m2 = m1.cast('c') + self.assertEqual(m2.obj, x) + self.assertEqual(m2.itemsize, 1) + self.assertIs(m2.readonly, False) + self.assertEqual(m2.ndim, 1) + self.assertEqual(m2.shape, (m2.nbytes,)) + self.assertEqual(m2.strides, (1,)) + self.assertEqual(m2.suboffsets, ()) + + def test_memoryview_tolist(self): + + # Most tolist() tests are in self.verify() etc. + + a = array.array('h', list(range(-6, 6))) + m = memoryview(a) + self.assertEqual(m, a) + self.assertEqual(m.tolist(), a.tolist()) + + a = a[2::3] + m = m[2::3] + self.assertEqual(m, a) + self.assertEqual(m.tolist(), a.tolist()) + + ex = ndarray(list(range(2*3*5*7*11)), shape=[11,2,7,3,5], format='L') + m = memoryview(ex) + self.assertEqual(m.tolist(), ex.tolist()) + + ex = ndarray([(2, 5), (7, 11)], shape=[2], format='lh') + m = memoryview(ex) + self.assertRaises(NotImplementedError, m.tolist) + + ex = ndarray([b'12345'], shape=[1], format="s") + m = memoryview(ex) + self.assertRaises(NotImplementedError, m.tolist) + + ex = ndarray([b"a",b"b",b"c",b"d",b"e",b"f"], shape=[2,3], format='s') + m = memoryview(ex) + self.assertRaises(NotImplementedError, m.tolist) + + def test_memoryview_repr(self): + m = memoryview(bytearray(9)) + r = m.__repr__() + self.assertTrue(r.startswith("l:x:>l:y:}" + class BEPoint(ctypes.BigEndianStructure): + _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] + point = BEPoint(100, 200) + a = memoryview(point) + b = memoryview(point) + self.assertNotEqual(a, b) + self.assertNotEqual(a, point) + self.assertNotEqual(point, a) + self.assertRaises(NotImplementedError, a.tolist) + + def test_memoryview_compare_ndim_zero(self): + + nd1 = ndarray(1729, shape=[], format='@L') + nd2 = ndarray(1729, shape=[], format='L', flags=ND_WRITABLE) + v = memoryview(nd1) + w = memoryview(nd2) + self.assertEqual(v, w) + self.assertEqual(w, v) + self.assertEqual(v, nd2) + self.assertEqual(nd2, v) + self.assertEqual(w, nd1) + self.assertEqual(nd1, w) + + self.assertFalse(v.__ne__(w)) + self.assertFalse(w.__ne__(v)) + + w[()] = 1728 + self.assertNotEqual(v, w) + self.assertNotEqual(w, v) + self.assertNotEqual(v, nd2) + self.assertNotEqual(nd2, v) + self.assertNotEqual(w, nd1) + self.assertNotEqual(nd1, w) + + self.assertFalse(v.__eq__(w)) + self.assertFalse(w.__eq__(v)) + + nd = ndarray(list(range(12)), shape=[12], flags=ND_WRITABLE|ND_PIL) + ex = ndarray(list(range(12)), shape=[12], flags=ND_WRITABLE|ND_PIL) + m = memoryview(ex) + + self.assertEqual(m, nd) + m[9] = 100 + self.assertNotEqual(m, nd) + + # struct module: equal + nd1 = ndarray((1729, 1.2, b'12345'), shape=[], format='Lf5s') + nd2 = ndarray((1729, 1.2, b'12345'), shape=[], format='hf5s', + flags=ND_WRITABLE) + v = memoryview(nd1) + w = memoryview(nd2) + self.assertEqual(v, w) + self.assertEqual(w, v) + self.assertEqual(v, nd2) + self.assertEqual(nd2, v) + self.assertEqual(w, nd1) + self.assertEqual(nd1, w) + + # struct module: not equal + nd1 = ndarray((1729, 1.2, b'12345'), shape=[], format='Lf5s') + nd2 = ndarray((-1729, 1.2, b'12345'), shape=[], format='hf5s', + flags=ND_WRITABLE) + v = memoryview(nd1) + w = memoryview(nd2) + self.assertNotEqual(v, w) + self.assertNotEqual(w, v) + self.assertNotEqual(v, nd2) + self.assertNotEqual(nd2, v) + self.assertNotEqual(w, nd1) + self.assertNotEqual(nd1, w) + self.assertEqual(v, nd1) + self.assertEqual(w, nd2) + + def test_memoryview_compare_ndim_one(self): + + # contiguous + nd1 = ndarray([-529, 576, -625, 676, -729], shape=[5], format='@h') + nd2 = ndarray([-529, 576, -625, 676, 729], shape=[5], format='@h') + v = memoryview(nd1) + w = memoryview(nd2) + + self.assertEqual(v, nd1) + self.assertEqual(w, nd2) + self.assertNotEqual(v, nd2) + self.assertNotEqual(w, nd1) + self.assertNotEqual(v, w) + + # contiguous, struct module + nd1 = ndarray([-529, 576, -625, 676, -729], shape=[5], format='', '!']: + x = ndarray([2**63]*120, shape=[3,5,2,2,2], format=byteorder+'Q') + y = ndarray([2**63]*120, shape=[3,5,2,2,2], format=byteorder+'Q', + flags=ND_WRITABLE|ND_FORTRAN) + y[2][3][1][1][1] = 1 + a = memoryview(x) + b = memoryview(y) + self.assertEqual(a, x) + self.assertEqual(b, y) + self.assertNotEqual(a, b) + self.assertNotEqual(a, y) + self.assertNotEqual(b, x) + + x = ndarray([(2**63, 2**31, 2**15)]*120, shape=[3,5,2,2,2], + format=byteorder+'QLH') + y = ndarray([(2**63, 2**31, 2**15)]*120, shape=[3,5,2,2,2], + format=byteorder+'QLH', flags=ND_WRITABLE|ND_FORTRAN) + y[2][3][1][1][1] = (1, 1, 1) + a = memoryview(x) + b = memoryview(y) + self.assertEqual(a, x) + self.assertEqual(b, y) + self.assertNotEqual(a, b) + self.assertNotEqual(a, y) + self.assertNotEqual(b, x) + + def test_memoryview_check_released(self): + + a = array.array('d', [1.1, 2.2, 3.3]) + + m = memoryview(a) + m.release() + + # PyMemoryView_FromObject() + self.assertRaises(ValueError, memoryview, m) + # memoryview.cast() + self.assertRaises(ValueError, m.cast, 'c') + # getbuffer() + self.assertRaises(ValueError, ndarray, m) + # memoryview.tolist() + self.assertRaises(ValueError, m.tolist) + # memoryview.tobytes() + self.assertRaises(ValueError, m.tobytes) + # sequence + self.assertRaises(ValueError, eval, "1.0 in m", locals()) + # subscript + self.assertRaises(ValueError, m.__getitem__, 0) + # assignment + self.assertRaises(ValueError, m.__setitem__, 0, 1) + + for attr in ('obj', 'nbytes', 'readonly', 'itemsize', 'format', 'ndim', + 'shape', 'strides', 'suboffsets', 'c_contiguous', + 'f_contiguous', 'contiguous'): + self.assertRaises(ValueError, m.__getattribute__, attr) + + # richcompare + b = array.array('d', [1.1, 2.2, 3.3]) + m1 = memoryview(a) + m2 = memoryview(b) + + self.assertEqual(m1, m2) + m1.release() + self.assertNotEqual(m1, m2) + self.assertNotEqual(m1, a) + self.assertEqual(m1, m1) + + def test_memoryview_tobytes(self): + # Many implicit tests are already in self.verify(). + + t = (-529, 576, -625, 676, -729) + + nd = ndarray(t, shape=[5], format='@h') + m = memoryview(nd) + self.assertEqual(m, nd) + self.assertEqual(m.tobytes(), nd.tobytes()) + + nd = ndarray([t], shape=[1], format='>hQiLl') + m = memoryview(nd) + self.assertEqual(m, nd) + self.assertEqual(m.tobytes(), nd.tobytes()) + + nd = ndarray([t for _ in range(12)], shape=[2,2,3], format='=hQiLl') + m = memoryview(nd) + self.assertEqual(m, nd) + self.assertEqual(m.tobytes(), nd.tobytes()) + + nd = ndarray([t for _ in range(120)], shape=[5,2,2,3,2], + format='l:x:>l:y:}" + class BEPoint(ctypes.BigEndianStructure): + _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] + point = BEPoint(100, 200) + a = memoryview(point) + self.assertEqual(a.tobytes(), bytes(point)) + + def test_memoryview_get_contiguous(self): + # Many implicit tests are already in self.verify(). + + # no buffer interface + self.assertRaises(TypeError, get_contiguous, {}, PyBUF_READ, 'F') + + # writable request to read-only object + self.assertRaises(BufferError, get_contiguous, b'x', PyBUF_WRITE, 'C') + + # writable request to non-contiguous object + nd = ndarray([1, 2, 3], shape=[2], strides=[2]) + self.assertRaises(BufferError, get_contiguous, nd, PyBUF_WRITE, 'A') + + # scalar, read-only request from read-only exporter + nd = ndarray(9, shape=(), format="L") + for order in ['C', 'F', 'A']: + m = get_contiguous(nd, PyBUF_READ, order) + self.assertEqual(m, nd) + self.assertEqual(m[()], 9) + + # scalar, read-only request from writable exporter + nd = ndarray(9, shape=(), format="L", flags=ND_WRITABLE) + for order in ['C', 'F', 'A']: + m = get_contiguous(nd, PyBUF_READ, order) + self.assertEqual(m, nd) + self.assertEqual(m[()], 9) + + # scalar, writable request + for order in ['C', 'F', 'A']: + nd[()] = 9 + m = get_contiguous(nd, PyBUF_WRITE, order) + self.assertEqual(m, nd) + self.assertEqual(m[()], 9) + + m[()] = 10 + self.assertEqual(m[()], 10) + self.assertEqual(nd[()], 10) + + # zeros in shape + nd = ndarray([1], shape=[0], format="L", flags=ND_WRITABLE) + for order in ['C', 'F', 'A']: + m = get_contiguous(nd, PyBUF_READ, order) + self.assertRaises(IndexError, m.__getitem__, 0) + self.assertEqual(m, nd) + self.assertEqual(m.tolist(), []) + + nd = ndarray(list(range(8)), shape=[2, 0, 7], format="L", + flags=ND_WRITABLE) + for order in ['C', 'F', 'A']: + m = get_contiguous(nd, PyBUF_READ, order) + self.assertEqual(ndarray(m).tolist(), [[], []]) + + # one-dimensional + nd = ndarray([1], shape=[1], format="h", flags=ND_WRITABLE) + for order in ['C', 'F', 'A']: + m = get_contiguous(nd, PyBUF_WRITE, order) + self.assertEqual(m, nd) + self.assertEqual(m.tolist(), nd.tolist()) + + nd = ndarray([1, 2, 3], shape=[3], format="b", flags=ND_WRITABLE) + for order in ['C', 'F', 'A']: + m = get_contiguous(nd, PyBUF_WRITE, order) + self.assertEqual(m, nd) + self.assertEqual(m.tolist(), nd.tolist()) + + # one-dimensional, non-contiguous + nd = ndarray([1, 2, 3], shape=[2], strides=[2], flags=ND_WRITABLE) + for order in ['C', 'F', 'A']: + m = get_contiguous(nd, PyBUF_READ, order) + self.assertEqual(m, nd) + self.assertEqual(m.tolist(), nd.tolist()) + self.assertRaises(TypeError, m.__setitem__, 1, 20) + self.assertEqual(m[1], 3) + self.assertEqual(nd[1], 3) + + nd = nd[::-1] + for order in ['C', 'F', 'A']: + m = get_contiguous(nd, PyBUF_READ, order) + self.assertEqual(m, nd) + self.assertEqual(m.tolist(), nd.tolist()) + self.assertRaises(TypeError, m.__setitem__, 1, 20) + self.assertEqual(m[1], 1) + self.assertEqual(nd[1], 1) + + # multi-dimensional, contiguous input + nd = ndarray(list(range(12)), shape=[3, 4], flags=ND_WRITABLE) + for order in ['C', 'A']: + m = get_contiguous(nd, PyBUF_WRITE, order) + self.assertEqual(ndarray(m).tolist(), nd.tolist()) + + self.assertRaises(BufferError, get_contiguous, nd, PyBUF_WRITE, 'F') + m = get_contiguous(nd, PyBUF_READ, order) + self.assertEqual(ndarray(m).tolist(), nd.tolist()) + + nd = ndarray(list(range(12)), shape=[3, 4], + flags=ND_WRITABLE|ND_FORTRAN) + for order in ['F', 'A']: + m = get_contiguous(nd, PyBUF_WRITE, order) + self.assertEqual(ndarray(m).tolist(), nd.tolist()) + + self.assertRaises(BufferError, get_contiguous, nd, PyBUF_WRITE, 'C') + m = get_contiguous(nd, PyBUF_READ, order) + self.assertEqual(ndarray(m).tolist(), nd.tolist()) + + # multi-dimensional, non-contiguous input + nd = ndarray(list(range(12)), shape=[3, 4], flags=ND_WRITABLE|ND_PIL) + for order in ['C', 'F', 'A']: + self.assertRaises(BufferError, get_contiguous, nd, PyBUF_WRITE, + order) + m = get_contiguous(nd, PyBUF_READ, order) + self.assertEqual(ndarray(m).tolist(), nd.tolist()) + + # flags + nd = ndarray([1,2,3,4,5], shape=[3], strides=[2]) + m = get_contiguous(nd, PyBUF_READ, 'C') + self.assertTrue(m.c_contiguous) + + def test_memoryview_serializing(self): + + # C-contiguous + size = struct.calcsize('i') + a = array.array('i', [1,2,3,4,5]) + m = memoryview(a) + buf = io.BytesIO(m) + b = bytearray(5*size) + buf.readinto(b) + self.assertEqual(m.tobytes(), b) + + # C-contiguous, multi-dimensional + size = struct.calcsize('L') + nd = ndarray(list(range(12)), shape=[2,3,2], format="L") + m = memoryview(nd) + buf = io.BytesIO(m) + b = bytearray(2*3*2*size) + buf.readinto(b) + self.assertEqual(m.tobytes(), b) + + # Fortran contiguous, multi-dimensional + #size = struct.calcsize('L') + #nd = ndarray(list(range(12)), shape=[2,3,2], format="L", + # flags=ND_FORTRAN) + #m = memoryview(nd) + #buf = io.BytesIO(m) + #b = bytearray(2*3*2*size) + #buf.readinto(b) + #self.assertEqual(m.tobytes(), b) + + def test_memoryview_hash(self): + + # bytes exporter + b = bytes(list(range(12))) + m = memoryview(b) + self.assertEqual(hash(b), hash(m)) + + # C-contiguous + mc = m.cast('c', shape=[3,4]) + self.assertEqual(hash(mc), hash(b)) + + # non-contiguous + mx = m[::-2] + b = bytes(list(range(12))[::-2]) + self.assertEqual(hash(mx), hash(b)) + + # Fortran contiguous + nd = ndarray(list(range(30)), shape=[3,2,5], flags=ND_FORTRAN) + m = memoryview(nd) + self.assertEqual(hash(m), hash(nd)) + + # multi-dimensional slice + nd = ndarray(list(range(30)), shape=[3,2,5]) + x = nd[::2, ::, ::-1] + m = memoryview(x) + self.assertEqual(hash(m), hash(x)) + + # multi-dimensional slice with suboffsets + nd = ndarray(list(range(30)), shape=[2,5,3], flags=ND_PIL) + x = nd[::2, ::, ::-1] + m = memoryview(x) + self.assertEqual(hash(m), hash(x)) + + # equality-hash invariant + x = ndarray(list(range(12)), shape=[12], format='B') + a = memoryview(x) + + y = ndarray(list(range(12)), shape=[12], format='b') + b = memoryview(y) + + self.assertEqual(a, b) + self.assertEqual(hash(a), hash(b)) + + # non-byte formats + nd = ndarray(list(range(12)), shape=[2,2,3], format='L') + m = memoryview(nd) + self.assertRaises(ValueError, m.__hash__) + + nd = ndarray(list(range(-6, 6)), shape=[2,2,3], format='h') + m = memoryview(nd) + self.assertRaises(ValueError, m.__hash__) + + nd = ndarray(list(range(12)), shape=[2,2,3], format='= L') + m = memoryview(nd) + self.assertRaises(ValueError, m.__hash__) + + nd = ndarray(list(range(-6, 6)), shape=[2,2,3], format='< h') + m = memoryview(nd) + self.assertRaises(ValueError, m.__hash__) + + def test_memoryview_release(self): + + # Create re-exporter from getbuffer(memoryview), then release the view. + a = bytearray([1,2,3]) + m = memoryview(a) + nd = ndarray(m) # re-exporter + self.assertRaises(BufferError, m.release) + del nd + m.release() + + a = bytearray([1,2,3]) + m = memoryview(a) + nd1 = ndarray(m, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) + nd2 = ndarray(nd1, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) + self.assertIs(nd2.obj, m) + self.assertRaises(BufferError, m.release) + del nd1, nd2 + m.release() + + # chained views + a = bytearray([1,2,3]) + m1 = memoryview(a) + m2 = memoryview(m1) + nd = ndarray(m2) # re-exporter + m1.release() + self.assertRaises(BufferError, m2.release) + del nd + m2.release() + + a = bytearray([1,2,3]) + m1 = memoryview(a) + m2 = memoryview(m1) + nd1 = ndarray(m2, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) + nd2 = ndarray(nd1, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) + self.assertIs(nd2.obj, m2) + m1.release() + self.assertRaises(BufferError, m2.release) + del nd1, nd2 + m2.release() + + # Allow changing layout while buffers are exported. + nd = ndarray([1,2,3], shape=[3], flags=ND_VAREXPORT) + m1 = memoryview(nd) + + nd.push([4,5,6,7,8], shape=[5]) # mutate nd + m2 = memoryview(nd) + + x = memoryview(m1) + self.assertEqual(x.tolist(), m1.tolist()) + + y = memoryview(m2) + self.assertEqual(y.tolist(), m2.tolist()) + self.assertEqual(y.tolist(), nd.tolist()) + m2.release() + y.release() + + nd.pop() # pop the current view + self.assertEqual(x.tolist(), nd.tolist()) + + del nd + m1.release() + x.release() + + # If multiple memoryviews share the same managed buffer, implicit + # release() in the context manager's __exit__() method should still + # work. + def catch22(b): + with memoryview(b) as m2: + pass + + x = bytearray(b'123') + with memoryview(x) as m1: + catch22(m1) + self.assertEqual(m1[0], ord(b'1')) + + x = ndarray(list(range(12)), shape=[2,2,3], format='l') + y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) + z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) + self.assertIs(z.obj, x) + with memoryview(z) as m: + catch22(m) + self.assertEqual(m[0:1].tolist(), [[[0, 1, 2], [3, 4, 5]]]) + + # Test garbage collection. + for flags in (0, ND_REDIRECT): + x = bytearray(b'123') + with memoryview(x) as m1: + del x + y = ndarray(m1, getbuf=PyBUF_FULL_RO, flags=flags) + with memoryview(y) as m2: + del y + z = ndarray(m2, getbuf=PyBUF_FULL_RO, flags=flags) + with memoryview(z) as m3: + del z + catch22(m3) + catch22(m2) + catch22(m1) + self.assertEqual(m1[0], ord(b'1')) + self.assertEqual(m2[1], ord(b'2')) + self.assertEqual(m3[2], ord(b'3')) + del m3 + del m2 + del m1 + + x = bytearray(b'123') + with memoryview(x) as m1: + del x + y = ndarray(m1, getbuf=PyBUF_FULL_RO, flags=flags) + with memoryview(y) as m2: + del y + z = ndarray(m2, getbuf=PyBUF_FULL_RO, flags=flags) + with memoryview(z) as m3: + del z + catch22(m1) + catch22(m2) + catch22(m3) + self.assertEqual(m1[0], ord(b'1')) + self.assertEqual(m2[1], ord(b'2')) + self.assertEqual(m3[2], ord(b'3')) + del m1, m2, m3 + + # memoryview.release() fails if the view has exported buffers. + x = bytearray(b'123') + with self.assertRaises(BufferError): + with memoryview(x) as m: + ex = ndarray(m) + m[0] == ord(b'1') + + def test_memoryview_redirect(self): + + nd = ndarray([1.0 * x for x in range(12)], shape=[12], format='d') + a = array.array('d', [1.0 * x for x in range(12)]) + + for x in (nd, a): + y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) + z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) + m = memoryview(z) + + self.assertIs(y.obj, x) + self.assertIs(z.obj, x) + self.assertIs(m.obj, x) + + self.assertEqual(m, x) + self.assertEqual(m, y) + self.assertEqual(m, z) + + self.assertEqual(m[1:3], x[1:3]) + self.assertEqual(m[1:3], y[1:3]) + self.assertEqual(m[1:3], z[1:3]) + del y, z + self.assertEqual(m[1:3], x[1:3]) + + def test_memoryview_from_static_exporter(self): + + fmt = 'B' + lst = [0,1,2,3,4,5,6,7,8,9,10,11] + + # exceptions + self.assertRaises(TypeError, staticarray, 1, 2, 3) + + # view.obj==x + x = staticarray() + y = memoryview(x) + self.verify(y, obj=x, + itemsize=1, fmt=fmt, readonly=True, + ndim=1, shape=[12], strides=[1], + lst=lst) + for i in range(12): + self.assertEqual(y[i], i) + del x + del y + + x = staticarray() + y = memoryview(x) + del y + del x + + x = staticarray() + y = ndarray(x, getbuf=PyBUF_FULL_RO) + z = ndarray(y, getbuf=PyBUF_FULL_RO) + m = memoryview(z) + self.assertIs(y.obj, x) + self.assertIs(m.obj, z) + self.verify(m, obj=z, + itemsize=1, fmt=fmt, readonly=True, + ndim=1, shape=[12], strides=[1], + lst=lst) + del x, y, z, m + + x = staticarray() + y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) + z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) + m = memoryview(z) + self.assertIs(y.obj, x) + self.assertIs(z.obj, x) + self.assertIs(m.obj, x) + self.verify(m, obj=x, + itemsize=1, fmt=fmt, readonly=True, + ndim=1, shape=[12], strides=[1], + lst=lst) + del x, y, z, m + + # view.obj==NULL + x = staticarray(legacy_mode=True) + y = memoryview(x) + self.verify(y, obj=None, + itemsize=1, fmt=fmt, readonly=True, + ndim=1, shape=[12], strides=[1], + lst=lst) + for i in range(12): + self.assertEqual(y[i], i) + del x + del y + + x = staticarray(legacy_mode=True) + y = memoryview(x) + del y + del x + + x = staticarray(legacy_mode=True) + y = ndarray(x, getbuf=PyBUF_FULL_RO) + z = ndarray(y, getbuf=PyBUF_FULL_RO) + m = memoryview(z) + self.assertIs(y.obj, None) + self.assertIs(m.obj, z) + self.verify(m, obj=z, + itemsize=1, fmt=fmt, readonly=True, + ndim=1, shape=[12], strides=[1], + lst=lst) + del x, y, z, m + + x = staticarray(legacy_mode=True) + y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) + z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) + m = memoryview(z) + # Clearly setting view.obj==NULL is inferior, since it + # messes up the redirection chain: + self.assertIs(y.obj, None) + self.assertIs(z.obj, y) + self.assertIs(m.obj, y) + self.verify(m, obj=y, + itemsize=1, fmt=fmt, readonly=True, + ndim=1, shape=[12], strides=[1], + lst=lst) + del x, y, z, m + + def test_memoryview_getbuffer_undefined(self): + + # getbufferproc does not adhere to the new documentation + nd = ndarray([1,2,3], [3], flags=ND_GETBUF_FAIL|ND_GETBUF_UNDEFINED) + self.assertRaises(BufferError, memoryview, nd) + + def test_issue_7385(self): + x = ndarray([1,2,3], shape=[3], flags=ND_GETBUF_FAIL) + self.assertRaises(BufferError, memoryview, x) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_cgitb.py b/Lib/test/test_cgitb.py index 6b7c58a192..5cbb054a3c 100644 --- a/Lib/test/test_cgitb.py +++ b/Lib/test/test_cgitb.py @@ -1,68 +1,68 @@ -from test.support.os_helper import temp_dir -from test.support.script_helper import assert_python_failure -import unittest -import sys -import cgitb - -class TestCgitb(unittest.TestCase): - - def test_fonts(self): - text = "Hello Robbie!" - self.assertEqual(cgitb.small(text), "{}".format(text)) - self.assertEqual(cgitb.strong(text), "{}".format(text)) - self.assertEqual(cgitb.grey(text), - '{}'.format(text)) - - def test_blanks(self): - self.assertEqual(cgitb.small(""), "") - self.assertEqual(cgitb.strong(""), "") - self.assertEqual(cgitb.grey(""), "") - - def test_html(self): - try: - raise ValueError("Hello World") - except ValueError as err: - # If the html was templated we could do a bit more here. - # At least check that we get details on what we just raised. - html = cgitb.html(sys.exc_info()) - self.assertIn("ValueError", html) - self.assertIn(str(err), html) - - def test_text(self): - try: - raise ValueError("Hello World") - except ValueError as err: - text = cgitb.text(sys.exc_info()) - self.assertIn("ValueError", text) - self.assertIn("Hello World", text) - - def test_syshook_no_logdir_default_format(self): - with temp_dir() as tracedir: - rc, out, err = assert_python_failure( - '-c', - ('import cgitb; cgitb.enable(logdir=%s); ' - 'raise ValueError("Hello World")') % repr(tracedir)) - out = out.decode(sys.getfilesystemencoding()) - self.assertIn("ValueError", out) - self.assertIn("Hello World", out) - self.assertIn("<module>", out) - # By default we emit HTML markup. - self.assertIn('

', out) - self.assertIn('

', out) - - def test_syshook_no_logdir_text_format(self): - # Issue 12890: we were emitting the

tag in text mode. - with temp_dir() as tracedir: - rc, out, err = assert_python_failure( - '-c', - ('import cgitb; cgitb.enable(format="text", logdir=%s); ' - 'raise ValueError("Hello World")') % repr(tracedir)) - out = out.decode(sys.getfilesystemencoding()) - self.assertIn("ValueError", out) - self.assertIn("Hello World", out) - self.assertNotIn('

', out) - self.assertNotIn('

', out) - - -if __name__ == "__main__": - unittest.main() +from test.support.os_helper import temp_dir +from test.support.script_helper import assert_python_failure +import unittest +import sys +import cgitb + +class TestCgitb(unittest.TestCase): + + def test_fonts(self): + text = "Hello Robbie!" + self.assertEqual(cgitb.small(text), "{}".format(text)) + self.assertEqual(cgitb.strong(text), "{}".format(text)) + self.assertEqual(cgitb.grey(text), + '{}'.format(text)) + + def test_blanks(self): + self.assertEqual(cgitb.small(""), "") + self.assertEqual(cgitb.strong(""), "") + self.assertEqual(cgitb.grey(""), "") + + def test_html(self): + try: + raise ValueError("Hello World") + except ValueError as err: + # If the html was templated we could do a bit more here. + # At least check that we get details on what we just raised. + html = cgitb.html(sys.exc_info()) + self.assertIn("ValueError", html) + self.assertIn(str(err), html) + + def test_text(self): + try: + raise ValueError("Hello World") + except ValueError as err: + text = cgitb.text(sys.exc_info()) + self.assertIn("ValueError", text) + self.assertIn("Hello World", text) + + def test_syshook_no_logdir_default_format(self): + with temp_dir() as tracedir: + rc, out, err = assert_python_failure( + '-c', + ('import cgitb; cgitb.enable(logdir=%s); ' + 'raise ValueError("Hello World")') % repr(tracedir)) + out = out.decode(sys.getfilesystemencoding()) + self.assertIn("ValueError", out) + self.assertIn("Hello World", out) + self.assertIn("<module>", out) + # By default we emit HTML markup. + self.assertIn('

', out) + self.assertIn('

', out) + + def test_syshook_no_logdir_text_format(self): + # Issue 12890: we were emitting the

tag in text mode. + with temp_dir() as tracedir: + rc, out, err = assert_python_failure( + '-c', + ('import cgitb; cgitb.enable(format="text", logdir=%s); ' + 'raise ValueError("Hello World")') % repr(tracedir)) + out = out.decode(sys.getfilesystemencoding()) + self.assertIn("ValueError", out) + self.assertIn("Hello World", out) + self.assertNotIn('

', out) + self.assertNotIn('

', out) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index da4329048e..b590d1b4e0 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -1,893 +1,893 @@ -# Tests invocation of the interpreter with various command line arguments -# Most tests are executed with environment variables ignored -# See test_cmd_line_script.py for testing of script execution - -import os -import subprocess -import sys -import tempfile -import unittest -from test import support -from test.support.script_helper import ( - spawn_python, kill_python, assert_python_ok, assert_python_failure, - interpreter_requires_environment -) -from test.support import os_helper - - -# Debug build? -Py_DEBUG = hasattr(sys, "gettotalrefcount") - - -# XXX (ncoghlan): Move to script_helper and make consistent with run_python -def _kill_python_and_exit_code(p): - data = kill_python(p) - returncode = p.wait() - return data, returncode - -class CmdLineTest(unittest.TestCase): - def test_directories(self): - assert_python_failure('.') - assert_python_failure('< .') - - def verify_valid_flag(self, cmd_line): - rc, out, err = assert_python_ok(*cmd_line) - self.assertTrue(out == b'' or out.endswith(b'\n')) - self.assertNotIn(b'Traceback', out) - self.assertNotIn(b'Traceback', err) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_optimize(self): - self.verify_valid_flag('-O') - self.verify_valid_flag('-OO') - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_site_flag(self): - self.verify_valid_flag('-S') - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_usage(self): - rc, out, err = assert_python_ok('-h') - lines = out.splitlines() - self.assertIn(b'usage', lines[0]) - # The first line contains the program name, - # but the rest should be ASCII-only - b''.join(lines[1:]).decode('ascii') - - # NOTE: RUSTPYTHON version never starts with Python - @unittest.expectedFailure - def test_version(self): - version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii") - for switch in '-V', '--version', '-VV': - rc, out, err = assert_python_ok(switch) - self.assertFalse(err.startswith(version)) - self.assertTrue(out.startswith(version)) - - def test_verbose(self): - # -v causes imports to write to stderr. If the write to - # stderr itself causes an import to happen (for the output - # codec), a recursion loop can occur. - rc, out, err = assert_python_ok('-v') - self.assertNotIn(b'stack overflow', err) - rc, out, err = assert_python_ok('-vv') - self.assertNotIn(b'stack overflow', err) - - @unittest.skipIf(interpreter_requires_environment(), - 'Cannot run -E tests when PYTHON env vars are required.') - def test_xoptions(self): - def get_xoptions(*args): - # use subprocess module directly because test.support.script_helper adds - # "-X faulthandler" to the command line - args = (sys.executable, '-E') + args - args += ('-c', 'import sys; print(sys._xoptions)') - out = subprocess.check_output(args) - opts = eval(out.splitlines()[0]) - return opts - - opts = get_xoptions() - self.assertEqual(opts, {}) - - opts = get_xoptions('-Xa', '-Xb=c,d=e') - self.assertEqual(opts, {'a': True, 'b': 'c,d=e'}) - - def test_showrefcount(self): - def run_python(*args): - # this is similar to assert_python_ok but doesn't strip - # the refcount from stderr. It can be replaced once - # assert_python_ok stops doing that. - cmd = [sys.executable] - cmd.extend(args) - PIPE = subprocess.PIPE - p = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE) - out, err = p.communicate() - p.stdout.close() - p.stderr.close() - rc = p.returncode - self.assertEqual(rc, 0) - return rc, out, err - code = 'import sys; print(sys._xoptions)' - # normally the refcount is hidden - rc, out, err = run_python('-c', code) - self.assertEqual(out.rstrip(), b'{}') - self.assertEqual(err, b'') - # "-X showrefcount" shows the refcount, but only in debug builds - rc, out, err = run_python('-X', 'showrefcount', '-c', code) - self.assertEqual(out.rstrip(), b"{'showrefcount': True}") - if Py_DEBUG: - self.assertRegex(err, br'^\[\d+ refs, \d+ blocks\]') - else: - self.assertEqual(err, b'') - - def test_run_module(self): - # Test expected operation of the '-m' switch - # Switch needs an argument - assert_python_failure('-m') - # Check we get an error for a nonexistent module - assert_python_failure('-m', 'fnord43520xyz') - # Check the runpy module also gives an error for - # a nonexistent module - assert_python_failure('-m', 'runpy', 'fnord43520xyz') - # All good if module is located and run successfully - assert_python_ok('-m', 'timeit', '-n', '1') - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_run_module_bug1764407(self): - # -m and -i need to play well together - # Runs the timeit module and checks the __main__ - # namespace has been populated appropriately - p = spawn_python('-i', '-m', 'timeit', '-n', '1') - p.stdin.write(b'Timer\n') - p.stdin.write(b'exit()\n') - data = kill_python(p) - self.assertTrue(data.find(b'1 loop') != -1) - self.assertTrue(data.find(b'__main__.Timer') != -1) - - def test_run_code(self): - # Test expected operation of the '-c' switch - # Switch needs an argument - assert_python_failure('-c') - # Check we get an error for an uncaught exception - assert_python_failure('-c', 'raise Exception') - # All good if execution is successful - assert_python_ok('-c', 'pass') - - @unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII') - def test_non_ascii(self): - # Test handling of non-ascii data - command = ("assert(ord(%r) == %s)" - % (os_helper.FS_NONASCII, ord(os_helper.FS_NONASCII))) - assert_python_ok('-c', command) - - # On Windows, pass bytes to subprocess doesn't test how Python decodes the - # command line, but how subprocess does decode bytes to unicode. Python - # doesn't decode the command line because Windows provides directly the - # arguments as unicode (using wmain() instead of main()). - # TODO: RUSTPYTHON - @unittest.expectedFailure - @unittest.skipIf(sys.platform == 'win32', - 'Windows has a native unicode API') - def test_undecodable_code(self): - undecodable = b"\xff" - env = os.environ.copy() - # Use C locale to get ascii for the locale encoding - env['LC_ALL'] = 'C' - env['PYTHONCOERCECLOCALE'] = '0' - code = ( - b'import locale; ' - b'print(ascii("' + undecodable + b'"), ' - b'locale.getpreferredencoding())') - p = subprocess.Popen( - [sys.executable, "-c", code], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - env=env) - stdout, stderr = p.communicate() - if p.returncode == 1: - # _Py_char2wchar() decoded b'\xff' as '\udcff' (b'\xff' is not - # decodable from ASCII) and run_command() failed on - # PyUnicode_AsUTF8String(). This is the expected behaviour on - # Linux. - pattern = b"Unable to decode the command from the command line:" - elif p.returncode == 0: - # _Py_char2wchar() decoded b'\xff' as '\xff' even if the locale is - # C and the locale encoding is ASCII. It occurs on FreeBSD, Solaris - # and Mac OS X. - pattern = b"'\\xff' " - # The output is followed by the encoding name, an alias to ASCII. - # Examples: "US-ASCII" or "646" (ISO 646, on Solaris). - else: - raise AssertionError("Unknown exit code: %s, output=%a" % (p.returncode, stdout)) - if not stdout.startswith(pattern): - raise AssertionError("%a doesn't start with %a" % (stdout, pattern)) - - @unittest.skip("TODO: RUSTPYTHON, thread 'main' panicked at 'unexpected invalid UTF-8 code point'") - @unittest.skipIf(sys.platform == 'win32', - 'Windows has a native unicode API') - def test_invalid_utf8_arg(self): - # bpo-35883: Py_DecodeLocale() must escape b'\xfd\xbf\xbf\xbb\xba\xba' - # byte sequence with surrogateescape rather than decoding it as the - # U+7fffbeba character which is outside the [U+0000; U+10ffff] range of - # Python Unicode characters. - # - # Test with default config, in the C locale, in the Python UTF-8 Mode. - code = 'import sys, os; s=os.fsencode(sys.argv[1]); print(ascii(s))' - base_cmd = [sys.executable, '-c', code] - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def run_default(arg): - cmd = [sys.executable, '-c', code, arg] - return subprocess.run(cmd, stdout=subprocess.PIPE, text=True) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def run_c_locale(arg): - cmd = [sys.executable, '-c', code, arg] - env = dict(os.environ) - env['LC_ALL'] = 'C' - return subprocess.run(cmd, stdout=subprocess.PIPE, - text=True, env=env) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def run_utf8_mode(arg): - cmd = [sys.executable, '-X', 'utf8', '-c', code, arg] - return subprocess.run(cmd, stdout=subprocess.PIPE, text=True) - - valid_utf8 = 'e:\xe9, euro:\u20ac, non-bmp:\U0010ffff'.encode('utf-8') - # invalid UTF-8 byte sequences with a valid UTF-8 sequence - # in the middle. - invalid_utf8 = ( - b'\xff' # invalid byte - b'\xc3\xff' # invalid byte sequence - b'\xc3\xa9' # valid utf-8: U+00E9 character - b'\xed\xa0\x80' # lone surrogate character (invalid) - b'\xfd\xbf\xbf\xbb\xba\xba' # character outside [U+0000; U+10ffff] - ) - test_args = [valid_utf8, invalid_utf8] - - for run_cmd in (run_default, run_c_locale, run_utf8_mode): - with self.subTest(run_cmd=run_cmd): - for arg in test_args: - proc = run_cmd(arg) - self.assertEqual(proc.stdout.rstrip(), ascii(arg)) - - @unittest.skipUnless((sys.platform == 'darwin' or - support.is_android), 'test specific to Mac OS X and Android') - def test_osx_android_utf8(self): - text = 'e:\xe9, euro:\u20ac, non-bmp:\U0010ffff'.encode('utf-8') - code = "import sys; print(ascii(sys.argv[1]))" - - decoded = text.decode('utf-8', 'surrogateescape') - expected = ascii(decoded).encode('ascii') + b'\n' - - env = os.environ.copy() - # C locale gives ASCII locale encoding, but Python uses UTF-8 - # to parse the command line arguments on Mac OS X and Android. - env['LC_ALL'] = 'C' - - p = subprocess.Popen( - (sys.executable, "-c", code, text), - stdout=subprocess.PIPE, - env=env) - stdout, stderr = p.communicate() - self.assertEqual(stdout, expected) - self.assertEqual(p.returncode, 0) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_unbuffered_output(self): - # Test expected operation of the '-u' switch - for stream in ('stdout', 'stderr'): - # Binary is unbuffered - code = ("import os, sys; sys.%s.buffer.write(b'x'); os._exit(0)" - % stream) - rc, out, err = assert_python_ok('-u', '-c', code) - data = err if stream == 'stderr' else out - self.assertEqual(data, b'x', "binary %s not unbuffered" % stream) - # Text is unbuffered - code = ("import os, sys; sys.%s.write('x'); os._exit(0)" - % stream) - rc, out, err = assert_python_ok('-u', '-c', code) - data = err if stream == 'stderr' else out - self.assertEqual(data, b'x', "text %s not unbuffered" % stream) - - def test_unbuffered_input(self): - # sys.stdin still works with '-u' - code = ("import sys; sys.stdout.write(sys.stdin.read(1))") - p = spawn_python('-u', '-c', code) - p.stdin.write(b'x') - p.stdin.flush() - data, rc = _kill_python_and_exit_code(p) - self.assertEqual(rc, 0) - self.assertTrue(data.startswith(b'x'), data) - - def test_large_PYTHONPATH(self): - path1 = "ABCDE" * 100 - path2 = "FGHIJ" * 100 - path = path1 + os.pathsep + path2 - - code = """if 1: - import sys - path = ":".join(sys.path) - path = path.encode("ascii", "backslashreplace") - sys.stdout.buffer.write(path)""" - rc, out, err = assert_python_ok('-S', '-c', code, - PYTHONPATH=path) - self.assertIn(path1.encode('ascii'), out) - self.assertIn(path2.encode('ascii'), out) - - def test_empty_PYTHONPATH_issue16309(self): - # On Posix, it is documented that setting PATH to the - # empty string is equivalent to not setting PATH at all, - # which is an exception to the rule that in a string like - # "/bin::/usr/bin" the empty string in the middle gets - # interpreted as '.' - code = """if 1: - import sys - path = ":".join(sys.path) - path = path.encode("ascii", "backslashreplace") - sys.stdout.buffer.write(path)""" - rc1, out1, err1 = assert_python_ok('-c', code, PYTHONPATH="") - rc2, out2, err2 = assert_python_ok('-c', code, __isolated=False) - # regarding to Posix specification, outputs should be equal - # for empty and unset PYTHONPATH - self.assertEqual(out1, out2) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_displayhook_unencodable(self): - for encoding in ('ascii', 'latin-1', 'utf-8'): - env = os.environ.copy() - env['PYTHONIOENCODING'] = encoding - p = subprocess.Popen( - [sys.executable, '-i'], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - env=env) - # non-ascii, surrogate, non-BMP printable, non-BMP unprintable - text = "a=\xe9 b=\uDC80 c=\U00010000 d=\U0010FFFF" - p.stdin.write(ascii(text).encode('ascii') + b"\n") - p.stdin.write(b'exit()\n') - data = kill_python(p) - escaped = repr(text).encode(encoding, 'backslashreplace') - self.assertIn(escaped, data) - - def check_input(self, code, expected): - with tempfile.NamedTemporaryFile("wb+") as stdin: - sep = os.linesep.encode('ASCII') - stdin.write(sep.join((b'abc', b'def'))) - stdin.flush() - stdin.seek(0) - with subprocess.Popen( - (sys.executable, "-c", code), - stdin=stdin, stdout=subprocess.PIPE) as proc: - stdout, stderr = proc.communicate() - self.assertEqual(stdout.rstrip(), expected) - - @unittest.skipIf(sys.platform == "win32", "AssertionError: b"'abc\\r'" != b"'abc'"") - def test_stdin_readline(self): - # Issue #11272: check that sys.stdin.readline() replaces '\r\n' by '\n' - # on Windows (sys.stdin is opened in binary mode) - self.check_input( - "import sys; print(repr(sys.stdin.readline()))", - b"'abc\\n'") - - @unittest.skipIf(sys.platform == "win32", "AssertionError: b"'abc\\r'" != b"'abc'"") - def test_builtin_input(self): - # Issue #11272: check that input() strips newlines ('\n' or '\r\n') - self.check_input( - "print(repr(input()))", - b"'abc'") - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_output_newline(self): - # Issue 13119 Newline for print() should be \r\n on Windows. - code = """if 1: - import sys - print(1) - print(2) - print(3, file=sys.stderr) - print(4, file=sys.stderr)""" - rc, out, err = assert_python_ok('-c', code) - - if sys.platform == 'win32': - self.assertEqual(b'1\r\n2\r\n', out) - self.assertEqual(b'3\r\n4', err) - else: - self.assertEqual(b'1\n2\n', out) - self.assertEqual(b'3\n4', err) - - def test_unmached_quote(self): - # Issue #10206: python program starting with unmatched quote - # spewed spaces to stdout - rc, out, err = assert_python_failure('-c', "'") - self.assertRegex(err.decode('ascii', 'ignore'), 'SyntaxError') - self.assertEqual(b'', out) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_stdout_flush_at_shutdown(self): - # Issue #5319: if stdout.flush() fails at shutdown, an error should - # be printed out. - code = """if 1: - import os, sys, test.support - test.support.SuppressCrashReport().__enter__() - sys.stdout.write('x') - os.close(sys.stdout.fileno())""" - rc, out, err = assert_python_failure('-c', code) - self.assertEqual(b'', out) - self.assertEqual(120, rc) - self.assertRegex(err.decode('ascii', 'ignore'), - 'Exception ignored in.*\nOSError: .*') - - def test_closed_stdout(self): - # Issue #13444: if stdout has been explicitly closed, we should - # not attempt to flush it at shutdown. - code = "import sys; sys.stdout.close()" - rc, out, err = assert_python_ok('-c', code) - self.assertEqual(b'', err) - - # Issue #7111: Python should work without standard streams - - @unittest.skipIf(os.name != 'posix', "test needs POSIX semantics") - @unittest.skipIf(sys.platform == "vxworks", - "test needs preexec support in subprocess.Popen") - def _test_no_stdio(self, streams): - code = """if 1: - import os, sys - for i, s in enumerate({streams}): - if getattr(sys, s) is not None: - os._exit(i + 1) - os._exit(42)""".format(streams=streams) - def preexec(): - if 'stdin' in streams: - os.close(0) - if 'stdout' in streams: - os.close(1) - if 'stderr' in streams: - os.close(2) - p = subprocess.Popen( - [sys.executable, "-E", "-c", code], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - preexec_fn=preexec) - out, err = p.communicate() - self.assertEqual(support.strip_python_stderr(err), b'') - self.assertEqual(p.returncode, 42) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_no_stdin(self): - self._test_no_stdio(['stdin']) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_no_stdout(self): - self._test_no_stdio(['stdout']) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_no_stderr(self): - self._test_no_stdio(['stderr']) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_no_std_streams(self): - self._test_no_stdio(['stdin', 'stdout', 'stderr']) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_hash_randomization(self): - # Verify that -R enables hash randomization: - self.verify_valid_flag('-R') - hashes = [] - if os.environ.get('PYTHONHASHSEED', 'random') != 'random': - env = dict(os.environ) # copy - # We need to test that it is enabled by default without - # the environment variable enabling it for us. - del env['PYTHONHASHSEED'] - env['__cleanenv'] = '1' # consumed by assert_python_ok() - else: - env = {} - for i in range(3): - code = 'print(hash("spam"))' - rc, out, err = assert_python_ok('-c', code, **env) - self.assertEqual(rc, 0) - hashes.append(out) - hashes = sorted(set(hashes)) # uniq - # Rare chance of failure due to 3 random seeds honestly being equal. - self.assertGreater(len(hashes), 1, - msg='3 runs produced an identical random hash ' - ' for "spam": {}'.format(hashes)) - - # Verify that sys.flags contains hash_randomization - code = 'import sys; print("random is", sys.flags.hash_randomization)' - rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='') - self.assertIn(b'random is 1', out) - - rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='random') - self.assertIn(b'random is 1', out) - - rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='0') - self.assertIn(b'random is 0', out) - - rc, out, err = assert_python_ok('-R', '-c', code, PYTHONHASHSEED='0') - self.assertIn(b'random is 1', out) - - def test_del___main__(self): - # Issue #15001: PyRun_SimpleFileExFlags() did crash because it kept a - # borrowed reference to the dict of __main__ module and later modify - # the dict whereas the module was destroyed - filename = os_helper.TESTFN - self.addCleanup(os_helper.unlink, filename) - with open(filename, "w") as script: - print("import sys", file=script) - print("del sys.modules['__main__']", file=script) - assert_python_ok(filename) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_unknown_options(self): - rc, out, err = assert_python_failure('-E', '-z') - self.assertIn(b'Unknown option: -z', err) - self.assertEqual(err.splitlines().count(b'Unknown option: -z'), 1) - self.assertEqual(b'', out) - # Add "without='-E'" to prevent _assert_python to append -E - # to env_vars and change the output of stderr - rc, out, err = assert_python_failure('-z', without='-E') - self.assertIn(b'Unknown option: -z', err) - self.assertEqual(err.splitlines().count(b'Unknown option: -z'), 1) - self.assertEqual(b'', out) - rc, out, err = assert_python_failure('-a', '-z', without='-E') - self.assertIn(b'Unknown option: -a', err) - # only the first unknown option is reported - self.assertNotIn(b'Unknown option: -z', err) - self.assertEqual(err.splitlines().count(b'Unknown option: -a'), 1) - self.assertEqual(b'', out) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - @unittest.skipIf(interpreter_requires_environment(), - 'Cannot run -I tests when PYTHON env vars are required.') - def test_isolatedmode(self): - self.verify_valid_flag('-I') - self.verify_valid_flag('-IEs') - rc, out, err = assert_python_ok('-I', '-c', - 'from sys import flags as f; ' - 'print(f.no_user_site, f.ignore_environment, f.isolated)', - # dummyvar to prevent extraneous -E - dummyvar="") - self.assertEqual(out.strip(), b'1 1 1') - with os_helper.temp_cwd() as tmpdir: - fake = os.path.join(tmpdir, "uuid.py") - main = os.path.join(tmpdir, "main.py") - with open(fake, "w") as f: - f.write("raise RuntimeError('isolated mode test')\n") - with open(main, "w") as f: - f.write("import uuid\n") - f.write("print('ok')\n") - self.assertRaises(subprocess.CalledProcessError, - subprocess.check_output, - [sys.executable, main], cwd=tmpdir, - stderr=subprocess.DEVNULL) - out = subprocess.check_output([sys.executable, "-I", main], - cwd=tmpdir) - self.assertEqual(out.strip(), b"ok") - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_sys_flags_set(self): - # Issue 31845: a startup refactoring broke reading flags from env vars - for value, expected in (("", 0), ("1", 1), ("text", 1), ("2", 2)): - env_vars = dict( - PYTHONDEBUG=value, - PYTHONOPTIMIZE=value, - PYTHONDONTWRITEBYTECODE=value, - PYTHONVERBOSE=value, - ) - dont_write_bytecode = int(bool(value)) - code = ( - "import sys; " - "sys.stderr.write(str(sys.flags)); " - f"""sys.exit(not ( - sys.flags.debug == sys.flags.optimize == - sys.flags.verbose == - {expected} - and sys.flags.dont_write_bytecode == {dont_write_bytecode} - ))""" - ) - with self.subTest(envar_value=value): - assert_python_ok('-c', code, **env_vars) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_set_pycache_prefix(self): - # sys.pycache_prefix can be set from either -X pycache_prefix or - # PYTHONPYCACHEPREFIX env var, with the former taking precedence. - NO_VALUE = object() # `-X pycache_prefix` with no `=PATH` - cases = [ - # (PYTHONPYCACHEPREFIX, -X pycache_prefix, sys.pycache_prefix) - (None, None, None), - ('foo', None, 'foo'), - (None, 'bar', 'bar'), - ('foo', 'bar', 'bar'), - ('foo', '', None), - ('foo', NO_VALUE, None), - ] - for envval, opt, expected in cases: - exp_clause = "is None" if expected is None else f'== "{expected}"' - code = f"import sys; sys.exit(not sys.pycache_prefix {exp_clause})" - args = ['-c', code] - env = {} if envval is None else {'PYTHONPYCACHEPREFIX': envval} - if opt is NO_VALUE: - args[:0] = ['-X', 'pycache_prefix'] - elif opt is not None: - args[:0] = ['-X', f'pycache_prefix={opt}'] - with self.subTest(envval=envval, opt=opt): - with os_helper.temp_cwd(): - assert_python_ok(*args, **env) - - def run_xdev(self, *args, check_exitcode=True, xdev=True): - env = dict(os.environ) - env.pop('PYTHONWARNINGS', None) - env.pop('PYTHONDEVMODE', None) - env.pop('PYTHONMALLOC', None) - - if xdev: - args = (sys.executable, '-X', 'dev', *args) - else: - args = (sys.executable, *args) - proc = subprocess.run(args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - env=env) - if check_exitcode: - self.assertEqual(proc.returncode, 0, proc) - return proc.stdout.rstrip() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_xdev(self): - # sys.flags.dev_mode - code = "import sys; print(sys.flags.dev_mode)" - out = self.run_xdev("-c", code, xdev=False) - self.assertEqual(out, "False") - out = self.run_xdev("-c", code) - self.assertEqual(out, "True") - - # Warnings - code = ("import warnings; " - "print(' '.join('%s::%s' % (f[0], f[2].__name__) " - "for f in warnings.filters))") - if Py_DEBUG: - expected_filters = "default::Warning" - else: - expected_filters = ("default::Warning " - "default::DeprecationWarning " - "ignore::DeprecationWarning " - "ignore::PendingDeprecationWarning " - "ignore::ImportWarning " - "ignore::ResourceWarning") - - out = self.run_xdev("-c", code) - self.assertEqual(out, expected_filters) - - out = self.run_xdev("-b", "-c", code) - self.assertEqual(out, f"default::BytesWarning {expected_filters}") - - out = self.run_xdev("-bb", "-c", code) - self.assertEqual(out, f"error::BytesWarning {expected_filters}") - - out = self.run_xdev("-Werror", "-c", code) - self.assertEqual(out, f"error::Warning {expected_filters}") - - # Memory allocator debug hooks - try: - import _testcapi - except ImportError: - pass - else: - code = "import _testcapi; print(_testcapi.pymem_getallocatorsname())" - with support.SuppressCrashReport(): - out = self.run_xdev("-c", code, check_exitcode=False) - if support.with_pymalloc(): - alloc_name = "pymalloc_debug" - else: - alloc_name = "malloc_debug" - self.assertEqual(out, alloc_name) - - # Faulthandler - try: - import faulthandler - except ImportError: - pass - else: - code = "import faulthandler; print(faulthandler.is_enabled())" - out = self.run_xdev("-c", code) - self.assertEqual(out, "True") - - def check_warnings_filters(self, cmdline_option, envvar, use_pywarning=False): - if use_pywarning: - code = ("import sys; from test.support.import_helper import import_fresh_module; " - "warnings = import_fresh_module('warnings', blocked=['_warnings']); ") - else: - code = "import sys, warnings; " - code += ("print(' '.join('%s::%s' % (f[0], f[2].__name__) " - "for f in warnings.filters))") - args = (sys.executable, '-W', cmdline_option, '-bb', '-c', code) - env = dict(os.environ) - env.pop('PYTHONDEVMODE', None) - env["PYTHONWARNINGS"] = envvar - proc = subprocess.run(args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - env=env) - self.assertEqual(proc.returncode, 0, proc) - return proc.stdout.rstrip() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_warnings_filter_precedence(self): - expected_filters = ("error::BytesWarning " - "once::UserWarning " - "always::UserWarning") - if not Py_DEBUG: - expected_filters += (" " - "default::DeprecationWarning " - "ignore::DeprecationWarning " - "ignore::PendingDeprecationWarning " - "ignore::ImportWarning " - "ignore::ResourceWarning") - - out = self.check_warnings_filters("once::UserWarning", - "always::UserWarning") - self.assertEqual(out, expected_filters) - - out = self.check_warnings_filters("once::UserWarning", - "always::UserWarning", - use_pywarning=True) - self.assertEqual(out, expected_filters) - - def check_pythonmalloc(self, env_var, name): - code = 'import _testcapi; print(_testcapi.pymem_getallocatorsname())' - env = dict(os.environ) - env.pop('PYTHONDEVMODE', None) - if env_var is not None: - env['PYTHONMALLOC'] = env_var - else: - env.pop('PYTHONMALLOC', None) - args = (sys.executable, '-c', code) - proc = subprocess.run(args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - env=env) - self.assertEqual(proc.stdout.rstrip(), name) - self.assertEqual(proc.returncode, 0) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_pythonmalloc(self): - # Test the PYTHONMALLOC environment variable - pymalloc = support.with_pymalloc() - if pymalloc: - default_name = 'pymalloc_debug' if Py_DEBUG else 'pymalloc' - default_name_debug = 'pymalloc_debug' - else: - default_name = 'malloc_debug' if Py_DEBUG else 'malloc' - default_name_debug = 'malloc_debug' - - tests = [ - (None, default_name), - ('debug', default_name_debug), - ('malloc', 'malloc'), - ('malloc_debug', 'malloc_debug'), - ] - if pymalloc: - tests.extend(( - ('pymalloc', 'pymalloc'), - ('pymalloc_debug', 'pymalloc_debug'), - )) - - for env_var, name in tests: - with self.subTest(env_var=env_var, name=name): - self.check_pythonmalloc(env_var, name) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_pythondevmode_env(self): - # Test the PYTHONDEVMODE environment variable - code = "import sys; print(sys.flags.dev_mode)" - env = dict(os.environ) - env.pop('PYTHONDEVMODE', None) - args = (sys.executable, '-c', code) - - proc = subprocess.run(args, stdout=subprocess.PIPE, - universal_newlines=True, env=env) - self.assertEqual(proc.stdout.rstrip(), 'False') - self.assertEqual(proc.returncode, 0, proc) - - env['PYTHONDEVMODE'] = '1' - proc = subprocess.run(args, stdout=subprocess.PIPE, - universal_newlines=True, env=env) - self.assertEqual(proc.stdout.rstrip(), 'True') - self.assertEqual(proc.returncode, 0, proc) - - @unittest.skipUnless(sys.platform == 'win32', - 'bpo-32457 only applies on Windows') - def test_argv0_normalization(self): - args = sys.executable, '-c', 'print(0)' - prefix, exe = os.path.split(sys.executable) - executable = prefix + '\\.\\.\\.\\' + exe - - proc = subprocess.run(args, stdout=subprocess.PIPE, - executable=executable) - self.assertEqual(proc.returncode, 0, proc) - self.assertEqual(proc.stdout.strip(), b'0') - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_parsing_error(self): - args = [sys.executable, '-I', '--unknown-option'] - proc = subprocess.run(args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True) - err_msg = "unknown option --unknown-option\nusage: " - self.assertTrue(proc.stderr.startswith(err_msg), proc.stderr) - self.assertNotEqual(proc.returncode, 0) - - -@unittest.skipIf(interpreter_requires_environment(), - 'Cannot run -I tests when PYTHON env vars are required.') -class IgnoreEnvironmentTest(unittest.TestCase): - - def run_ignoring_vars(self, predicate, **env_vars): - # Runs a subprocess with -E set, even though we're passing - # specific environment variables - # Logical inversion to match predicate check to a zero return - # code indicating success - code = "import sys; sys.stderr.write(str(sys.flags)); sys.exit(not ({}))".format(predicate) - return assert_python_ok('-E', '-c', code, **env_vars) - - def test_ignore_PYTHONPATH(self): - path = "should_be_ignored" - self.run_ignoring_vars("'{}' not in sys.path".format(path), - PYTHONPATH=path) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_ignore_PYTHONHASHSEED(self): - self.run_ignoring_vars("sys.flags.hash_randomization == 1", - PYTHONHASHSEED="0") - - def test_sys_flags_not_set(self): - # Issue 31845: a startup refactoring broke reading flags from env vars - expected_outcome = """ - (sys.flags.debug == sys.flags.optimize == - sys.flags.dont_write_bytecode == sys.flags.verbose == 0) - """ - self.run_ignoring_vars( - expected_outcome, - PYTHONDEBUG="1", - PYTHONOPTIMIZE="1", - PYTHONDONTWRITEBYTECODE="1", - PYTHONVERBOSE="1", - ) - - -def test_main(): - support.run_unittest(CmdLineTest, IgnoreEnvironmentTest) - support.reap_children() - -if __name__ == "__main__": - test_main() +# Tests invocation of the interpreter with various command line arguments +# Most tests are executed with environment variables ignored +# See test_cmd_line_script.py for testing of script execution + +import os +import subprocess +import sys +import tempfile +import unittest +from test import support +from test.support.script_helper import ( + spawn_python, kill_python, assert_python_ok, assert_python_failure, + interpreter_requires_environment +) +from test.support import os_helper + + +# Debug build? +Py_DEBUG = hasattr(sys, "gettotalrefcount") + + +# XXX (ncoghlan): Move to script_helper and make consistent with run_python +def _kill_python_and_exit_code(p): + data = kill_python(p) + returncode = p.wait() + return data, returncode + +class CmdLineTest(unittest.TestCase): + def test_directories(self): + assert_python_failure('.') + assert_python_failure('< .') + + def verify_valid_flag(self, cmd_line): + rc, out, err = assert_python_ok(*cmd_line) + self.assertTrue(out == b'' or out.endswith(b'\n')) + self.assertNotIn(b'Traceback', out) + self.assertNotIn(b'Traceback', err) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_optimize(self): + self.verify_valid_flag('-O') + self.verify_valid_flag('-OO') + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_site_flag(self): + self.verify_valid_flag('-S') + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_usage(self): + rc, out, err = assert_python_ok('-h') + lines = out.splitlines() + self.assertIn(b'usage', lines[0]) + # The first line contains the program name, + # but the rest should be ASCII-only + b''.join(lines[1:]).decode('ascii') + + # NOTE: RUSTPYTHON version never starts with Python + @unittest.expectedFailure + def test_version(self): + version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii") + for switch in '-V', '--version', '-VV': + rc, out, err = assert_python_ok(switch) + self.assertFalse(err.startswith(version)) + self.assertTrue(out.startswith(version)) + + def test_verbose(self): + # -v causes imports to write to stderr. If the write to + # stderr itself causes an import to happen (for the output + # codec), a recursion loop can occur. + rc, out, err = assert_python_ok('-v') + self.assertNotIn(b'stack overflow', err) + rc, out, err = assert_python_ok('-vv') + self.assertNotIn(b'stack overflow', err) + + @unittest.skipIf(interpreter_requires_environment(), + 'Cannot run -E tests when PYTHON env vars are required.') + def test_xoptions(self): + def get_xoptions(*args): + # use subprocess module directly because test.support.script_helper adds + # "-X faulthandler" to the command line + args = (sys.executable, '-E') + args + args += ('-c', 'import sys; print(sys._xoptions)') + out = subprocess.check_output(args) + opts = eval(out.splitlines()[0]) + return opts + + opts = get_xoptions() + self.assertEqual(opts, {}) + + opts = get_xoptions('-Xa', '-Xb=c,d=e') + self.assertEqual(opts, {'a': True, 'b': 'c,d=e'}) + + def test_showrefcount(self): + def run_python(*args): + # this is similar to assert_python_ok but doesn't strip + # the refcount from stderr. It can be replaced once + # assert_python_ok stops doing that. + cmd = [sys.executable] + cmd.extend(args) + PIPE = subprocess.PIPE + p = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE) + out, err = p.communicate() + p.stdout.close() + p.stderr.close() + rc = p.returncode + self.assertEqual(rc, 0) + return rc, out, err + code = 'import sys; print(sys._xoptions)' + # normally the refcount is hidden + rc, out, err = run_python('-c', code) + self.assertEqual(out.rstrip(), b'{}') + self.assertEqual(err, b'') + # "-X showrefcount" shows the refcount, but only in debug builds + rc, out, err = run_python('-X', 'showrefcount', '-c', code) + self.assertEqual(out.rstrip(), b"{'showrefcount': True}") + if Py_DEBUG: + self.assertRegex(err, br'^\[\d+ refs, \d+ blocks\]') + else: + self.assertEqual(err, b'') + + def test_run_module(self): + # Test expected operation of the '-m' switch + # Switch needs an argument + assert_python_failure('-m') + # Check we get an error for a nonexistent module + assert_python_failure('-m', 'fnord43520xyz') + # Check the runpy module also gives an error for + # a nonexistent module + assert_python_failure('-m', 'runpy', 'fnord43520xyz') + # All good if module is located and run successfully + assert_python_ok('-m', 'timeit', '-n', '1') + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_run_module_bug1764407(self): + # -m and -i need to play well together + # Runs the timeit module and checks the __main__ + # namespace has been populated appropriately + p = spawn_python('-i', '-m', 'timeit', '-n', '1') + p.stdin.write(b'Timer\n') + p.stdin.write(b'exit()\n') + data = kill_python(p) + self.assertTrue(data.find(b'1 loop') != -1) + self.assertTrue(data.find(b'__main__.Timer') != -1) + + def test_run_code(self): + # Test expected operation of the '-c' switch + # Switch needs an argument + assert_python_failure('-c') + # Check we get an error for an uncaught exception + assert_python_failure('-c', 'raise Exception') + # All good if execution is successful + assert_python_ok('-c', 'pass') + + @unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII') + def test_non_ascii(self): + # Test handling of non-ascii data + command = ("assert(ord(%r) == %s)" + % (os_helper.FS_NONASCII, ord(os_helper.FS_NONASCII))) + assert_python_ok('-c', command) + + # On Windows, pass bytes to subprocess doesn't test how Python decodes the + # command line, but how subprocess does decode bytes to unicode. Python + # doesn't decode the command line because Windows provides directly the + # arguments as unicode (using wmain() instead of main()). + # TODO: RUSTPYTHON + @unittest.expectedFailure + @unittest.skipIf(sys.platform == 'win32', + 'Windows has a native unicode API') + def test_undecodable_code(self): + undecodable = b"\xff" + env = os.environ.copy() + # Use C locale to get ascii for the locale encoding + env['LC_ALL'] = 'C' + env['PYTHONCOERCECLOCALE'] = '0' + code = ( + b'import locale; ' + b'print(ascii("' + undecodable + b'"), ' + b'locale.getpreferredencoding())') + p = subprocess.Popen( + [sys.executable, "-c", code], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + env=env) + stdout, stderr = p.communicate() + if p.returncode == 1: + # _Py_char2wchar() decoded b'\xff' as '\udcff' (b'\xff' is not + # decodable from ASCII) and run_command() failed on + # PyUnicode_AsUTF8String(). This is the expected behaviour on + # Linux. + pattern = b"Unable to decode the command from the command line:" + elif p.returncode == 0: + # _Py_char2wchar() decoded b'\xff' as '\xff' even if the locale is + # C and the locale encoding is ASCII. It occurs on FreeBSD, Solaris + # and Mac OS X. + pattern = b"'\\xff' " + # The output is followed by the encoding name, an alias to ASCII. + # Examples: "US-ASCII" or "646" (ISO 646, on Solaris). + else: + raise AssertionError("Unknown exit code: %s, output=%a" % (p.returncode, stdout)) + if not stdout.startswith(pattern): + raise AssertionError("%a doesn't start with %a" % (stdout, pattern)) + + @unittest.skip("TODO: RUSTPYTHON, thread 'main' panicked at 'unexpected invalid UTF-8 code point'") + @unittest.skipIf(sys.platform == 'win32', + 'Windows has a native unicode API') + def test_invalid_utf8_arg(self): + # bpo-35883: Py_DecodeLocale() must escape b'\xfd\xbf\xbf\xbb\xba\xba' + # byte sequence with surrogateescape rather than decoding it as the + # U+7fffbeba character which is outside the [U+0000; U+10ffff] range of + # Python Unicode characters. + # + # Test with default config, in the C locale, in the Python UTF-8 Mode. + code = 'import sys, os; s=os.fsencode(sys.argv[1]); print(ascii(s))' + base_cmd = [sys.executable, '-c', code] + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def run_default(arg): + cmd = [sys.executable, '-c', code, arg] + return subprocess.run(cmd, stdout=subprocess.PIPE, text=True) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def run_c_locale(arg): + cmd = [sys.executable, '-c', code, arg] + env = dict(os.environ) + env['LC_ALL'] = 'C' + return subprocess.run(cmd, stdout=subprocess.PIPE, + text=True, env=env) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def run_utf8_mode(arg): + cmd = [sys.executable, '-X', 'utf8', '-c', code, arg] + return subprocess.run(cmd, stdout=subprocess.PIPE, text=True) + + valid_utf8 = 'e:\xe9, euro:\u20ac, non-bmp:\U0010ffff'.encode('utf-8') + # invalid UTF-8 byte sequences with a valid UTF-8 sequence + # in the middle. + invalid_utf8 = ( + b'\xff' # invalid byte + b'\xc3\xff' # invalid byte sequence + b'\xc3\xa9' # valid utf-8: U+00E9 character + b'\xed\xa0\x80' # lone surrogate character (invalid) + b'\xfd\xbf\xbf\xbb\xba\xba' # character outside [U+0000; U+10ffff] + ) + test_args = [valid_utf8, invalid_utf8] + + for run_cmd in (run_default, run_c_locale, run_utf8_mode): + with self.subTest(run_cmd=run_cmd): + for arg in test_args: + proc = run_cmd(arg) + self.assertEqual(proc.stdout.rstrip(), ascii(arg)) + + @unittest.skipUnless((sys.platform == 'darwin' or + support.is_android), 'test specific to Mac OS X and Android') + def test_osx_android_utf8(self): + text = 'e:\xe9, euro:\u20ac, non-bmp:\U0010ffff'.encode('utf-8') + code = "import sys; print(ascii(sys.argv[1]))" + + decoded = text.decode('utf-8', 'surrogateescape') + expected = ascii(decoded).encode('ascii') + b'\n' + + env = os.environ.copy() + # C locale gives ASCII locale encoding, but Python uses UTF-8 + # to parse the command line arguments on Mac OS X and Android. + env['LC_ALL'] = 'C' + + p = subprocess.Popen( + (sys.executable, "-c", code, text), + stdout=subprocess.PIPE, + env=env) + stdout, stderr = p.communicate() + self.assertEqual(stdout, expected) + self.assertEqual(p.returncode, 0) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_unbuffered_output(self): + # Test expected operation of the '-u' switch + for stream in ('stdout', 'stderr'): + # Binary is unbuffered + code = ("import os, sys; sys.%s.buffer.write(b'x'); os._exit(0)" + % stream) + rc, out, err = assert_python_ok('-u', '-c', code) + data = err if stream == 'stderr' else out + self.assertEqual(data, b'x', "binary %s not unbuffered" % stream) + # Text is unbuffered + code = ("import os, sys; sys.%s.write('x'); os._exit(0)" + % stream) + rc, out, err = assert_python_ok('-u', '-c', code) + data = err if stream == 'stderr' else out + self.assertEqual(data, b'x', "text %s not unbuffered" % stream) + + def test_unbuffered_input(self): + # sys.stdin still works with '-u' + code = ("import sys; sys.stdout.write(sys.stdin.read(1))") + p = spawn_python('-u', '-c', code) + p.stdin.write(b'x') + p.stdin.flush() + data, rc = _kill_python_and_exit_code(p) + self.assertEqual(rc, 0) + self.assertTrue(data.startswith(b'x'), data) + + def test_large_PYTHONPATH(self): + path1 = "ABCDE" * 100 + path2 = "FGHIJ" * 100 + path = path1 + os.pathsep + path2 + + code = """if 1: + import sys + path = ":".join(sys.path) + path = path.encode("ascii", "backslashreplace") + sys.stdout.buffer.write(path)""" + rc, out, err = assert_python_ok('-S', '-c', code, + PYTHONPATH=path) + self.assertIn(path1.encode('ascii'), out) + self.assertIn(path2.encode('ascii'), out) + + def test_empty_PYTHONPATH_issue16309(self): + # On Posix, it is documented that setting PATH to the + # empty string is equivalent to not setting PATH at all, + # which is an exception to the rule that in a string like + # "/bin::/usr/bin" the empty string in the middle gets + # interpreted as '.' + code = """if 1: + import sys + path = ":".join(sys.path) + path = path.encode("ascii", "backslashreplace") + sys.stdout.buffer.write(path)""" + rc1, out1, err1 = assert_python_ok('-c', code, PYTHONPATH="") + rc2, out2, err2 = assert_python_ok('-c', code, __isolated=False) + # regarding to Posix specification, outputs should be equal + # for empty and unset PYTHONPATH + self.assertEqual(out1, out2) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_displayhook_unencodable(self): + for encoding in ('ascii', 'latin-1', 'utf-8'): + env = os.environ.copy() + env['PYTHONIOENCODING'] = encoding + p = subprocess.Popen( + [sys.executable, '-i'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env) + # non-ascii, surrogate, non-BMP printable, non-BMP unprintable + text = "a=\xe9 b=\uDC80 c=\U00010000 d=\U0010FFFF" + p.stdin.write(ascii(text).encode('ascii') + b"\n") + p.stdin.write(b'exit()\n') + data = kill_python(p) + escaped = repr(text).encode(encoding, 'backslashreplace') + self.assertIn(escaped, data) + + def check_input(self, code, expected): + with tempfile.NamedTemporaryFile("wb+") as stdin: + sep = os.linesep.encode('ASCII') + stdin.write(sep.join((b'abc', b'def'))) + stdin.flush() + stdin.seek(0) + with subprocess.Popen( + (sys.executable, "-c", code), + stdin=stdin, stdout=subprocess.PIPE) as proc: + stdout, stderr = proc.communicate() + self.assertEqual(stdout.rstrip(), expected) + + @unittest.skipIf(sys.platform == "win32", "AssertionError: b"'abc\\r'" != b"'abc'"") + def test_stdin_readline(self): + # Issue #11272: check that sys.stdin.readline() replaces '\r\n' by '\n' + # on Windows (sys.stdin is opened in binary mode) + self.check_input( + "import sys; print(repr(sys.stdin.readline()))", + b"'abc\\n'") + + @unittest.skipIf(sys.platform == "win32", "AssertionError: b"'abc\\r'" != b"'abc'"") + def test_builtin_input(self): + # Issue #11272: check that input() strips newlines ('\n' or '\r\n') + self.check_input( + "print(repr(input()))", + b"'abc'") + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_output_newline(self): + # Issue 13119 Newline for print() should be \r\n on Windows. + code = """if 1: + import sys + print(1) + print(2) + print(3, file=sys.stderr) + print(4, file=sys.stderr)""" + rc, out, err = assert_python_ok('-c', code) + + if sys.platform == 'win32': + self.assertEqual(b'1\r\n2\r\n', out) + self.assertEqual(b'3\r\n4', err) + else: + self.assertEqual(b'1\n2\n', out) + self.assertEqual(b'3\n4', err) + + def test_unmached_quote(self): + # Issue #10206: python program starting with unmatched quote + # spewed spaces to stdout + rc, out, err = assert_python_failure('-c', "'") + self.assertRegex(err.decode('ascii', 'ignore'), 'SyntaxError') + self.assertEqual(b'', out) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_stdout_flush_at_shutdown(self): + # Issue #5319: if stdout.flush() fails at shutdown, an error should + # be printed out. + code = """if 1: + import os, sys, test.support + test.support.SuppressCrashReport().__enter__() + sys.stdout.write('x') + os.close(sys.stdout.fileno())""" + rc, out, err = assert_python_failure('-c', code) + self.assertEqual(b'', out) + self.assertEqual(120, rc) + self.assertRegex(err.decode('ascii', 'ignore'), + 'Exception ignored in.*\nOSError: .*') + + def test_closed_stdout(self): + # Issue #13444: if stdout has been explicitly closed, we should + # not attempt to flush it at shutdown. + code = "import sys; sys.stdout.close()" + rc, out, err = assert_python_ok('-c', code) + self.assertEqual(b'', err) + + # Issue #7111: Python should work without standard streams + + @unittest.skipIf(os.name != 'posix', "test needs POSIX semantics") + @unittest.skipIf(sys.platform == "vxworks", + "test needs preexec support in subprocess.Popen") + def _test_no_stdio(self, streams): + code = """if 1: + import os, sys + for i, s in enumerate({streams}): + if getattr(sys, s) is not None: + os._exit(i + 1) + os._exit(42)""".format(streams=streams) + def preexec(): + if 'stdin' in streams: + os.close(0) + if 'stdout' in streams: + os.close(1) + if 'stderr' in streams: + os.close(2) + p = subprocess.Popen( + [sys.executable, "-E", "-c", code], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + preexec_fn=preexec) + out, err = p.communicate() + self.assertEqual(support.strip_python_stderr(err), b'') + self.assertEqual(p.returncode, 42) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_no_stdin(self): + self._test_no_stdio(['stdin']) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_no_stdout(self): + self._test_no_stdio(['stdout']) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_no_stderr(self): + self._test_no_stdio(['stderr']) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_no_std_streams(self): + self._test_no_stdio(['stdin', 'stdout', 'stderr']) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_hash_randomization(self): + # Verify that -R enables hash randomization: + self.verify_valid_flag('-R') + hashes = [] + if os.environ.get('PYTHONHASHSEED', 'random') != 'random': + env = dict(os.environ) # copy + # We need to test that it is enabled by default without + # the environment variable enabling it for us. + del env['PYTHONHASHSEED'] + env['__cleanenv'] = '1' # consumed by assert_python_ok() + else: + env = {} + for i in range(3): + code = 'print(hash("spam"))' + rc, out, err = assert_python_ok('-c', code, **env) + self.assertEqual(rc, 0) + hashes.append(out) + hashes = sorted(set(hashes)) # uniq + # Rare chance of failure due to 3 random seeds honestly being equal. + self.assertGreater(len(hashes), 1, + msg='3 runs produced an identical random hash ' + ' for "spam": {}'.format(hashes)) + + # Verify that sys.flags contains hash_randomization + code = 'import sys; print("random is", sys.flags.hash_randomization)' + rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='') + self.assertIn(b'random is 1', out) + + rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='random') + self.assertIn(b'random is 1', out) + + rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='0') + self.assertIn(b'random is 0', out) + + rc, out, err = assert_python_ok('-R', '-c', code, PYTHONHASHSEED='0') + self.assertIn(b'random is 1', out) + + def test_del___main__(self): + # Issue #15001: PyRun_SimpleFileExFlags() did crash because it kept a + # borrowed reference to the dict of __main__ module and later modify + # the dict whereas the module was destroyed + filename = os_helper.TESTFN + self.addCleanup(os_helper.unlink, filename) + with open(filename, "w") as script: + print("import sys", file=script) + print("del sys.modules['__main__']", file=script) + assert_python_ok(filename) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_unknown_options(self): + rc, out, err = assert_python_failure('-E', '-z') + self.assertIn(b'Unknown option: -z', err) + self.assertEqual(err.splitlines().count(b'Unknown option: -z'), 1) + self.assertEqual(b'', out) + # Add "without='-E'" to prevent _assert_python to append -E + # to env_vars and change the output of stderr + rc, out, err = assert_python_failure('-z', without='-E') + self.assertIn(b'Unknown option: -z', err) + self.assertEqual(err.splitlines().count(b'Unknown option: -z'), 1) + self.assertEqual(b'', out) + rc, out, err = assert_python_failure('-a', '-z', without='-E') + self.assertIn(b'Unknown option: -a', err) + # only the first unknown option is reported + self.assertNotIn(b'Unknown option: -z', err) + self.assertEqual(err.splitlines().count(b'Unknown option: -a'), 1) + self.assertEqual(b'', out) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + @unittest.skipIf(interpreter_requires_environment(), + 'Cannot run -I tests when PYTHON env vars are required.') + def test_isolatedmode(self): + self.verify_valid_flag('-I') + self.verify_valid_flag('-IEs') + rc, out, err = assert_python_ok('-I', '-c', + 'from sys import flags as f; ' + 'print(f.no_user_site, f.ignore_environment, f.isolated)', + # dummyvar to prevent extraneous -E + dummyvar="") + self.assertEqual(out.strip(), b'1 1 1') + with os_helper.temp_cwd() as tmpdir: + fake = os.path.join(tmpdir, "uuid.py") + main = os.path.join(tmpdir, "main.py") + with open(fake, "w") as f: + f.write("raise RuntimeError('isolated mode test')\n") + with open(main, "w") as f: + f.write("import uuid\n") + f.write("print('ok')\n") + self.assertRaises(subprocess.CalledProcessError, + subprocess.check_output, + [sys.executable, main], cwd=tmpdir, + stderr=subprocess.DEVNULL) + out = subprocess.check_output([sys.executable, "-I", main], + cwd=tmpdir) + self.assertEqual(out.strip(), b"ok") + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_sys_flags_set(self): + # Issue 31845: a startup refactoring broke reading flags from env vars + for value, expected in (("", 0), ("1", 1), ("text", 1), ("2", 2)): + env_vars = dict( + PYTHONDEBUG=value, + PYTHONOPTIMIZE=value, + PYTHONDONTWRITEBYTECODE=value, + PYTHONVERBOSE=value, + ) + dont_write_bytecode = int(bool(value)) + code = ( + "import sys; " + "sys.stderr.write(str(sys.flags)); " + f"""sys.exit(not ( + sys.flags.debug == sys.flags.optimize == + sys.flags.verbose == + {expected} + and sys.flags.dont_write_bytecode == {dont_write_bytecode} + ))""" + ) + with self.subTest(envar_value=value): + assert_python_ok('-c', code, **env_vars) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_set_pycache_prefix(self): + # sys.pycache_prefix can be set from either -X pycache_prefix or + # PYTHONPYCACHEPREFIX env var, with the former taking precedence. + NO_VALUE = object() # `-X pycache_prefix` with no `=PATH` + cases = [ + # (PYTHONPYCACHEPREFIX, -X pycache_prefix, sys.pycache_prefix) + (None, None, None), + ('foo', None, 'foo'), + (None, 'bar', 'bar'), + ('foo', 'bar', 'bar'), + ('foo', '', None), + ('foo', NO_VALUE, None), + ] + for envval, opt, expected in cases: + exp_clause = "is None" if expected is None else f'== "{expected}"' + code = f"import sys; sys.exit(not sys.pycache_prefix {exp_clause})" + args = ['-c', code] + env = {} if envval is None else {'PYTHONPYCACHEPREFIX': envval} + if opt is NO_VALUE: + args[:0] = ['-X', 'pycache_prefix'] + elif opt is not None: + args[:0] = ['-X', f'pycache_prefix={opt}'] + with self.subTest(envval=envval, opt=opt): + with os_helper.temp_cwd(): + assert_python_ok(*args, **env) + + def run_xdev(self, *args, check_exitcode=True, xdev=True): + env = dict(os.environ) + env.pop('PYTHONWARNINGS', None) + env.pop('PYTHONDEVMODE', None) + env.pop('PYTHONMALLOC', None) + + if xdev: + args = (sys.executable, '-X', 'dev', *args) + else: + args = (sys.executable, *args) + proc = subprocess.run(args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + env=env) + if check_exitcode: + self.assertEqual(proc.returncode, 0, proc) + return proc.stdout.rstrip() + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_xdev(self): + # sys.flags.dev_mode + code = "import sys; print(sys.flags.dev_mode)" + out = self.run_xdev("-c", code, xdev=False) + self.assertEqual(out, "False") + out = self.run_xdev("-c", code) + self.assertEqual(out, "True") + + # Warnings + code = ("import warnings; " + "print(' '.join('%s::%s' % (f[0], f[2].__name__) " + "for f in warnings.filters))") + if Py_DEBUG: + expected_filters = "default::Warning" + else: + expected_filters = ("default::Warning " + "default::DeprecationWarning " + "ignore::DeprecationWarning " + "ignore::PendingDeprecationWarning " + "ignore::ImportWarning " + "ignore::ResourceWarning") + + out = self.run_xdev("-c", code) + self.assertEqual(out, expected_filters) + + out = self.run_xdev("-b", "-c", code) + self.assertEqual(out, f"default::BytesWarning {expected_filters}") + + out = self.run_xdev("-bb", "-c", code) + self.assertEqual(out, f"error::BytesWarning {expected_filters}") + + out = self.run_xdev("-Werror", "-c", code) + self.assertEqual(out, f"error::Warning {expected_filters}") + + # Memory allocator debug hooks + try: + import _testcapi + except ImportError: + pass + else: + code = "import _testcapi; print(_testcapi.pymem_getallocatorsname())" + with support.SuppressCrashReport(): + out = self.run_xdev("-c", code, check_exitcode=False) + if support.with_pymalloc(): + alloc_name = "pymalloc_debug" + else: + alloc_name = "malloc_debug" + self.assertEqual(out, alloc_name) + + # Faulthandler + try: + import faulthandler + except ImportError: + pass + else: + code = "import faulthandler; print(faulthandler.is_enabled())" + out = self.run_xdev("-c", code) + self.assertEqual(out, "True") + + def check_warnings_filters(self, cmdline_option, envvar, use_pywarning=False): + if use_pywarning: + code = ("import sys; from test.support.import_helper import import_fresh_module; " + "warnings = import_fresh_module('warnings', blocked=['_warnings']); ") + else: + code = "import sys, warnings; " + code += ("print(' '.join('%s::%s' % (f[0], f[2].__name__) " + "for f in warnings.filters))") + args = (sys.executable, '-W', cmdline_option, '-bb', '-c', code) + env = dict(os.environ) + env.pop('PYTHONDEVMODE', None) + env["PYTHONWARNINGS"] = envvar + proc = subprocess.run(args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + env=env) + self.assertEqual(proc.returncode, 0, proc) + return proc.stdout.rstrip() + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_warnings_filter_precedence(self): + expected_filters = ("error::BytesWarning " + "once::UserWarning " + "always::UserWarning") + if not Py_DEBUG: + expected_filters += (" " + "default::DeprecationWarning " + "ignore::DeprecationWarning " + "ignore::PendingDeprecationWarning " + "ignore::ImportWarning " + "ignore::ResourceWarning") + + out = self.check_warnings_filters("once::UserWarning", + "always::UserWarning") + self.assertEqual(out, expected_filters) + + out = self.check_warnings_filters("once::UserWarning", + "always::UserWarning", + use_pywarning=True) + self.assertEqual(out, expected_filters) + + def check_pythonmalloc(self, env_var, name): + code = 'import _testcapi; print(_testcapi.pymem_getallocatorsname())' + env = dict(os.environ) + env.pop('PYTHONDEVMODE', None) + if env_var is not None: + env['PYTHONMALLOC'] = env_var + else: + env.pop('PYTHONMALLOC', None) + args = (sys.executable, '-c', code) + proc = subprocess.run(args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + env=env) + self.assertEqual(proc.stdout.rstrip(), name) + self.assertEqual(proc.returncode, 0) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_pythonmalloc(self): + # Test the PYTHONMALLOC environment variable + pymalloc = support.with_pymalloc() + if pymalloc: + default_name = 'pymalloc_debug' if Py_DEBUG else 'pymalloc' + default_name_debug = 'pymalloc_debug' + else: + default_name = 'malloc_debug' if Py_DEBUG else 'malloc' + default_name_debug = 'malloc_debug' + + tests = [ + (None, default_name), + ('debug', default_name_debug), + ('malloc', 'malloc'), + ('malloc_debug', 'malloc_debug'), + ] + if pymalloc: + tests.extend(( + ('pymalloc', 'pymalloc'), + ('pymalloc_debug', 'pymalloc_debug'), + )) + + for env_var, name in tests: + with self.subTest(env_var=env_var, name=name): + self.check_pythonmalloc(env_var, name) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_pythondevmode_env(self): + # Test the PYTHONDEVMODE environment variable + code = "import sys; print(sys.flags.dev_mode)" + env = dict(os.environ) + env.pop('PYTHONDEVMODE', None) + args = (sys.executable, '-c', code) + + proc = subprocess.run(args, stdout=subprocess.PIPE, + universal_newlines=True, env=env) + self.assertEqual(proc.stdout.rstrip(), 'False') + self.assertEqual(proc.returncode, 0, proc) + + env['PYTHONDEVMODE'] = '1' + proc = subprocess.run(args, stdout=subprocess.PIPE, + universal_newlines=True, env=env) + self.assertEqual(proc.stdout.rstrip(), 'True') + self.assertEqual(proc.returncode, 0, proc) + + @unittest.skipUnless(sys.platform == 'win32', + 'bpo-32457 only applies on Windows') + def test_argv0_normalization(self): + args = sys.executable, '-c', 'print(0)' + prefix, exe = os.path.split(sys.executable) + executable = prefix + '\\.\\.\\.\\' + exe + + proc = subprocess.run(args, stdout=subprocess.PIPE, + executable=executable) + self.assertEqual(proc.returncode, 0, proc) + self.assertEqual(proc.stdout.strip(), b'0') + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_parsing_error(self): + args = [sys.executable, '-I', '--unknown-option'] + proc = subprocess.run(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True) + err_msg = "unknown option --unknown-option\nusage: " + self.assertTrue(proc.stderr.startswith(err_msg), proc.stderr) + self.assertNotEqual(proc.returncode, 0) + + +@unittest.skipIf(interpreter_requires_environment(), + 'Cannot run -I tests when PYTHON env vars are required.') +class IgnoreEnvironmentTest(unittest.TestCase): + + def run_ignoring_vars(self, predicate, **env_vars): + # Runs a subprocess with -E set, even though we're passing + # specific environment variables + # Logical inversion to match predicate check to a zero return + # code indicating success + code = "import sys; sys.stderr.write(str(sys.flags)); sys.exit(not ({}))".format(predicate) + return assert_python_ok('-E', '-c', code, **env_vars) + + def test_ignore_PYTHONPATH(self): + path = "should_be_ignored" + self.run_ignoring_vars("'{}' not in sys.path".format(path), + PYTHONPATH=path) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_ignore_PYTHONHASHSEED(self): + self.run_ignoring_vars("sys.flags.hash_randomization == 1", + PYTHONHASHSEED="0") + + def test_sys_flags_not_set(self): + # Issue 31845: a startup refactoring broke reading flags from env vars + expected_outcome = """ + (sys.flags.debug == sys.flags.optimize == + sys.flags.dont_write_bytecode == sys.flags.verbose == 0) + """ + self.run_ignoring_vars( + expected_outcome, + PYTHONDEBUG="1", + PYTHONOPTIMIZE="1", + PYTHONDONTWRITEBYTECODE="1", + PYTHONVERBOSE="1", + ) + + +def test_main(): + support.run_unittest(CmdLineTest, IgnoreEnvironmentTest) + support.reap_children() + +if __name__ == "__main__": + test_main() diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 9338aebf0e..d10725a9f0 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -1,783 +1,783 @@ -# tests command line execution of scripts - -import contextlib -import importlib -import importlib.machinery -import zipimport -import unittest -import sys -import os -import os.path -import py_compile -import subprocess -import io - -import textwrap -from test import support -from test.support import import_helper -from test.support import os_helper -from test.support.script_helper import ( - make_pkg, make_script, make_zip_pkg, make_zip_script, - assert_python_ok, assert_python_failure, spawn_python, kill_python) - -verbose = support.verbose - -example_args = ['test1', 'test2', 'test3'] - -test_source = """\ -# Script may be run with optimisation enabled, so don't rely on assert -# statements being executed -def assertEqual(lhs, rhs): - if lhs != rhs: - raise AssertionError('%r != %r' % (lhs, rhs)) -def assertIdentical(lhs, rhs): - if lhs is not rhs: - raise AssertionError('%r is not %r' % (lhs, rhs)) -# Check basic code execution -result = ['Top level assignment'] -def f(): - result.append('Lower level reference') -f() -assertEqual(result, ['Top level assignment', 'Lower level reference']) -# Check population of magic variables -assertEqual(__name__, '__main__') -from importlib.machinery import BuiltinImporter -_loader = __loader__ if __loader__ is BuiltinImporter else type(__loader__) -print('__loader__==%a' % _loader) -print('__file__==%a' % __file__) -print('__cached__==%a' % __cached__) -print('__package__==%r' % __package__) -# Check PEP 451 details -import os.path -if __package__ is not None: - print('__main__ was located through the import system') - assertIdentical(__spec__.loader, __loader__) - expected_spec_name = os.path.splitext(os.path.basename(__file__))[0] - if __package__: - expected_spec_name = __package__ + "." + expected_spec_name - assertEqual(__spec__.name, expected_spec_name) - assertEqual(__spec__.parent, __package__) - assertIdentical(__spec__.submodule_search_locations, None) - assertEqual(__spec__.origin, __file__) - if __spec__.cached is not None: - assertEqual(__spec__.cached, __cached__) -# Check the sys module -import sys -assertIdentical(globals(), sys.modules[__name__].__dict__) -if __spec__ is not None: - # XXX: We're not currently making __main__ available under its real name - pass # assertIdentical(globals(), sys.modules[__spec__.name].__dict__) -from test import test_cmd_line_script -example_args_list = test_cmd_line_script.example_args -assertEqual(sys.argv[1:], example_args_list) -print('sys.argv[0]==%a' % sys.argv[0]) -print('sys.path[0]==%a' % sys.path[0]) -# Check the working directory -import os -print('cwd==%a' % os.getcwd()) -""" - -def _make_test_script(script_dir, script_basename, source=test_source): - to_return = make_script(script_dir, script_basename, source) - importlib.invalidate_caches() - return to_return - -def _make_test_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename, - source=test_source, depth=1): - to_return = make_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename, - source, depth) - importlib.invalidate_caches() - return to_return - -class CmdLineTest(unittest.TestCase): - def _check_output(self, script_name, exit_code, data, - expected_file, expected_argv0, - expected_path0, expected_package, - expected_loader, expected_cwd=None): - if verbose > 1: - print("Output from test script %r:" % script_name) - print(repr(data)) - self.assertEqual(exit_code, 0) - printed_loader = '__loader__==%a' % expected_loader - printed_file = '__file__==%a' % expected_file - printed_package = '__package__==%r' % expected_package - printed_argv0 = 'sys.argv[0]==%a' % expected_argv0 - printed_path0 = 'sys.path[0]==%a' % expected_path0 - if expected_cwd is None: - expected_cwd = os.getcwd() - printed_cwd = 'cwd==%a' % expected_cwd - if verbose > 1: - print('Expected output:') - print(printed_file) - print(printed_package) - print(printed_argv0) - print(printed_cwd) - self.assertIn(printed_loader.encode('utf-8'), data) - self.assertIn(printed_file.encode('utf-8'), data) - self.assertIn(printed_package.encode('utf-8'), data) - self.assertIn(printed_argv0.encode('utf-8'), data) - self.assertIn(printed_path0.encode('utf-8'), data) - self.assertIn(printed_cwd.encode('utf-8'), data) - - def _check_script(self, script_exec_args, expected_file, - expected_argv0, expected_path0, - expected_package, expected_loader, - *cmd_line_switches, cwd=None, **env_vars): - if isinstance(script_exec_args, str): - script_exec_args = [script_exec_args] - run_args = [*support.optim_args_from_interpreter_flags(), - *cmd_line_switches, *script_exec_args, *example_args] - rc, out, err = assert_python_ok( - *run_args, __isolated=False, __cwd=cwd, **env_vars - ) - self._check_output(script_exec_args, rc, out + err, expected_file, - expected_argv0, expected_path0, - expected_package, expected_loader, cwd) - - def _check_import_error(self, script_exec_args, expected_msg, - *cmd_line_switches, cwd=None, **env_vars): - if isinstance(script_exec_args, str): - script_exec_args = (script_exec_args,) - else: - script_exec_args = tuple(script_exec_args) - run_args = cmd_line_switches + script_exec_args - rc, out, err = assert_python_failure( - *run_args, __isolated=False, __cwd=cwd, **env_vars - ) - if verbose > 1: - print(f'Output from test script {script_exec_args!r:}') - print(repr(err)) - print('Expected output: %r' % expected_msg) - self.assertIn(expected_msg.encode('utf-8'), err) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_dash_c_loader(self): - rc, out, err = assert_python_ok("-c", "print(__loader__)") - expected = repr(importlib.machinery.BuiltinImporter).encode("utf-8") - self.assertIn(expected, out) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_stdin_loader(self): - # Unfortunately, there's no way to automatically test the fully - # interactive REPL, since that code path only gets executed when - # stdin is an interactive tty. - p = spawn_python() - try: - p.stdin.write(b"print(__loader__)\n") - p.stdin.flush() - finally: - out = kill_python(p) - expected = repr(importlib.machinery.BuiltinImporter).encode("utf-8") - self.assertIn(expected, out) - - @contextlib.contextmanager - def interactive_python(self, separate_stderr=False): - if separate_stderr: - p = spawn_python('-i', stderr=subprocess.PIPE) - stderr = p.stderr - else: - p = spawn_python('-i', stderr=subprocess.STDOUT) - stderr = p.stdout - try: - # Drain stderr until prompt - while True: - data = stderr.read(4) - if data == b">>> ": - break - stderr.readline() - yield p - finally: - kill_python(p) - stderr.close() - - def check_repl_stdout_flush(self, separate_stderr=False): - with self.interactive_python(separate_stderr) as p: - p.stdin.write(b"print('foo')\n") - p.stdin.flush() - self.assertEqual(b'foo', p.stdout.readline().strip()) - - def check_repl_stderr_flush(self, separate_stderr=False): - with self.interactive_python(separate_stderr) as p: - p.stdin.write(b"1/0\n") - p.stdin.flush() - stderr = p.stderr if separate_stderr else p.stdout - self.assertIn(b'Traceback ', stderr.readline()) - self.assertIn(b'File ""', stderr.readline()) - self.assertIn(b'ZeroDivisionError', stderr.readline()) - - @unittest.skip("TODO: RUSTPYTHON, test hang in middle") - def test_repl_stdout_flush(self): - self.check_repl_stdout_flush() - - @unittest.skip("TODO: RUSTPYTHON, test hang in middle") - def test_repl_stdout_flush_separate_stderr(self): - self.check_repl_stdout_flush(True) - - @unittest.skip("TODO: RUSTPYTHON, test hang in middle") - def test_repl_stderr_flush(self): - self.check_repl_stderr_flush() - - @unittest.skip("TODO: RUSTPYTHON, test hang in middle") - def test_repl_stderr_flush_separate_stderr(self): - self.check_repl_stderr_flush(True) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_basic_script(self): - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, 'script') - self._check_script(script_name, script_name, script_name, - script_dir, None, - importlib.machinery.SourceFileLoader, - expected_cwd=script_dir) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_script_abspath(self): - # pass the script using the relative path, expect the absolute path - # in __file__ - with os_helper.temp_cwd() as script_dir: - self.assertTrue(os.path.isabs(script_dir), script_dir) - - script_name = _make_test_script(script_dir, 'script') - relative_name = os.path.basename(script_name) - self._check_script(relative_name, script_name, relative_name, - script_dir, None, - importlib.machinery.SourceFileLoader) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_script_compiled(self): - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, 'script') - py_compile.compile(script_name, doraise=True) - os.remove(script_name) - pyc_file = import_helper.make_legacy_pyc(script_name) - self._check_script(pyc_file, pyc_file, - pyc_file, script_dir, None, - importlib.machinery.SourcelessFileLoader) - - def test_directory(self): - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, '__main__') - self._check_script(script_dir, script_name, script_dir, - script_dir, '', - importlib.machinery.SourceFileLoader) - - def test_directory_compiled(self): - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, '__main__') - py_compile.compile(script_name, doraise=True) - os.remove(script_name) - pyc_file = import_helper.make_legacy_pyc(script_name) - self._check_script(script_dir, pyc_file, script_dir, - script_dir, '', - importlib.machinery.SourcelessFileLoader) - - def test_directory_error(self): - with os_helper.temp_dir() as script_dir: - msg = "can't find '__main__' module in %r" % script_dir - self._check_import_error(script_dir, msg) - - def test_zipfile(self): - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, '__main__') - zip_name, run_name = make_zip_script(script_dir, 'test_zip', script_name) - self._check_script(zip_name, run_name, zip_name, zip_name, '', - zipimport.zipimporter) - - def test_zipfile_compiled_timestamp(self): - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, '__main__') - compiled_name = py_compile.compile( - script_name, doraise=True, - invalidation_mode=py_compile.PycInvalidationMode.TIMESTAMP) - zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name) - self._check_script(zip_name, run_name, zip_name, zip_name, '', - zipimport.zipimporter) - - def test_zipfile_compiled_checked_hash(self): - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, '__main__') - compiled_name = py_compile.compile( - script_name, doraise=True, - invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH) - zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name) - self._check_script(zip_name, run_name, zip_name, zip_name, '', - zipimport.zipimporter) - - def test_zipfile_compiled_unchecked_hash(self): - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, '__main__') - compiled_name = py_compile.compile( - script_name, doraise=True, - invalidation_mode=py_compile.PycInvalidationMode.UNCHECKED_HASH) - zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name) - self._check_script(zip_name, run_name, zip_name, zip_name, '', - zipimport.zipimporter) - - def test_zipfile_error(self): - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, 'not_main') - zip_name, run_name = make_zip_script(script_dir, 'test_zip', script_name) - msg = "can't find '__main__' module in %r" % zip_name - self._check_import_error(zip_name, msg) - - def test_module_in_package(self): - with os_helper.temp_dir() as script_dir: - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir) - script_name = _make_test_script(pkg_dir, 'script') - self._check_script(["-m", "test_pkg.script"], script_name, script_name, - script_dir, 'test_pkg', - importlib.machinery.SourceFileLoader, - cwd=script_dir) - - def test_module_in_package_in_zipfile(self): - with os_helper.temp_dir() as script_dir: - zip_name, run_name = _make_test_zip_pkg(script_dir, 'test_zip', 'test_pkg', 'script') - self._check_script(["-m", "test_pkg.script"], run_name, run_name, - script_dir, 'test_pkg', zipimport.zipimporter, - PYTHONPATH=zip_name, cwd=script_dir) - - def test_module_in_subpackage_in_zipfile(self): - with os_helper.temp_dir() as script_dir: - zip_name, run_name = _make_test_zip_pkg(script_dir, 'test_zip', 'test_pkg', 'script', depth=2) - self._check_script(["-m", "test_pkg.test_pkg.script"], run_name, run_name, - script_dir, 'test_pkg.test_pkg', - zipimport.zipimporter, - PYTHONPATH=zip_name, cwd=script_dir) - - def test_package(self): - with os_helper.temp_dir() as script_dir: - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir) - script_name = _make_test_script(pkg_dir, '__main__') - self._check_script(["-m", "test_pkg"], script_name, - script_name, script_dir, 'test_pkg', - importlib.machinery.SourceFileLoader, - cwd=script_dir) - - def test_package_compiled(self): - with os_helper.temp_dir() as script_dir: - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir) - script_name = _make_test_script(pkg_dir, '__main__') - compiled_name = py_compile.compile(script_name, doraise=True) - os.remove(script_name) - pyc_file = import_helper.make_legacy_pyc(script_name) - self._check_script(["-m", "test_pkg"], pyc_file, - pyc_file, script_dir, 'test_pkg', - importlib.machinery.SourcelessFileLoader, - cwd=script_dir) - - def test_package_error(self): - with os_helper.temp_dir() as script_dir: - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir) - msg = ("'test_pkg' is a package and cannot " - "be directly executed") - self._check_import_error(["-m", "test_pkg"], msg, cwd=script_dir) - - def test_package_recursion(self): - with os_helper.temp_dir() as script_dir: - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir) - main_dir = os.path.join(pkg_dir, '__main__') - make_pkg(main_dir) - msg = ("Cannot use package as __main__ module; " - "'test_pkg' is a package and cannot " - "be directly executed") - self._check_import_error(["-m", "test_pkg"], msg, cwd=script_dir) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_issue8202(self): - # Make sure package __init__ modules see "-m" in sys.argv0 while - # searching for the module to execute - with os_helper.temp_dir() as script_dir: - with os_helper.change_cwd(path=script_dir): - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir, "import sys; print('init_argv0==%r' % sys.argv[0])") - script_name = _make_test_script(pkg_dir, 'script') - rc, out, err = assert_python_ok('-m', 'test_pkg.script', *example_args, __isolated=False) - if verbose > 1: - print(repr(out)) - expected = "init_argv0==%r" % '-m' - self.assertIn(expected.encode('utf-8'), out) - self._check_output(script_name, rc, out, - script_name, script_name, script_dir, 'test_pkg', - importlib.machinery.SourceFileLoader) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_issue8202_dash_c_file_ignored(self): - # Make sure a "-c" file in the current directory - # does not alter the value of sys.path[0] - with os_helper.temp_dir() as script_dir: - with os_helper.change_cwd(path=script_dir): - with open("-c", "w", encoding="utf-8") as f: - f.write("data") - rc, out, err = assert_python_ok('-c', - 'import sys; print("sys.path[0]==%r" % sys.path[0])', - __isolated=False) - if verbose > 1: - print(repr(out)) - expected = "sys.path[0]==%r" % '' - self.assertIn(expected.encode('utf-8'), out) - - def test_issue8202_dash_m_file_ignored(self): - # Make sure a "-m" file in the current directory - # does not alter the value of sys.path[0] - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, 'other') - with os_helper.change_cwd(path=script_dir): - with open("-m", "w", encoding="utf-8") as f: - f.write("data") - rc, out, err = assert_python_ok('-m', 'other', *example_args, - __isolated=False) - self._check_output(script_name, rc, out, - script_name, script_name, script_dir, '', - importlib.machinery.SourceFileLoader) - - def test_issue20884(self): - # On Windows, script with encoding cookie and LF line ending - # will be failed. - with os_helper.temp_dir() as script_dir: - script_name = os.path.join(script_dir, "issue20884.py") - with open(script_name, "w", encoding="latin1", newline='\n') as f: - f.write("#coding: iso-8859-1\n") - f.write('"""\n') - for _ in range(30): - f.write('x'*80 + '\n') - f.write('"""\n') - - with os_helper.change_cwd(path=script_dir): - rc, out, err = assert_python_ok(script_name) - self.assertEqual(b"", out) - self.assertEqual(b"", err) - - @contextlib.contextmanager - def setup_test_pkg(self, *args): - with os_helper.temp_dir() as script_dir, \ - os_helper.change_cwd(path=script_dir): - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir, *args) - yield pkg_dir - - def check_dash_m_failure(self, *args): - rc, out, err = assert_python_failure('-m', *args, __isolated=False) - if verbose > 1: - print(repr(out)) - self.assertEqual(rc, 1) - return err - - def test_dash_m_error_code_is_one(self): - # If a module is invoked with the -m command line flag - # and results in an error that the return code to the - # shell is '1' - with self.setup_test_pkg() as pkg_dir: - script_name = _make_test_script(pkg_dir, 'other', - "if __name__ == '__main__': raise ValueError") - err = self.check_dash_m_failure('test_pkg.other', *example_args) - self.assertIn(b'ValueError', err) - - def test_dash_m_errors(self): - # Exercise error reporting for various invalid package executions - tests = ( - ('builtins', br'No code object available'), - ('builtins.x', br'Error while finding module specification.*' - br'ModuleNotFoundError'), - ('builtins.x.y', br'Error while finding module specification.*' - br'ModuleNotFoundError.*No module named.*not a package'), - ('os.path', br'loader.*cannot handle'), - ('importlib', br'No module named.*' - br'is a package and cannot be directly executed'), - ('importlib.nonexistent', br'No module named'), - ('.unittest', br'Relative module names not supported'), - ) - for name, regex in tests: - with self.subTest(name): - rc, _, err = assert_python_failure('-m', name) - self.assertEqual(rc, 1) - self.assertRegex(err, regex) - self.assertNotIn(b'Traceback', err) - - def test_dash_m_bad_pyc(self): - with os_helper.temp_dir() as script_dir, \ - os_helper.change_cwd(path=script_dir): - os.mkdir('test_pkg') - # Create invalid *.pyc as empty file - with open('test_pkg/__init__.pyc', 'wb'): - pass - err = self.check_dash_m_failure('test_pkg') - self.assertRegex(err, - br'Error while finding module specification.*' - br'ImportError.*bad magic number') - self.assertNotIn(b'is a package', err) - self.assertNotIn(b'Traceback', err) - - def test_hint_when_triying_to_import_a_py_file(self): - with os_helper.temp_dir() as script_dir, \ - os_helper.change_cwd(path=script_dir): - # Create invalid *.pyc as empty file - with open('asyncio.py', 'wb'): - pass - err = self.check_dash_m_failure('asyncio.py') - self.assertIn(b"Try using 'asyncio' instead " - b"of 'asyncio.py' as the module name", err) - - def test_dash_m_init_traceback(self): - # These were wrapped in an ImportError and tracebacks were - # suppressed; see Issue 14285 - exceptions = (ImportError, AttributeError, TypeError, ValueError) - for exception in exceptions: - exception = exception.__name__ - init = "raise {0}('Exception in __init__.py')".format(exception) - with self.subTest(exception), \ - self.setup_test_pkg(init) as pkg_dir: - err = self.check_dash_m_failure('test_pkg') - self.assertIn(exception.encode('ascii'), err) - self.assertIn(b'Exception in __init__.py', err) - self.assertIn(b'Traceback', err) - - def test_dash_m_main_traceback(self): - # Ensure that an ImportError's traceback is reported - with self.setup_test_pkg() as pkg_dir: - main = "raise ImportError('Exception in __main__ module')" - _make_test_script(pkg_dir, '__main__', main) - err = self.check_dash_m_failure('test_pkg') - self.assertIn(b'ImportError', err) - self.assertIn(b'Exception in __main__ module', err) - self.assertIn(b'Traceback', err) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_pep_409_verbiage(self): - # Make sure PEP 409 syntax properly suppresses - # the context of an exception - script = textwrap.dedent("""\ - try: - raise ValueError - except: - raise NameError from None - """) - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, 'script', script) - exitcode, stdout, stderr = assert_python_failure(script_name) - text = stderr.decode('ascii').split('\n') - self.assertEqual(len(text), 5) - self.assertTrue(text[0].startswith('Traceback')) - self.assertTrue(text[1].startswith(' File ')) - self.assertTrue(text[3].startswith('NameError')) - - def test_non_ascii(self): - # Mac OS X denies the creation of a file with an invalid UTF-8 name. - # Windows allows creating a name with an arbitrary bytes name, but - # Python cannot a undecodable bytes argument to a subprocess. - if (os_helper.TESTFN_UNDECODABLE - and sys.platform not in ('win32', 'darwin')): - name = os.fsdecode(os_helper.TESTFN_UNDECODABLE) - elif os_helper.TESTFN_NONASCII: - name = os_helper.TESTFN_NONASCII - else: - self.skipTest("need os_helper.TESTFN_NONASCII") - - # Issue #16218 - source = 'print(ascii(__file__))\n' - script_name = _make_test_script(os.getcwd(), name, source) - self.addCleanup(os_helper.unlink, script_name) - rc, stdout, stderr = assert_python_ok(script_name) - self.assertEqual( - ascii(script_name), - stdout.rstrip().decode('ascii'), - 'stdout=%r stderr=%r' % (stdout, stderr)) - self.assertEqual(0, rc) - - def test_issue20500_exit_with_exception_value(self): - script = textwrap.dedent("""\ - import sys - error = None - try: - raise ValueError('some text') - except ValueError as err: - error = err - - if error: - sys.exit(error) - """) - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, 'script', script) - exitcode, stdout, stderr = assert_python_failure(script_name) - text = stderr.decode('ascii') - self.assertEqual(text.rstrip(), "some text") - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_syntaxerror_unindented_caret_position(self): - script = "1 + 1 = 2\n" - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, 'script', script) - exitcode, stdout, stderr = assert_python_failure(script_name) - text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read() - # Confirm that the caret is located under the '=' sign - self.assertIn("\n ^^^^^\n", text) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_syntaxerror_indented_caret_position(self): - script = textwrap.dedent("""\ - if True: - 1 + 1 = 2 - """) - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, 'script', script) - exitcode, stdout, stderr = assert_python_failure(script_name) - text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read() - # Confirm that the caret starts under the first 1 character - self.assertIn("\n 1 + 1 = 2\n ^^^^^\n", text) - - # Try the same with a form feed at the start of the indented line - script = ( - "if True:\n" - "\f 1 + 1 = 2\n" - ) - script_name = _make_test_script(script_dir, "script", script) - exitcode, stdout, stderr = assert_python_failure(script_name) - text = io.TextIOWrapper(io.BytesIO(stderr), "ascii").read() - self.assertNotIn("\f", text) - self.assertIn("\n 1 + 1 = 2\n ^^^^^\n", text) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_syntaxerror_multi_line_fstring(self): - script = 'foo = f"""{}\nfoo"""\n' - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, 'script', script) - exitcode, stdout, stderr = assert_python_failure(script_name) - self.assertEqual( - stderr.splitlines()[-3:], - [ - b' foo"""', - b' ^', - b'SyntaxError: f-string: empty expression not allowed', - ], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_syntaxerror_invalid_escape_sequence_multi_line(self): - script = 'foo = """\\q"""\n' - with os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, 'script', script) - exitcode, stdout, stderr = assert_python_failure( - '-Werror', script_name, - ) - self.assertEqual( - stderr.splitlines()[-3:], - [ b' foo = """\\q"""', - b' ^^^^^^^^', - b'SyntaxError: invalid escape sequence \'\\q\'' - ], - ) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_consistent_sys_path_for_direct_execution(self): - # This test case ensures that the following all give the same - # sys.path configuration: - # - # ./python -s script_dir/__main__.py - # ./python -s script_dir - # ./python -I script_dir - script = textwrap.dedent("""\ - import sys - for entry in sys.path: - print(entry) - """) - # Always show full path diffs on errors - self.maxDiff = None - with os_helper.temp_dir() as work_dir, os_helper.temp_dir() as script_dir: - script_name = _make_test_script(script_dir, '__main__', script) - # Reference output comes from directly executing __main__.py - # We omit PYTHONPATH and user site to align with isolated mode - p = spawn_python("-Es", script_name, cwd=work_dir) - out_by_name = kill_python(p).decode().splitlines() - self.assertEqual(out_by_name[0], script_dir) - self.assertNotIn(work_dir, out_by_name) - # Directory execution should give the same output - p = spawn_python("-Es", script_dir, cwd=work_dir) - out_by_dir = kill_python(p).decode().splitlines() - self.assertEqual(out_by_dir, out_by_name) - # As should directory execution in isolated mode - p = spawn_python("-I", script_dir, cwd=work_dir) - out_by_dir_isolated = kill_python(p).decode().splitlines() - self.assertEqual(out_by_dir_isolated, out_by_dir, out_by_name) - - def test_consistent_sys_path_for_module_execution(self): - # This test case ensures that the following both give the same - # sys.path configuration: - # ./python -sm script_pkg.__main__ - # ./python -sm script_pkg - # - # And that this fails as unable to find the package: - # ./python -Im script_pkg - script = textwrap.dedent("""\ - import sys - for entry in sys.path: - print(entry) - """) - # Always show full path diffs on errors - self.maxDiff = None - with os_helper.temp_dir() as work_dir: - script_dir = os.path.join(work_dir, "script_pkg") - os.mkdir(script_dir) - script_name = _make_test_script(script_dir, '__main__', script) - # Reference output comes from `-m script_pkg.__main__` - # We omit PYTHONPATH and user site to better align with the - # direct execution test cases - p = spawn_python("-sm", "script_pkg.__main__", cwd=work_dir) - out_by_module = kill_python(p).decode().splitlines() - self.assertEqual(out_by_module[0], work_dir) - self.assertNotIn(script_dir, out_by_module) - # Package execution should give the same output - p = spawn_python("-sm", "script_pkg", cwd=work_dir) - out_by_package = kill_python(p).decode().splitlines() - self.assertEqual(out_by_package, out_by_module) - # Isolated mode should fail with an import error - exitcode, stdout, stderr = assert_python_failure( - "-Im", "script_pkg", cwd=work_dir - ) - traceback_lines = stderr.decode().splitlines() - self.assertIn("No module named script_pkg", traceback_lines[-1]) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_nonexisting_script(self): - # bpo-34783: "./python script.py" must not crash - # if the script file doesn't exist. - # (Skip test for macOS framework builds because sys.executable name - # is not the actual Python executable file name. - script = 'nonexistingscript.py' - self.assertFalse(os.path.exists(script)) - - proc = spawn_python(script, text=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out, err = proc.communicate() - self.assertIn(": can't open file ", err) - self.assertNotEqual(proc.returncode, 0) - -# TODO: RUSTPYTHON -# def tearDownModule(): -def test_main(): - support.run_unittest(CmdLineTest) - support.reap_children() - - -if __name__ == '__main__': - # TODO: RUSTPYTHON - # unittest.main() - test_main() +# tests command line execution of scripts + +import contextlib +import importlib +import importlib.machinery +import zipimport +import unittest +import sys +import os +import os.path +import py_compile +import subprocess +import io + +import textwrap +from test import support +from test.support import import_helper +from test.support import os_helper +from test.support.script_helper import ( + make_pkg, make_script, make_zip_pkg, make_zip_script, + assert_python_ok, assert_python_failure, spawn_python, kill_python) + +verbose = support.verbose + +example_args = ['test1', 'test2', 'test3'] + +test_source = """\ +# Script may be run with optimisation enabled, so don't rely on assert +# statements being executed +def assertEqual(lhs, rhs): + if lhs != rhs: + raise AssertionError('%r != %r' % (lhs, rhs)) +def assertIdentical(lhs, rhs): + if lhs is not rhs: + raise AssertionError('%r is not %r' % (lhs, rhs)) +# Check basic code execution +result = ['Top level assignment'] +def f(): + result.append('Lower level reference') +f() +assertEqual(result, ['Top level assignment', 'Lower level reference']) +# Check population of magic variables +assertEqual(__name__, '__main__') +from importlib.machinery import BuiltinImporter +_loader = __loader__ if __loader__ is BuiltinImporter else type(__loader__) +print('__loader__==%a' % _loader) +print('__file__==%a' % __file__) +print('__cached__==%a' % __cached__) +print('__package__==%r' % __package__) +# Check PEP 451 details +import os.path +if __package__ is not None: + print('__main__ was located through the import system') + assertIdentical(__spec__.loader, __loader__) + expected_spec_name = os.path.splitext(os.path.basename(__file__))[0] + if __package__: + expected_spec_name = __package__ + "." + expected_spec_name + assertEqual(__spec__.name, expected_spec_name) + assertEqual(__spec__.parent, __package__) + assertIdentical(__spec__.submodule_search_locations, None) + assertEqual(__spec__.origin, __file__) + if __spec__.cached is not None: + assertEqual(__spec__.cached, __cached__) +# Check the sys module +import sys +assertIdentical(globals(), sys.modules[__name__].__dict__) +if __spec__ is not None: + # XXX: We're not currently making __main__ available under its real name + pass # assertIdentical(globals(), sys.modules[__spec__.name].__dict__) +from test import test_cmd_line_script +example_args_list = test_cmd_line_script.example_args +assertEqual(sys.argv[1:], example_args_list) +print('sys.argv[0]==%a' % sys.argv[0]) +print('sys.path[0]==%a' % sys.path[0]) +# Check the working directory +import os +print('cwd==%a' % os.getcwd()) +""" + +def _make_test_script(script_dir, script_basename, source=test_source): + to_return = make_script(script_dir, script_basename, source) + importlib.invalidate_caches() + return to_return + +def _make_test_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename, + source=test_source, depth=1): + to_return = make_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename, + source, depth) + importlib.invalidate_caches() + return to_return + +class CmdLineTest(unittest.TestCase): + def _check_output(self, script_name, exit_code, data, + expected_file, expected_argv0, + expected_path0, expected_package, + expected_loader, expected_cwd=None): + if verbose > 1: + print("Output from test script %r:" % script_name) + print(repr(data)) + self.assertEqual(exit_code, 0) + printed_loader = '__loader__==%a' % expected_loader + printed_file = '__file__==%a' % expected_file + printed_package = '__package__==%r' % expected_package + printed_argv0 = 'sys.argv[0]==%a' % expected_argv0 + printed_path0 = 'sys.path[0]==%a' % expected_path0 + if expected_cwd is None: + expected_cwd = os.getcwd() + printed_cwd = 'cwd==%a' % expected_cwd + if verbose > 1: + print('Expected output:') + print(printed_file) + print(printed_package) + print(printed_argv0) + print(printed_cwd) + self.assertIn(printed_loader.encode('utf-8'), data) + self.assertIn(printed_file.encode('utf-8'), data) + self.assertIn(printed_package.encode('utf-8'), data) + self.assertIn(printed_argv0.encode('utf-8'), data) + self.assertIn(printed_path0.encode('utf-8'), data) + self.assertIn(printed_cwd.encode('utf-8'), data) + + def _check_script(self, script_exec_args, expected_file, + expected_argv0, expected_path0, + expected_package, expected_loader, + *cmd_line_switches, cwd=None, **env_vars): + if isinstance(script_exec_args, str): + script_exec_args = [script_exec_args] + run_args = [*support.optim_args_from_interpreter_flags(), + *cmd_line_switches, *script_exec_args, *example_args] + rc, out, err = assert_python_ok( + *run_args, __isolated=False, __cwd=cwd, **env_vars + ) + self._check_output(script_exec_args, rc, out + err, expected_file, + expected_argv0, expected_path0, + expected_package, expected_loader, cwd) + + def _check_import_error(self, script_exec_args, expected_msg, + *cmd_line_switches, cwd=None, **env_vars): + if isinstance(script_exec_args, str): + script_exec_args = (script_exec_args,) + else: + script_exec_args = tuple(script_exec_args) + run_args = cmd_line_switches + script_exec_args + rc, out, err = assert_python_failure( + *run_args, __isolated=False, __cwd=cwd, **env_vars + ) + if verbose > 1: + print(f'Output from test script {script_exec_args!r:}') + print(repr(err)) + print('Expected output: %r' % expected_msg) + self.assertIn(expected_msg.encode('utf-8'), err) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_dash_c_loader(self): + rc, out, err = assert_python_ok("-c", "print(__loader__)") + expected = repr(importlib.machinery.BuiltinImporter).encode("utf-8") + self.assertIn(expected, out) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_stdin_loader(self): + # Unfortunately, there's no way to automatically test the fully + # interactive REPL, since that code path only gets executed when + # stdin is an interactive tty. + p = spawn_python() + try: + p.stdin.write(b"print(__loader__)\n") + p.stdin.flush() + finally: + out = kill_python(p) + expected = repr(importlib.machinery.BuiltinImporter).encode("utf-8") + self.assertIn(expected, out) + + @contextlib.contextmanager + def interactive_python(self, separate_stderr=False): + if separate_stderr: + p = spawn_python('-i', stderr=subprocess.PIPE) + stderr = p.stderr + else: + p = spawn_python('-i', stderr=subprocess.STDOUT) + stderr = p.stdout + try: + # Drain stderr until prompt + while True: + data = stderr.read(4) + if data == b">>> ": + break + stderr.readline() + yield p + finally: + kill_python(p) + stderr.close() + + def check_repl_stdout_flush(self, separate_stderr=False): + with self.interactive_python(separate_stderr) as p: + p.stdin.write(b"print('foo')\n") + p.stdin.flush() + self.assertEqual(b'foo', p.stdout.readline().strip()) + + def check_repl_stderr_flush(self, separate_stderr=False): + with self.interactive_python(separate_stderr) as p: + p.stdin.write(b"1/0\n") + p.stdin.flush() + stderr = p.stderr if separate_stderr else p.stdout + self.assertIn(b'Traceback ', stderr.readline()) + self.assertIn(b'File ""', stderr.readline()) + self.assertIn(b'ZeroDivisionError', stderr.readline()) + + @unittest.skip("TODO: RUSTPYTHON, test hang in middle") + def test_repl_stdout_flush(self): + self.check_repl_stdout_flush() + + @unittest.skip("TODO: RUSTPYTHON, test hang in middle") + def test_repl_stdout_flush_separate_stderr(self): + self.check_repl_stdout_flush(True) + + @unittest.skip("TODO: RUSTPYTHON, test hang in middle") + def test_repl_stderr_flush(self): + self.check_repl_stderr_flush() + + @unittest.skip("TODO: RUSTPYTHON, test hang in middle") + def test_repl_stderr_flush_separate_stderr(self): + self.check_repl_stderr_flush(True) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_basic_script(self): + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, 'script') + self._check_script(script_name, script_name, script_name, + script_dir, None, + importlib.machinery.SourceFileLoader, + expected_cwd=script_dir) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_script_abspath(self): + # pass the script using the relative path, expect the absolute path + # in __file__ + with os_helper.temp_cwd() as script_dir: + self.assertTrue(os.path.isabs(script_dir), script_dir) + + script_name = _make_test_script(script_dir, 'script') + relative_name = os.path.basename(script_name) + self._check_script(relative_name, script_name, relative_name, + script_dir, None, + importlib.machinery.SourceFileLoader) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_script_compiled(self): + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, 'script') + py_compile.compile(script_name, doraise=True) + os.remove(script_name) + pyc_file = import_helper.make_legacy_pyc(script_name) + self._check_script(pyc_file, pyc_file, + pyc_file, script_dir, None, + importlib.machinery.SourcelessFileLoader) + + def test_directory(self): + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, '__main__') + self._check_script(script_dir, script_name, script_dir, + script_dir, '', + importlib.machinery.SourceFileLoader) + + def test_directory_compiled(self): + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, '__main__') + py_compile.compile(script_name, doraise=True) + os.remove(script_name) + pyc_file = import_helper.make_legacy_pyc(script_name) + self._check_script(script_dir, pyc_file, script_dir, + script_dir, '', + importlib.machinery.SourcelessFileLoader) + + def test_directory_error(self): + with os_helper.temp_dir() as script_dir: + msg = "can't find '__main__' module in %r" % script_dir + self._check_import_error(script_dir, msg) + + def test_zipfile(self): + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, '__main__') + zip_name, run_name = make_zip_script(script_dir, 'test_zip', script_name) + self._check_script(zip_name, run_name, zip_name, zip_name, '', + zipimport.zipimporter) + + def test_zipfile_compiled_timestamp(self): + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, '__main__') + compiled_name = py_compile.compile( + script_name, doraise=True, + invalidation_mode=py_compile.PycInvalidationMode.TIMESTAMP) + zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name) + self._check_script(zip_name, run_name, zip_name, zip_name, '', + zipimport.zipimporter) + + def test_zipfile_compiled_checked_hash(self): + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, '__main__') + compiled_name = py_compile.compile( + script_name, doraise=True, + invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH) + zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name) + self._check_script(zip_name, run_name, zip_name, zip_name, '', + zipimport.zipimporter) + + def test_zipfile_compiled_unchecked_hash(self): + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, '__main__') + compiled_name = py_compile.compile( + script_name, doraise=True, + invalidation_mode=py_compile.PycInvalidationMode.UNCHECKED_HASH) + zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name) + self._check_script(zip_name, run_name, zip_name, zip_name, '', + zipimport.zipimporter) + + def test_zipfile_error(self): + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, 'not_main') + zip_name, run_name = make_zip_script(script_dir, 'test_zip', script_name) + msg = "can't find '__main__' module in %r" % zip_name + self._check_import_error(zip_name, msg) + + def test_module_in_package(self): + with os_helper.temp_dir() as script_dir: + pkg_dir = os.path.join(script_dir, 'test_pkg') + make_pkg(pkg_dir) + script_name = _make_test_script(pkg_dir, 'script') + self._check_script(["-m", "test_pkg.script"], script_name, script_name, + script_dir, 'test_pkg', + importlib.machinery.SourceFileLoader, + cwd=script_dir) + + def test_module_in_package_in_zipfile(self): + with os_helper.temp_dir() as script_dir: + zip_name, run_name = _make_test_zip_pkg(script_dir, 'test_zip', 'test_pkg', 'script') + self._check_script(["-m", "test_pkg.script"], run_name, run_name, + script_dir, 'test_pkg', zipimport.zipimporter, + PYTHONPATH=zip_name, cwd=script_dir) + + def test_module_in_subpackage_in_zipfile(self): + with os_helper.temp_dir() as script_dir: + zip_name, run_name = _make_test_zip_pkg(script_dir, 'test_zip', 'test_pkg', 'script', depth=2) + self._check_script(["-m", "test_pkg.test_pkg.script"], run_name, run_name, + script_dir, 'test_pkg.test_pkg', + zipimport.zipimporter, + PYTHONPATH=zip_name, cwd=script_dir) + + def test_package(self): + with os_helper.temp_dir() as script_dir: + pkg_dir = os.path.join(script_dir, 'test_pkg') + make_pkg(pkg_dir) + script_name = _make_test_script(pkg_dir, '__main__') + self._check_script(["-m", "test_pkg"], script_name, + script_name, script_dir, 'test_pkg', + importlib.machinery.SourceFileLoader, + cwd=script_dir) + + def test_package_compiled(self): + with os_helper.temp_dir() as script_dir: + pkg_dir = os.path.join(script_dir, 'test_pkg') + make_pkg(pkg_dir) + script_name = _make_test_script(pkg_dir, '__main__') + compiled_name = py_compile.compile(script_name, doraise=True) + os.remove(script_name) + pyc_file = import_helper.make_legacy_pyc(script_name) + self._check_script(["-m", "test_pkg"], pyc_file, + pyc_file, script_dir, 'test_pkg', + importlib.machinery.SourcelessFileLoader, + cwd=script_dir) + + def test_package_error(self): + with os_helper.temp_dir() as script_dir: + pkg_dir = os.path.join(script_dir, 'test_pkg') + make_pkg(pkg_dir) + msg = ("'test_pkg' is a package and cannot " + "be directly executed") + self._check_import_error(["-m", "test_pkg"], msg, cwd=script_dir) + + def test_package_recursion(self): + with os_helper.temp_dir() as script_dir: + pkg_dir = os.path.join(script_dir, 'test_pkg') + make_pkg(pkg_dir) + main_dir = os.path.join(pkg_dir, '__main__') + make_pkg(main_dir) + msg = ("Cannot use package as __main__ module; " + "'test_pkg' is a package and cannot " + "be directly executed") + self._check_import_error(["-m", "test_pkg"], msg, cwd=script_dir) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_issue8202(self): + # Make sure package __init__ modules see "-m" in sys.argv0 while + # searching for the module to execute + with os_helper.temp_dir() as script_dir: + with os_helper.change_cwd(path=script_dir): + pkg_dir = os.path.join(script_dir, 'test_pkg') + make_pkg(pkg_dir, "import sys; print('init_argv0==%r' % sys.argv[0])") + script_name = _make_test_script(pkg_dir, 'script') + rc, out, err = assert_python_ok('-m', 'test_pkg.script', *example_args, __isolated=False) + if verbose > 1: + print(repr(out)) + expected = "init_argv0==%r" % '-m' + self.assertIn(expected.encode('utf-8'), out) + self._check_output(script_name, rc, out, + script_name, script_name, script_dir, 'test_pkg', + importlib.machinery.SourceFileLoader) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_issue8202_dash_c_file_ignored(self): + # Make sure a "-c" file in the current directory + # does not alter the value of sys.path[0] + with os_helper.temp_dir() as script_dir: + with os_helper.change_cwd(path=script_dir): + with open("-c", "w", encoding="utf-8") as f: + f.write("data") + rc, out, err = assert_python_ok('-c', + 'import sys; print("sys.path[0]==%r" % sys.path[0])', + __isolated=False) + if verbose > 1: + print(repr(out)) + expected = "sys.path[0]==%r" % '' + self.assertIn(expected.encode('utf-8'), out) + + def test_issue8202_dash_m_file_ignored(self): + # Make sure a "-m" file in the current directory + # does not alter the value of sys.path[0] + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, 'other') + with os_helper.change_cwd(path=script_dir): + with open("-m", "w", encoding="utf-8") as f: + f.write("data") + rc, out, err = assert_python_ok('-m', 'other', *example_args, + __isolated=False) + self._check_output(script_name, rc, out, + script_name, script_name, script_dir, '', + importlib.machinery.SourceFileLoader) + + def test_issue20884(self): + # On Windows, script with encoding cookie and LF line ending + # will be failed. + with os_helper.temp_dir() as script_dir: + script_name = os.path.join(script_dir, "issue20884.py") + with open(script_name, "w", encoding="latin1", newline='\n') as f: + f.write("#coding: iso-8859-1\n") + f.write('"""\n') + for _ in range(30): + f.write('x'*80 + '\n') + f.write('"""\n') + + with os_helper.change_cwd(path=script_dir): + rc, out, err = assert_python_ok(script_name) + self.assertEqual(b"", out) + self.assertEqual(b"", err) + + @contextlib.contextmanager + def setup_test_pkg(self, *args): + with os_helper.temp_dir() as script_dir, \ + os_helper.change_cwd(path=script_dir): + pkg_dir = os.path.join(script_dir, 'test_pkg') + make_pkg(pkg_dir, *args) + yield pkg_dir + + def check_dash_m_failure(self, *args): + rc, out, err = assert_python_failure('-m', *args, __isolated=False) + if verbose > 1: + print(repr(out)) + self.assertEqual(rc, 1) + return err + + def test_dash_m_error_code_is_one(self): + # If a module is invoked with the -m command line flag + # and results in an error that the return code to the + # shell is '1' + with self.setup_test_pkg() as pkg_dir: + script_name = _make_test_script(pkg_dir, 'other', + "if __name__ == '__main__': raise ValueError") + err = self.check_dash_m_failure('test_pkg.other', *example_args) + self.assertIn(b'ValueError', err) + + def test_dash_m_errors(self): + # Exercise error reporting for various invalid package executions + tests = ( + ('builtins', br'No code object available'), + ('builtins.x', br'Error while finding module specification.*' + br'ModuleNotFoundError'), + ('builtins.x.y', br'Error while finding module specification.*' + br'ModuleNotFoundError.*No module named.*not a package'), + ('os.path', br'loader.*cannot handle'), + ('importlib', br'No module named.*' + br'is a package and cannot be directly executed'), + ('importlib.nonexistent', br'No module named'), + ('.unittest', br'Relative module names not supported'), + ) + for name, regex in tests: + with self.subTest(name): + rc, _, err = assert_python_failure('-m', name) + self.assertEqual(rc, 1) + self.assertRegex(err, regex) + self.assertNotIn(b'Traceback', err) + + def test_dash_m_bad_pyc(self): + with os_helper.temp_dir() as script_dir, \ + os_helper.change_cwd(path=script_dir): + os.mkdir('test_pkg') + # Create invalid *.pyc as empty file + with open('test_pkg/__init__.pyc', 'wb'): + pass + err = self.check_dash_m_failure('test_pkg') + self.assertRegex(err, + br'Error while finding module specification.*' + br'ImportError.*bad magic number') + self.assertNotIn(b'is a package', err) + self.assertNotIn(b'Traceback', err) + + def test_hint_when_triying_to_import_a_py_file(self): + with os_helper.temp_dir() as script_dir, \ + os_helper.change_cwd(path=script_dir): + # Create invalid *.pyc as empty file + with open('asyncio.py', 'wb'): + pass + err = self.check_dash_m_failure('asyncio.py') + self.assertIn(b"Try using 'asyncio' instead " + b"of 'asyncio.py' as the module name", err) + + def test_dash_m_init_traceback(self): + # These were wrapped in an ImportError and tracebacks were + # suppressed; see Issue 14285 + exceptions = (ImportError, AttributeError, TypeError, ValueError) + for exception in exceptions: + exception = exception.__name__ + init = "raise {0}('Exception in __init__.py')".format(exception) + with self.subTest(exception), \ + self.setup_test_pkg(init) as pkg_dir: + err = self.check_dash_m_failure('test_pkg') + self.assertIn(exception.encode('ascii'), err) + self.assertIn(b'Exception in __init__.py', err) + self.assertIn(b'Traceback', err) + + def test_dash_m_main_traceback(self): + # Ensure that an ImportError's traceback is reported + with self.setup_test_pkg() as pkg_dir: + main = "raise ImportError('Exception in __main__ module')" + _make_test_script(pkg_dir, '__main__', main) + err = self.check_dash_m_failure('test_pkg') + self.assertIn(b'ImportError', err) + self.assertIn(b'Exception in __main__ module', err) + self.assertIn(b'Traceback', err) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_pep_409_verbiage(self): + # Make sure PEP 409 syntax properly suppresses + # the context of an exception + script = textwrap.dedent("""\ + try: + raise ValueError + except: + raise NameError from None + """) + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, 'script', script) + exitcode, stdout, stderr = assert_python_failure(script_name) + text = stderr.decode('ascii').split('\n') + self.assertEqual(len(text), 5) + self.assertTrue(text[0].startswith('Traceback')) + self.assertTrue(text[1].startswith(' File ')) + self.assertTrue(text[3].startswith('NameError')) + + def test_non_ascii(self): + # Mac OS X denies the creation of a file with an invalid UTF-8 name. + # Windows allows creating a name with an arbitrary bytes name, but + # Python cannot a undecodable bytes argument to a subprocess. + if (os_helper.TESTFN_UNDECODABLE + and sys.platform not in ('win32', 'darwin')): + name = os.fsdecode(os_helper.TESTFN_UNDECODABLE) + elif os_helper.TESTFN_NONASCII: + name = os_helper.TESTFN_NONASCII + else: + self.skipTest("need os_helper.TESTFN_NONASCII") + + # Issue #16218 + source = 'print(ascii(__file__))\n' + script_name = _make_test_script(os.getcwd(), name, source) + self.addCleanup(os_helper.unlink, script_name) + rc, stdout, stderr = assert_python_ok(script_name) + self.assertEqual( + ascii(script_name), + stdout.rstrip().decode('ascii'), + 'stdout=%r stderr=%r' % (stdout, stderr)) + self.assertEqual(0, rc) + + def test_issue20500_exit_with_exception_value(self): + script = textwrap.dedent("""\ + import sys + error = None + try: + raise ValueError('some text') + except ValueError as err: + error = err + + if error: + sys.exit(error) + """) + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, 'script', script) + exitcode, stdout, stderr = assert_python_failure(script_name) + text = stderr.decode('ascii') + self.assertEqual(text.rstrip(), "some text") + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_syntaxerror_unindented_caret_position(self): + script = "1 + 1 = 2\n" + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, 'script', script) + exitcode, stdout, stderr = assert_python_failure(script_name) + text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read() + # Confirm that the caret is located under the '=' sign + self.assertIn("\n ^^^^^\n", text) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_syntaxerror_indented_caret_position(self): + script = textwrap.dedent("""\ + if True: + 1 + 1 = 2 + """) + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, 'script', script) + exitcode, stdout, stderr = assert_python_failure(script_name) + text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read() + # Confirm that the caret starts under the first 1 character + self.assertIn("\n 1 + 1 = 2\n ^^^^^\n", text) + + # Try the same with a form feed at the start of the indented line + script = ( + "if True:\n" + "\f 1 + 1 = 2\n" + ) + script_name = _make_test_script(script_dir, "script", script) + exitcode, stdout, stderr = assert_python_failure(script_name) + text = io.TextIOWrapper(io.BytesIO(stderr), "ascii").read() + self.assertNotIn("\f", text) + self.assertIn("\n 1 + 1 = 2\n ^^^^^\n", text) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_syntaxerror_multi_line_fstring(self): + script = 'foo = f"""{}\nfoo"""\n' + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, 'script', script) + exitcode, stdout, stderr = assert_python_failure(script_name) + self.assertEqual( + stderr.splitlines()[-3:], + [ + b' foo"""', + b' ^', + b'SyntaxError: f-string: empty expression not allowed', + ], + ) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_syntaxerror_invalid_escape_sequence_multi_line(self): + script = 'foo = """\\q"""\n' + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, 'script', script) + exitcode, stdout, stderr = assert_python_failure( + '-Werror', script_name, + ) + self.assertEqual( + stderr.splitlines()[-3:], + [ b' foo = """\\q"""', + b' ^^^^^^^^', + b'SyntaxError: invalid escape sequence \'\\q\'' + ], + ) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_consistent_sys_path_for_direct_execution(self): + # This test case ensures that the following all give the same + # sys.path configuration: + # + # ./python -s script_dir/__main__.py + # ./python -s script_dir + # ./python -I script_dir + script = textwrap.dedent("""\ + import sys + for entry in sys.path: + print(entry) + """) + # Always show full path diffs on errors + self.maxDiff = None + with os_helper.temp_dir() as work_dir, os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, '__main__', script) + # Reference output comes from directly executing __main__.py + # We omit PYTHONPATH and user site to align with isolated mode + p = spawn_python("-Es", script_name, cwd=work_dir) + out_by_name = kill_python(p).decode().splitlines() + self.assertEqual(out_by_name[0], script_dir) + self.assertNotIn(work_dir, out_by_name) + # Directory execution should give the same output + p = spawn_python("-Es", script_dir, cwd=work_dir) + out_by_dir = kill_python(p).decode().splitlines() + self.assertEqual(out_by_dir, out_by_name) + # As should directory execution in isolated mode + p = spawn_python("-I", script_dir, cwd=work_dir) + out_by_dir_isolated = kill_python(p).decode().splitlines() + self.assertEqual(out_by_dir_isolated, out_by_dir, out_by_name) + + def test_consistent_sys_path_for_module_execution(self): + # This test case ensures that the following both give the same + # sys.path configuration: + # ./python -sm script_pkg.__main__ + # ./python -sm script_pkg + # + # And that this fails as unable to find the package: + # ./python -Im script_pkg + script = textwrap.dedent("""\ + import sys + for entry in sys.path: + print(entry) + """) + # Always show full path diffs on errors + self.maxDiff = None + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + script_name = _make_test_script(script_dir, '__main__', script) + # Reference output comes from `-m script_pkg.__main__` + # We omit PYTHONPATH and user site to better align with the + # direct execution test cases + p = spawn_python("-sm", "script_pkg.__main__", cwd=work_dir) + out_by_module = kill_python(p).decode().splitlines() + self.assertEqual(out_by_module[0], work_dir) + self.assertNotIn(script_dir, out_by_module) + # Package execution should give the same output + p = spawn_python("-sm", "script_pkg", cwd=work_dir) + out_by_package = kill_python(p).decode().splitlines() + self.assertEqual(out_by_package, out_by_module) + # Isolated mode should fail with an import error + exitcode, stdout, stderr = assert_python_failure( + "-Im", "script_pkg", cwd=work_dir + ) + traceback_lines = stderr.decode().splitlines() + self.assertIn("No module named script_pkg", traceback_lines[-1]) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_nonexisting_script(self): + # bpo-34783: "./python script.py" must not crash + # if the script file doesn't exist. + # (Skip test for macOS framework builds because sys.executable name + # is not the actual Python executable file name. + script = 'nonexistingscript.py' + self.assertFalse(os.path.exists(script)) + + proc = spawn_python(script, text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = proc.communicate() + self.assertIn(": can't open file ", err) + self.assertNotEqual(proc.returncode, 0) + +# TODO: RUSTPYTHON +# def tearDownModule(): +def test_main(): + support.run_unittest(CmdLineTest) + support.reap_children() + + +if __name__ == '__main__': + # TODO: RUSTPYTHON + # unittest.main() + test_main() diff --git a/Lib/test/test_code_module.py b/Lib/test/test_code_module.py index d4fa7f1225..2b379fc378 100644 --- a/Lib/test/test_code_module.py +++ b/Lib/test/test_code_module.py @@ -1,157 +1,157 @@ -"Test InteractiveConsole and InteractiveInterpreter from code module" -import sys -import unittest -from textwrap import dedent -from contextlib import ExitStack -from unittest import mock -from test.support import import_helper - -code = import_helper.import_module('code') - - -class TestInteractiveConsole(unittest.TestCase): - - def setUp(self): - self.console = code.InteractiveConsole() - self.mock_sys() - - def mock_sys(self): - "Mock system environment for InteractiveConsole" - # use exit stack to match patch context managers to addCleanup - stack = ExitStack() - self.addCleanup(stack.close) - self.infunc = stack.enter_context(mock.patch('code.input', - create=True)) - self.stdout = stack.enter_context(mock.patch('code.sys.stdout')) - self.stderr = stack.enter_context(mock.patch('code.sys.stderr')) - prepatch = mock.patch('code.sys', wraps=code.sys, spec=code.sys) - self.sysmod = stack.enter_context(prepatch) - if sys.excepthook is sys.__excepthook__: - self.sysmod.excepthook = self.sysmod.__excepthook__ - del self.sysmod.ps1 - del self.sysmod.ps2 - - def test_ps1(self): - self.infunc.side_effect = EOFError('Finished') - self.console.interact() - self.assertEqual(self.sysmod.ps1, '>>> ') - self.sysmod.ps1 = 'custom1> ' - self.console.interact() - self.assertEqual(self.sysmod.ps1, 'custom1> ') - - def test_ps2(self): - self.infunc.side_effect = EOFError('Finished') - self.console.interact() - self.assertEqual(self.sysmod.ps2, '... ') - self.sysmod.ps1 = 'custom2> ' - self.console.interact() - self.assertEqual(self.sysmod.ps1, 'custom2> ') - - def test_console_stderr(self): - self.infunc.side_effect = ["'antioch'", "", EOFError('Finished')] - self.console.interact() - for call in list(self.stdout.method_calls): - if 'antioch' in ''.join(call[1]): - break - else: - raise AssertionError("no console stdout") - - def test_syntax_error(self): - self.infunc.side_effect = ["undefined", EOFError('Finished')] - self.console.interact() - for call in self.stderr.method_calls: - if 'NameError' in ''.join(call[1]): - break - else: - raise AssertionError("No syntax error from console") - - def test_sysexcepthook(self): - self.infunc.side_effect = ["raise ValueError('')", - EOFError('Finished')] - hook = mock.Mock() - self.sysmod.excepthook = hook - self.console.interact() - self.assertTrue(hook.called) - - def test_banner(self): - # with banner - self.infunc.side_effect = EOFError('Finished') - self.console.interact(banner='Foo') - self.assertEqual(len(self.stderr.method_calls), 3) - banner_call = self.stderr.method_calls[0] - self.assertEqual(banner_call, ['write', ('Foo\n',), {}]) - - # no banner - self.stderr.reset_mock() - self.infunc.side_effect = EOFError('Finished') - self.console.interact(banner='') - self.assertEqual(len(self.stderr.method_calls), 2) - - def test_exit_msg(self): - # default exit message - self.infunc.side_effect = EOFError('Finished') - self.console.interact(banner='') - self.assertEqual(len(self.stderr.method_calls), 2) - err_msg = self.stderr.method_calls[1] - expected = 'now exiting InteractiveConsole...\n' - self.assertEqual(err_msg, ['write', (expected,), {}]) - - # no exit message - self.stderr.reset_mock() - self.infunc.side_effect = EOFError('Finished') - self.console.interact(banner='', exitmsg='') - self.assertEqual(len(self.stderr.method_calls), 1) - - # custom exit message - self.stderr.reset_mock() - message = ( - 'bye! \N{GREEK SMALL LETTER ZETA}\N{CYRILLIC SMALL LETTER ZHE}' - ) - self.infunc.side_effect = EOFError('Finished') - self.console.interact(banner='', exitmsg=message) - self.assertEqual(len(self.stderr.method_calls), 2) - err_msg = self.stderr.method_calls[1] - expected = message + '\n' - self.assertEqual(err_msg, ['write', (expected,), {}]) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_cause_tb(self): - self.infunc.side_effect = ["raise ValueError('') from AttributeError", - EOFError('Finished')] - self.console.interact() - output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) - expected = dedent(""" - AttributeError - - The above exception was the direct cause of the following exception: - - Traceback (most recent call last): - File "", line 1, in - ValueError - """) - self.assertIn(expected, output) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_context_tb(self): - self.infunc.side_effect = ["try: ham\nexcept: eggs\n", - EOFError('Finished')] - self.console.interact() - output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) - expected = dedent(""" - Traceback (most recent call last): - File "", line 1, in - NameError: name 'ham' is not defined - - During handling of the above exception, another exception occurred: - - Traceback (most recent call last): - File "", line 2, in - NameError: name 'eggs' is not defined - """) - self.assertIn(expected, output) - - -if __name__ == "__main__": - unittest.main() +"Test InteractiveConsole and InteractiveInterpreter from code module" +import sys +import unittest +from textwrap import dedent +from contextlib import ExitStack +from unittest import mock +from test.support import import_helper + +code = import_helper.import_module('code') + + +class TestInteractiveConsole(unittest.TestCase): + + def setUp(self): + self.console = code.InteractiveConsole() + self.mock_sys() + + def mock_sys(self): + "Mock system environment for InteractiveConsole" + # use exit stack to match patch context managers to addCleanup + stack = ExitStack() + self.addCleanup(stack.close) + self.infunc = stack.enter_context(mock.patch('code.input', + create=True)) + self.stdout = stack.enter_context(mock.patch('code.sys.stdout')) + self.stderr = stack.enter_context(mock.patch('code.sys.stderr')) + prepatch = mock.patch('code.sys', wraps=code.sys, spec=code.sys) + self.sysmod = stack.enter_context(prepatch) + if sys.excepthook is sys.__excepthook__: + self.sysmod.excepthook = self.sysmod.__excepthook__ + del self.sysmod.ps1 + del self.sysmod.ps2 + + def test_ps1(self): + self.infunc.side_effect = EOFError('Finished') + self.console.interact() + self.assertEqual(self.sysmod.ps1, '>>> ') + self.sysmod.ps1 = 'custom1> ' + self.console.interact() + self.assertEqual(self.sysmod.ps1, 'custom1> ') + + def test_ps2(self): + self.infunc.side_effect = EOFError('Finished') + self.console.interact() + self.assertEqual(self.sysmod.ps2, '... ') + self.sysmod.ps1 = 'custom2> ' + self.console.interact() + self.assertEqual(self.sysmod.ps1, 'custom2> ') + + def test_console_stderr(self): + self.infunc.side_effect = ["'antioch'", "", EOFError('Finished')] + self.console.interact() + for call in list(self.stdout.method_calls): + if 'antioch' in ''.join(call[1]): + break + else: + raise AssertionError("no console stdout") + + def test_syntax_error(self): + self.infunc.side_effect = ["undefined", EOFError('Finished')] + self.console.interact() + for call in self.stderr.method_calls: + if 'NameError' in ''.join(call[1]): + break + else: + raise AssertionError("No syntax error from console") + + def test_sysexcepthook(self): + self.infunc.side_effect = ["raise ValueError('')", + EOFError('Finished')] + hook = mock.Mock() + self.sysmod.excepthook = hook + self.console.interact() + self.assertTrue(hook.called) + + def test_banner(self): + # with banner + self.infunc.side_effect = EOFError('Finished') + self.console.interact(banner='Foo') + self.assertEqual(len(self.stderr.method_calls), 3) + banner_call = self.stderr.method_calls[0] + self.assertEqual(banner_call, ['write', ('Foo\n',), {}]) + + # no banner + self.stderr.reset_mock() + self.infunc.side_effect = EOFError('Finished') + self.console.interact(banner='') + self.assertEqual(len(self.stderr.method_calls), 2) + + def test_exit_msg(self): + # default exit message + self.infunc.side_effect = EOFError('Finished') + self.console.interact(banner='') + self.assertEqual(len(self.stderr.method_calls), 2) + err_msg = self.stderr.method_calls[1] + expected = 'now exiting InteractiveConsole...\n' + self.assertEqual(err_msg, ['write', (expected,), {}]) + + # no exit message + self.stderr.reset_mock() + self.infunc.side_effect = EOFError('Finished') + self.console.interact(banner='', exitmsg='') + self.assertEqual(len(self.stderr.method_calls), 1) + + # custom exit message + self.stderr.reset_mock() + message = ( + 'bye! \N{GREEK SMALL LETTER ZETA}\N{CYRILLIC SMALL LETTER ZHE}' + ) + self.infunc.side_effect = EOFError('Finished') + self.console.interact(banner='', exitmsg=message) + self.assertEqual(len(self.stderr.method_calls), 2) + err_msg = self.stderr.method_calls[1] + expected = message + '\n' + self.assertEqual(err_msg, ['write', (expected,), {}]) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_cause_tb(self): + self.infunc.side_effect = ["raise ValueError('') from AttributeError", + EOFError('Finished')] + self.console.interact() + output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) + expected = dedent(""" + AttributeError + + The above exception was the direct cause of the following exception: + + Traceback (most recent call last): + File "", line 1, in + ValueError + """) + self.assertIn(expected, output) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_context_tb(self): + self.infunc.side_effect = ["try: ham\nexcept: eggs\n", + EOFError('Finished')] + self.console.interact() + output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) + expected = dedent(""" + Traceback (most recent call last): + File "", line 1, in + NameError: name 'ham' is not defined + + During handling of the above exception, another exception occurred: + + Traceback (most recent call last): + File "", line 2, in + NameError: name 'eggs' is not defined + """) + self.assertIn(expected, output) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index 7e14cdf3e4..30820b00e5 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -1,1068 +1,1068 @@ -"""Unit tests for contextlib.py, and other context managers.""" - -import io -import sys -import tempfile -import threading -import unittest -from contextlib import * # Tests __all__ -from test import support -from test.support import os_helper -import weakref - - -class TestAbstractContextManager(unittest.TestCase): - - def test_enter(self): - class DefaultEnter(AbstractContextManager): - def __exit__(self, *args): - super().__exit__(*args) - - manager = DefaultEnter() - self.assertIs(manager.__enter__(), manager) - - def test_exit_is_abstract(self): - class MissingExit(AbstractContextManager): - pass - - with self.assertRaises(TypeError): - MissingExit() - - def test_structural_subclassing(self): - class ManagerFromScratch: - def __enter__(self): - return self - def __exit__(self, exc_type, exc_value, traceback): - return None - - self.assertTrue(issubclass(ManagerFromScratch, AbstractContextManager)) - - class DefaultEnter(AbstractContextManager): - def __exit__(self, *args): - super().__exit__(*args) - - self.assertTrue(issubclass(DefaultEnter, AbstractContextManager)) - - class NoEnter(ManagerFromScratch): - __enter__ = None - - self.assertFalse(issubclass(NoEnter, AbstractContextManager)) - - class NoExit(ManagerFromScratch): - __exit__ = None - - self.assertFalse(issubclass(NoExit, AbstractContextManager)) - - -class ContextManagerTestCase(unittest.TestCase): - - def test_contextmanager_plain(self): - state = [] - @contextmanager - def woohoo(): - state.append(1) - yield 42 - state.append(999) - with woohoo() as x: - self.assertEqual(state, [1]) - self.assertEqual(x, 42) - state.append(x) - self.assertEqual(state, [1, 42, 999]) - - def test_contextmanager_finally(self): - state = [] - @contextmanager - def woohoo(): - state.append(1) - try: - yield 42 - finally: - state.append(999) - with self.assertRaises(ZeroDivisionError): - with woohoo() as x: - self.assertEqual(state, [1]) - self.assertEqual(x, 42) - state.append(x) - raise ZeroDivisionError() - self.assertEqual(state, [1, 42, 999]) - - def test_contextmanager_no_reraise(self): - @contextmanager - def whee(): - yield - ctx = whee() - ctx.__enter__() - # Calling __exit__ should not result in an exception - self.assertFalse(ctx.__exit__(TypeError, TypeError("foo"), None)) - - def test_contextmanager_trap_yield_after_throw(self): - @contextmanager - def whoo(): - try: - yield - except: - yield - ctx = whoo() - ctx.__enter__() - self.assertRaises( - RuntimeError, ctx.__exit__, TypeError, TypeError("foo"), None - ) - - def test_contextmanager_except(self): - state = [] - @contextmanager - def woohoo(): - state.append(1) - try: - yield 42 - except ZeroDivisionError as e: - state.append(e.args[0]) - self.assertEqual(state, [1, 42, 999]) - with woohoo() as x: - self.assertEqual(state, [1]) - self.assertEqual(x, 42) - state.append(x) - raise ZeroDivisionError(999) - self.assertEqual(state, [1, 42, 999]) - - def test_contextmanager_except_stopiter(self): - stop_exc = StopIteration('spam') - @contextmanager - def woohoo(): - yield - try: - with self.assertWarnsRegex(DeprecationWarning, - "StopIteration"): - with woohoo(): - raise stop_exc - except Exception as ex: - self.assertIs(ex, stop_exc) - else: - self.fail('StopIteration was suppressed') - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_contextmanager_except_pep479(self): - code = """\ -from __future__ import generator_stop -from contextlib import contextmanager -@contextmanager -def woohoo(): - yield -""" - locals = {} - exec(code, locals, locals) - woohoo = locals['woohoo'] - - stop_exc = StopIteration('spam') - try: - with woohoo(): - raise stop_exc - except Exception as ex: - self.assertIs(ex, stop_exc) - else: - self.fail('StopIteration was suppressed') - - def test_contextmanager_do_not_unchain_non_stopiteration_exceptions(self): - @contextmanager - def test_issue29692(): - try: - yield - except Exception as exc: - raise RuntimeError('issue29692:Chained') from exc - try: - with test_issue29692(): - raise ZeroDivisionError - except Exception as ex: - self.assertIs(type(ex), RuntimeError) - self.assertEqual(ex.args[0], 'issue29692:Chained') - self.assertIsInstance(ex.__cause__, ZeroDivisionError) - - try: - with test_issue29692(): - raise StopIteration('issue29692:Unchained') - except Exception as ex: - self.assertIs(type(ex), StopIteration) - self.assertEqual(ex.args[0], 'issue29692:Unchained') - self.assertIsNone(ex.__cause__) - - def _create_contextmanager_attribs(self): - def attribs(**kw): - def decorate(func): - for k,v in kw.items(): - setattr(func,k,v) - return func - return decorate - @contextmanager - @attribs(foo='bar') - def baz(spam): - """Whee!""" - return baz - - def test_contextmanager_attribs(self): - baz = self._create_contextmanager_attribs() - self.assertEqual(baz.__name__,'baz') - self.assertEqual(baz.foo, 'bar') - - @support.requires_docstrings - def test_contextmanager_doc_attrib(self): - baz = self._create_contextmanager_attribs() - self.assertEqual(baz.__doc__, "Whee!") - - @support.requires_docstrings - def test_instance_docstring_given_cm_docstring(self): - baz = self._create_contextmanager_attribs()(None) - self.assertEqual(baz.__doc__, "Whee!") - - def test_keywords(self): - # Ensure no keyword arguments are inhibited - @contextmanager - def woohoo(self, func, args, kwds): - yield (self, func, args, kwds) - with woohoo(self=11, func=22, args=33, kwds=44) as target: - self.assertEqual(target, (11, 22, 33, 44)) - - def test_nokeepref(self): - class A: - pass - - @contextmanager - def woohoo(a, b): - a = weakref.ref(a) - b = weakref.ref(b) - self.assertIsNone(a()) - self.assertIsNone(b()) - yield - - with woohoo(A(), b=A()): - pass - - def test_param_errors(self): - @contextmanager - def woohoo(a, *, b): - yield - - with self.assertRaises(TypeError): - woohoo() - with self.assertRaises(TypeError): - woohoo(3, 5) - with self.assertRaises(TypeError): - woohoo(b=3) - - def test_recursive(self): - depth = 0 - @contextmanager - def woohoo(): - nonlocal depth - before = depth - depth += 1 - yield - depth -= 1 - self.assertEqual(depth, before) - - @woohoo() - def recursive(): - if depth < 10: - recursive() - - recursive() - self.assertEqual(depth, 0) - - -class ClosingTestCase(unittest.TestCase): - - @support.requires_docstrings - def test_instance_docs(self): - # Issue 19330: ensure context manager instances have good docstrings - cm_docstring = closing.__doc__ - obj = closing(None) - self.assertEqual(obj.__doc__, cm_docstring) - - def test_closing(self): - state = [] - class C: - def close(self): - state.append(1) - x = C() - self.assertEqual(state, []) - with closing(x) as y: - self.assertEqual(x, y) - self.assertEqual(state, [1]) - - def test_closing_error(self): - state = [] - class C: - def close(self): - state.append(1) - x = C() - self.assertEqual(state, []) - with self.assertRaises(ZeroDivisionError): - with closing(x) as y: - self.assertEqual(x, y) - 1 / 0 - self.assertEqual(state, [1]) - - -class NullcontextTestCase(unittest.TestCase): - def test_nullcontext(self): - class C: - pass - c = C() - with nullcontext(c) as c_in: - self.assertIs(c_in, c) - - -class FileContextTestCase(unittest.TestCase): - - def testWithOpen(self): - tfn = tempfile.mktemp() - try: - f = None - with open(tfn, "w") as f: - self.assertFalse(f.closed) - f.write("Booh\n") - self.assertTrue(f.closed) - f = None - with self.assertRaises(ZeroDivisionError): - with open(tfn, "r") as f: - self.assertFalse(f.closed) - self.assertEqual(f.read(), "Booh\n") - 1 / 0 - self.assertTrue(f.closed) - finally: - os_helper.unlink(tfn) - -class LockContextTestCase(unittest.TestCase): - - def boilerPlate(self, lock, locked): - self.assertFalse(locked()) - with lock: - self.assertTrue(locked()) - self.assertFalse(locked()) - with self.assertRaises(ZeroDivisionError): - with lock: - self.assertTrue(locked()) - 1 / 0 - self.assertFalse(locked()) - - def testWithLock(self): - lock = threading.Lock() - self.boilerPlate(lock, lock.locked) - - def testWithRLock(self): - lock = threading.RLock() - self.boilerPlate(lock, lock._is_owned) - - def testWithCondition(self): - lock = threading.Condition() - def locked(): - return lock._is_owned() - self.boilerPlate(lock, locked) - - def testWithSemaphore(self): - lock = threading.Semaphore() - def locked(): - if lock.acquire(False): - lock.release() - return False - else: - return True - self.boilerPlate(lock, locked) - - def testWithBoundedSemaphore(self): - lock = threading.BoundedSemaphore() - def locked(): - if lock.acquire(False): - lock.release() - return False - else: - return True - self.boilerPlate(lock, locked) - - -class mycontext(ContextDecorator): - """Example decoration-compatible context manager for testing""" - started = False - exc = None - catch = False - - def __enter__(self): - self.started = True - return self - - def __exit__(self, *exc): - self.exc = exc - return self.catch - - -class TestContextDecorator(unittest.TestCase): - - @support.requires_docstrings - def test_instance_docs(self): - # Issue 19330: ensure context manager instances have good docstrings - cm_docstring = mycontext.__doc__ - obj = mycontext() - self.assertEqual(obj.__doc__, cm_docstring) - - def test_contextdecorator(self): - context = mycontext() - with context as result: - self.assertIs(result, context) - self.assertTrue(context.started) - - self.assertEqual(context.exc, (None, None, None)) - - - def test_contextdecorator_with_exception(self): - context = mycontext() - - with self.assertRaisesRegex(NameError, 'foo'): - with context: - raise NameError('foo') - self.assertIsNotNone(context.exc) - self.assertIs(context.exc[0], NameError) - - context = mycontext() - context.catch = True - with context: - raise NameError('foo') - self.assertIsNotNone(context.exc) - self.assertIs(context.exc[0], NameError) - - - def test_decorator(self): - context = mycontext() - - @context - def test(): - self.assertIsNone(context.exc) - self.assertTrue(context.started) - test() - self.assertEqual(context.exc, (None, None, None)) - - - def test_decorator_with_exception(self): - context = mycontext() - - @context - def test(): - self.assertIsNone(context.exc) - self.assertTrue(context.started) - raise NameError('foo') - - with self.assertRaisesRegex(NameError, 'foo'): - test() - self.assertIsNotNone(context.exc) - self.assertIs(context.exc[0], NameError) - - - def test_decorating_method(self): - context = mycontext() - - class Test(object): - - @context - def method(self, a, b, c=None): - self.a = a - self.b = b - self.c = c - - # these tests are for argument passing when used as a decorator - test = Test() - test.method(1, 2) - self.assertEqual(test.a, 1) - self.assertEqual(test.b, 2) - self.assertEqual(test.c, None) - - test = Test() - test.method('a', 'b', 'c') - self.assertEqual(test.a, 'a') - self.assertEqual(test.b, 'b') - self.assertEqual(test.c, 'c') - - test = Test() - test.method(a=1, b=2) - self.assertEqual(test.a, 1) - self.assertEqual(test.b, 2) - - - def test_typo_enter(self): - class mycontext(ContextDecorator): - def __unter__(self): - pass - def __exit__(self, *exc): - pass - - with self.assertRaises(AttributeError): - with mycontext(): - pass - - - def test_typo_exit(self): - class mycontext(ContextDecorator): - def __enter__(self): - pass - def __uxit__(self, *exc): - pass - - with self.assertRaises(AttributeError): - with mycontext(): - pass - - - def test_contextdecorator_as_mixin(self): - class somecontext(object): - started = False - exc = None - - def __enter__(self): - self.started = True - return self - - def __exit__(self, *exc): - self.exc = exc - - class mycontext(somecontext, ContextDecorator): - pass - - context = mycontext() - @context - def test(): - self.assertIsNone(context.exc) - self.assertTrue(context.started) - test() - self.assertEqual(context.exc, (None, None, None)) - - - def test_contextmanager_as_decorator(self): - @contextmanager - def woohoo(y): - state.append(y) - yield - state.append(999) - - state = [] - @woohoo(1) - def test(x): - self.assertEqual(state, [1]) - state.append(x) - test('something') - self.assertEqual(state, [1, 'something', 999]) - - # Issue #11647: Ensure the decorated function is 'reusable' - state = [] - test('something else') - self.assertEqual(state, [1, 'something else', 999]) - - -class TestBaseExitStack: - exit_stack = None - - @support.requires_docstrings - def test_instance_docs(self): - # Issue 19330: ensure context manager instances have good docstrings - cm_docstring = self.exit_stack.__doc__ - obj = self.exit_stack() - self.assertEqual(obj.__doc__, cm_docstring) - - def test_no_resources(self): - with self.exit_stack(): - pass - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_callback(self): - expected = [ - ((), {}), - ((1,), {}), - ((1,2), {}), - ((), dict(example=1)), - ((1,), dict(example=1)), - ((1,2), dict(example=1)), - ((1,2), dict(self=3, callback=4)), - ] - result = [] - def _exit(*args, **kwds): - """Test metadata propagation""" - result.append((args, kwds)) - with self.exit_stack() as stack: - for args, kwds in reversed(expected): - if args and kwds: - f = stack.callback(_exit, *args, **kwds) - elif args: - f = stack.callback(_exit, *args) - elif kwds: - f = stack.callback(_exit, **kwds) - else: - f = stack.callback(_exit) - self.assertIs(f, _exit) - for wrapper in stack._exit_callbacks: - self.assertIs(wrapper[1].__wrapped__, _exit) - self.assertNotEqual(wrapper[1].__name__, _exit.__name__) - self.assertIsNone(wrapper[1].__doc__, _exit.__doc__) - self.assertEqual(result, expected) - - result = [] - with self.exit_stack() as stack: - with self.assertRaises(TypeError): - stack.callback(arg=1) - with self.assertRaises(TypeError): - self.exit_stack.callback(arg=2) - with self.assertWarns(DeprecationWarning): - stack.callback(callback=_exit, arg=3) - self.assertEqual(result, [((), {'arg': 3})]) - - def test_push(self): - exc_raised = ZeroDivisionError - def _expect_exc(exc_type, exc, exc_tb): - self.assertIs(exc_type, exc_raised) - def _suppress_exc(*exc_details): - return True - def _expect_ok(exc_type, exc, exc_tb): - self.assertIsNone(exc_type) - self.assertIsNone(exc) - self.assertIsNone(exc_tb) - class ExitCM(object): - def __init__(self, check_exc): - self.check_exc = check_exc - def __enter__(self): - self.fail("Should not be called!") - def __exit__(self, *exc_details): - self.check_exc(*exc_details) - with self.exit_stack() as stack: - stack.push(_expect_ok) - self.assertIs(stack._exit_callbacks[-1][1], _expect_ok) - cm = ExitCM(_expect_ok) - stack.push(cm) - self.assertIs(stack._exit_callbacks[-1][1].__self__, cm) - stack.push(_suppress_exc) - self.assertIs(stack._exit_callbacks[-1][1], _suppress_exc) - cm = ExitCM(_expect_exc) - stack.push(cm) - self.assertIs(stack._exit_callbacks[-1][1].__self__, cm) - stack.push(_expect_exc) - self.assertIs(stack._exit_callbacks[-1][1], _expect_exc) - stack.push(_expect_exc) - self.assertIs(stack._exit_callbacks[-1][1], _expect_exc) - 1/0 - - def test_enter_context(self): - class TestCM(object): - def __enter__(self): - result.append(1) - def __exit__(self, *exc_details): - result.append(3) - - result = [] - cm = TestCM() - with self.exit_stack() as stack: - @stack.callback # Registered first => cleaned up last - def _exit(): - result.append(4) - self.assertIsNotNone(_exit) - stack.enter_context(cm) - self.assertIs(stack._exit_callbacks[-1][1].__self__, cm) - result.append(2) - self.assertEqual(result, [1, 2, 3, 4]) - - def test_close(self): - result = [] - with self.exit_stack() as stack: - @stack.callback - def _exit(): - result.append(1) - self.assertIsNotNone(_exit) - stack.close() - result.append(2) - self.assertEqual(result, [1, 2]) - - def test_pop_all(self): - result = [] - with self.exit_stack() as stack: - @stack.callback - def _exit(): - result.append(3) - self.assertIsNotNone(_exit) - new_stack = stack.pop_all() - result.append(1) - result.append(2) - new_stack.close() - self.assertEqual(result, [1, 2, 3]) - - def test_exit_raise(self): - with self.assertRaises(ZeroDivisionError): - with self.exit_stack() as stack: - stack.push(lambda *exc: False) - 1/0 - - def test_exit_suppress(self): - with self.exit_stack() as stack: - stack.push(lambda *exc: True) - 1/0 - - def test_exit_exception_chaining_reference(self): - # Sanity check to make sure that ExitStack chaining matches - # actual nested with statements - class RaiseExc: - def __init__(self, exc): - self.exc = exc - def __enter__(self): - return self - def __exit__(self, *exc_details): - raise self.exc - - class RaiseExcWithContext: - def __init__(self, outer, inner): - self.outer = outer - self.inner = inner - def __enter__(self): - return self - def __exit__(self, *exc_details): - try: - raise self.inner - except: - raise self.outer - - class SuppressExc: - def __enter__(self): - return self - def __exit__(self, *exc_details): - type(self).saved_details = exc_details - return True - - try: - with RaiseExc(IndexError): - with RaiseExcWithContext(KeyError, AttributeError): - with SuppressExc(): - with RaiseExc(ValueError): - 1 / 0 - except IndexError as exc: - self.assertIsInstance(exc.__context__, KeyError) - self.assertIsInstance(exc.__context__.__context__, AttributeError) - # Inner exceptions were suppressed - self.assertIsNone(exc.__context__.__context__.__context__) - else: - self.fail("Expected IndexError, but no exception was raised") - # Check the inner exceptions - inner_exc = SuppressExc.saved_details[1] - self.assertIsInstance(inner_exc, ValueError) - self.assertIsInstance(inner_exc.__context__, ZeroDivisionError) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_exit_exception_chaining(self): - # Ensure exception chaining matches the reference behaviour - def raise_exc(exc): - raise exc - - saved_details = None - def suppress_exc(*exc_details): - nonlocal saved_details - saved_details = exc_details - return True - - try: - with self.exit_stack() as stack: - stack.callback(raise_exc, IndexError) - stack.callback(raise_exc, KeyError) - stack.callback(raise_exc, AttributeError) - stack.push(suppress_exc) - stack.callback(raise_exc, ValueError) - 1 / 0 - except IndexError as exc: - self.assertIsInstance(exc.__context__, KeyError) - self.assertIsInstance(exc.__context__.__context__, AttributeError) - # Inner exceptions were suppressed - self.assertIsNone(exc.__context__.__context__.__context__) - else: - self.fail("Expected IndexError, but no exception was raised") - # Check the inner exceptions - inner_exc = saved_details[1] - self.assertIsInstance(inner_exc, ValueError) - self.assertIsInstance(inner_exc.__context__, ZeroDivisionError) - - def test_exit_exception_non_suppressing(self): - # http://bugs.python.org/issue19092 - def raise_exc(exc): - raise exc - - def suppress_exc(*exc_details): - return True - - try: - with self.exit_stack() as stack: - stack.callback(lambda: None) - stack.callback(raise_exc, IndexError) - except Exception as exc: - self.assertIsInstance(exc, IndexError) - else: - self.fail("Expected IndexError, but no exception was raised") - - try: - with self.exit_stack() as stack: - stack.callback(raise_exc, KeyError) - stack.push(suppress_exc) - stack.callback(raise_exc, IndexError) - except Exception as exc: - self.assertIsInstance(exc, KeyError) - else: - self.fail("Expected KeyError, but no exception was raised") - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_exit_exception_with_correct_context(self): - # http://bugs.python.org/issue20317 - @contextmanager - def gets_the_context_right(exc): - try: - yield - finally: - raise exc - - exc1 = Exception(1) - exc2 = Exception(2) - exc3 = Exception(3) - exc4 = Exception(4) - - # The contextmanager already fixes the context, so prior to the - # fix, ExitStack would try to fix it *again* and get into an - # infinite self-referential loop - try: - with self.exit_stack() as stack: - stack.enter_context(gets_the_context_right(exc4)) - stack.enter_context(gets_the_context_right(exc3)) - stack.enter_context(gets_the_context_right(exc2)) - raise exc1 - except Exception as exc: - self.assertIs(exc, exc4) - self.assertIs(exc.__context__, exc3) - self.assertIs(exc.__context__.__context__, exc2) - self.assertIs(exc.__context__.__context__.__context__, exc1) - self.assertIsNone( - exc.__context__.__context__.__context__.__context__) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_exit_exception_with_existing_context(self): - # Addresses a lack of test coverage discovered after checking in a - # fix for issue 20317 that still contained debugging code. - def raise_nested(inner_exc, outer_exc): - try: - raise inner_exc - finally: - raise outer_exc - exc1 = Exception(1) - exc2 = Exception(2) - exc3 = Exception(3) - exc4 = Exception(4) - exc5 = Exception(5) - try: - with self.exit_stack() as stack: - stack.callback(raise_nested, exc4, exc5) - stack.callback(raise_nested, exc2, exc3) - raise exc1 - except Exception as exc: - self.assertIs(exc, exc5) - self.assertIs(exc.__context__, exc4) - self.assertIs(exc.__context__.__context__, exc3) - self.assertIs(exc.__context__.__context__.__context__, exc2) - self.assertIs( - exc.__context__.__context__.__context__.__context__, exc1) - self.assertIsNone( - exc.__context__.__context__.__context__.__context__.__context__) - - def test_body_exception_suppress(self): - def suppress_exc(*exc_details): - return True - try: - with self.exit_stack() as stack: - stack.push(suppress_exc) - 1/0 - except IndexError as exc: - self.fail("Expected no exception, got IndexError") - - def test_exit_exception_chaining_suppress(self): - with self.exit_stack() as stack: - stack.push(lambda *exc: True) - stack.push(lambda *exc: 1/0) - stack.push(lambda *exc: {}[1]) - - def test_excessive_nesting(self): - # The original implementation would die with RecursionError here - with self.exit_stack() as stack: - for i in range(10000): - stack.callback(int) - - def test_instance_bypass(self): - class Example(object): pass - cm = Example() - cm.__exit__ = object() - stack = self.exit_stack() - self.assertRaises(AttributeError, stack.enter_context, cm) - stack.push(cm) - self.assertIs(stack._exit_callbacks[-1][1], cm) - - def test_dont_reraise_RuntimeError(self): - # https://bugs.python.org/issue27122 - class UniqueException(Exception): pass - class UniqueRuntimeError(RuntimeError): pass - - @contextmanager - def second(): - try: - yield 1 - except Exception as exc: - raise UniqueException("new exception") from exc - - @contextmanager - def first(): - try: - yield 1 - except Exception as exc: - raise exc - - # The UniqueRuntimeError should be caught by second()'s exception - # handler which chain raised a new UniqueException. - with self.assertRaises(UniqueException) as err_ctx: - with self.exit_stack() as es_ctx: - es_ctx.enter_context(second()) - es_ctx.enter_context(first()) - raise UniqueRuntimeError("please no infinite loop.") - - exc = err_ctx.exception - self.assertIsInstance(exc, UniqueException) - self.assertIsInstance(exc.__context__, UniqueRuntimeError) - self.assertIsNone(exc.__context__.__context__) - self.assertIsNone(exc.__context__.__cause__) - self.assertIs(exc.__cause__, exc.__context__) - - -class TestExitStack(TestBaseExitStack, unittest.TestCase): - exit_stack = ExitStack - - -class TestRedirectStream: - - redirect_stream = None - orig_stream = None - - @support.requires_docstrings - def test_instance_docs(self): - # Issue 19330: ensure context manager instances have good docstrings - cm_docstring = self.redirect_stream.__doc__ - obj = self.redirect_stream(None) - self.assertEqual(obj.__doc__, cm_docstring) - - def test_no_redirect_in_init(self): - orig_stdout = getattr(sys, self.orig_stream) - self.redirect_stream(None) - self.assertIs(getattr(sys, self.orig_stream), orig_stdout) - - def test_redirect_to_string_io(self): - f = io.StringIO() - msg = "Consider an API like help(), which prints directly to stdout" - orig_stdout = getattr(sys, self.orig_stream) - with self.redirect_stream(f): - print(msg, file=getattr(sys, self.orig_stream)) - self.assertIs(getattr(sys, self.orig_stream), orig_stdout) - s = f.getvalue().strip() - self.assertEqual(s, msg) - - def test_enter_result_is_target(self): - f = io.StringIO() - with self.redirect_stream(f) as enter_result: - self.assertIs(enter_result, f) - - def test_cm_is_reusable(self): - f = io.StringIO() - write_to_f = self.redirect_stream(f) - orig_stdout = getattr(sys, self.orig_stream) - with write_to_f: - print("Hello", end=" ", file=getattr(sys, self.orig_stream)) - with write_to_f: - print("World!", file=getattr(sys, self.orig_stream)) - self.assertIs(getattr(sys, self.orig_stream), orig_stdout) - s = f.getvalue() - self.assertEqual(s, "Hello World!\n") - - def test_cm_is_reentrant(self): - f = io.StringIO() - write_to_f = self.redirect_stream(f) - orig_stdout = getattr(sys, self.orig_stream) - with write_to_f: - print("Hello", end=" ", file=getattr(sys, self.orig_stream)) - with write_to_f: - print("World!", file=getattr(sys, self.orig_stream)) - self.assertIs(getattr(sys, self.orig_stream), orig_stdout) - s = f.getvalue() - self.assertEqual(s, "Hello World!\n") - - -class TestRedirectStdout(TestRedirectStream, unittest.TestCase): - - redirect_stream = redirect_stdout - orig_stream = "stdout" - - -class TestRedirectStderr(TestRedirectStream, unittest.TestCase): - - redirect_stream = redirect_stderr - orig_stream = "stderr" - - -class TestSuppress(unittest.TestCase): - - @support.requires_docstrings - def test_instance_docs(self): - # Issue 19330: ensure context manager instances have good docstrings - cm_docstring = suppress.__doc__ - obj = suppress() - self.assertEqual(obj.__doc__, cm_docstring) - - def test_no_result_from_enter(self): - with suppress(ValueError) as enter_result: - self.assertIsNone(enter_result) - - def test_no_exception(self): - with suppress(ValueError): - self.assertEqual(pow(2, 5), 32) - - def test_exact_exception(self): - with suppress(TypeError): - len(5) - - def test_exception_hierarchy(self): - with suppress(LookupError): - 'Hello'[50] - - def test_other_exception(self): - with self.assertRaises(ZeroDivisionError): - with suppress(TypeError): - 1/0 - - def test_no_args(self): - with self.assertRaises(ZeroDivisionError): - with suppress(): - 1/0 - - def test_multiple_exception_args(self): - with suppress(ZeroDivisionError, TypeError): - 1/0 - with suppress(ZeroDivisionError, TypeError): - len(5) - - def test_cm_is_reentrant(self): - ignore_exceptions = suppress(Exception) - with ignore_exceptions: - pass - with ignore_exceptions: - len(5) - with ignore_exceptions: - with ignore_exceptions: # Check nested usage - len(5) - outer_continued = True - 1/0 - self.assertTrue(outer_continued) - -if __name__ == "__main__": - unittest.main() +"""Unit tests for contextlib.py, and other context managers.""" + +import io +import sys +import tempfile +import threading +import unittest +from contextlib import * # Tests __all__ +from test import support +from test.support import os_helper +import weakref + + +class TestAbstractContextManager(unittest.TestCase): + + def test_enter(self): + class DefaultEnter(AbstractContextManager): + def __exit__(self, *args): + super().__exit__(*args) + + manager = DefaultEnter() + self.assertIs(manager.__enter__(), manager) + + def test_exit_is_abstract(self): + class MissingExit(AbstractContextManager): + pass + + with self.assertRaises(TypeError): + MissingExit() + + def test_structural_subclassing(self): + class ManagerFromScratch: + def __enter__(self): + return self + def __exit__(self, exc_type, exc_value, traceback): + return None + + self.assertTrue(issubclass(ManagerFromScratch, AbstractContextManager)) + + class DefaultEnter(AbstractContextManager): + def __exit__(self, *args): + super().__exit__(*args) + + self.assertTrue(issubclass(DefaultEnter, AbstractContextManager)) + + class NoEnter(ManagerFromScratch): + __enter__ = None + + self.assertFalse(issubclass(NoEnter, AbstractContextManager)) + + class NoExit(ManagerFromScratch): + __exit__ = None + + self.assertFalse(issubclass(NoExit, AbstractContextManager)) + + +class ContextManagerTestCase(unittest.TestCase): + + def test_contextmanager_plain(self): + state = [] + @contextmanager + def woohoo(): + state.append(1) + yield 42 + state.append(999) + with woohoo() as x: + self.assertEqual(state, [1]) + self.assertEqual(x, 42) + state.append(x) + self.assertEqual(state, [1, 42, 999]) + + def test_contextmanager_finally(self): + state = [] + @contextmanager + def woohoo(): + state.append(1) + try: + yield 42 + finally: + state.append(999) + with self.assertRaises(ZeroDivisionError): + with woohoo() as x: + self.assertEqual(state, [1]) + self.assertEqual(x, 42) + state.append(x) + raise ZeroDivisionError() + self.assertEqual(state, [1, 42, 999]) + + def test_contextmanager_no_reraise(self): + @contextmanager + def whee(): + yield + ctx = whee() + ctx.__enter__() + # Calling __exit__ should not result in an exception + self.assertFalse(ctx.__exit__(TypeError, TypeError("foo"), None)) + + def test_contextmanager_trap_yield_after_throw(self): + @contextmanager + def whoo(): + try: + yield + except: + yield + ctx = whoo() + ctx.__enter__() + self.assertRaises( + RuntimeError, ctx.__exit__, TypeError, TypeError("foo"), None + ) + + def test_contextmanager_except(self): + state = [] + @contextmanager + def woohoo(): + state.append(1) + try: + yield 42 + except ZeroDivisionError as e: + state.append(e.args[0]) + self.assertEqual(state, [1, 42, 999]) + with woohoo() as x: + self.assertEqual(state, [1]) + self.assertEqual(x, 42) + state.append(x) + raise ZeroDivisionError(999) + self.assertEqual(state, [1, 42, 999]) + + def test_contextmanager_except_stopiter(self): + stop_exc = StopIteration('spam') + @contextmanager + def woohoo(): + yield + try: + with self.assertWarnsRegex(DeprecationWarning, + "StopIteration"): + with woohoo(): + raise stop_exc + except Exception as ex: + self.assertIs(ex, stop_exc) + else: + self.fail('StopIteration was suppressed') + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_contextmanager_except_pep479(self): + code = """\ +from __future__ import generator_stop +from contextlib import contextmanager +@contextmanager +def woohoo(): + yield +""" + locals = {} + exec(code, locals, locals) + woohoo = locals['woohoo'] + + stop_exc = StopIteration('spam') + try: + with woohoo(): + raise stop_exc + except Exception as ex: + self.assertIs(ex, stop_exc) + else: + self.fail('StopIteration was suppressed') + + def test_contextmanager_do_not_unchain_non_stopiteration_exceptions(self): + @contextmanager + def test_issue29692(): + try: + yield + except Exception as exc: + raise RuntimeError('issue29692:Chained') from exc + try: + with test_issue29692(): + raise ZeroDivisionError + except Exception as ex: + self.assertIs(type(ex), RuntimeError) + self.assertEqual(ex.args[0], 'issue29692:Chained') + self.assertIsInstance(ex.__cause__, ZeroDivisionError) + + try: + with test_issue29692(): + raise StopIteration('issue29692:Unchained') + except Exception as ex: + self.assertIs(type(ex), StopIteration) + self.assertEqual(ex.args[0], 'issue29692:Unchained') + self.assertIsNone(ex.__cause__) + + def _create_contextmanager_attribs(self): + def attribs(**kw): + def decorate(func): + for k,v in kw.items(): + setattr(func,k,v) + return func + return decorate + @contextmanager + @attribs(foo='bar') + def baz(spam): + """Whee!""" + return baz + + def test_contextmanager_attribs(self): + baz = self._create_contextmanager_attribs() + self.assertEqual(baz.__name__,'baz') + self.assertEqual(baz.foo, 'bar') + + @support.requires_docstrings + def test_contextmanager_doc_attrib(self): + baz = self._create_contextmanager_attribs() + self.assertEqual(baz.__doc__, "Whee!") + + @support.requires_docstrings + def test_instance_docstring_given_cm_docstring(self): + baz = self._create_contextmanager_attribs()(None) + self.assertEqual(baz.__doc__, "Whee!") + + def test_keywords(self): + # Ensure no keyword arguments are inhibited + @contextmanager + def woohoo(self, func, args, kwds): + yield (self, func, args, kwds) + with woohoo(self=11, func=22, args=33, kwds=44) as target: + self.assertEqual(target, (11, 22, 33, 44)) + + def test_nokeepref(self): + class A: + pass + + @contextmanager + def woohoo(a, b): + a = weakref.ref(a) + b = weakref.ref(b) + self.assertIsNone(a()) + self.assertIsNone(b()) + yield + + with woohoo(A(), b=A()): + pass + + def test_param_errors(self): + @contextmanager + def woohoo(a, *, b): + yield + + with self.assertRaises(TypeError): + woohoo() + with self.assertRaises(TypeError): + woohoo(3, 5) + with self.assertRaises(TypeError): + woohoo(b=3) + + def test_recursive(self): + depth = 0 + @contextmanager + def woohoo(): + nonlocal depth + before = depth + depth += 1 + yield + depth -= 1 + self.assertEqual(depth, before) + + @woohoo() + def recursive(): + if depth < 10: + recursive() + + recursive() + self.assertEqual(depth, 0) + + +class ClosingTestCase(unittest.TestCase): + + @support.requires_docstrings + def test_instance_docs(self): + # Issue 19330: ensure context manager instances have good docstrings + cm_docstring = closing.__doc__ + obj = closing(None) + self.assertEqual(obj.__doc__, cm_docstring) + + def test_closing(self): + state = [] + class C: + def close(self): + state.append(1) + x = C() + self.assertEqual(state, []) + with closing(x) as y: + self.assertEqual(x, y) + self.assertEqual(state, [1]) + + def test_closing_error(self): + state = [] + class C: + def close(self): + state.append(1) + x = C() + self.assertEqual(state, []) + with self.assertRaises(ZeroDivisionError): + with closing(x) as y: + self.assertEqual(x, y) + 1 / 0 + self.assertEqual(state, [1]) + + +class NullcontextTestCase(unittest.TestCase): + def test_nullcontext(self): + class C: + pass + c = C() + with nullcontext(c) as c_in: + self.assertIs(c_in, c) + + +class FileContextTestCase(unittest.TestCase): + + def testWithOpen(self): + tfn = tempfile.mktemp() + try: + f = None + with open(tfn, "w") as f: + self.assertFalse(f.closed) + f.write("Booh\n") + self.assertTrue(f.closed) + f = None + with self.assertRaises(ZeroDivisionError): + with open(tfn, "r") as f: + self.assertFalse(f.closed) + self.assertEqual(f.read(), "Booh\n") + 1 / 0 + self.assertTrue(f.closed) + finally: + os_helper.unlink(tfn) + +class LockContextTestCase(unittest.TestCase): + + def boilerPlate(self, lock, locked): + self.assertFalse(locked()) + with lock: + self.assertTrue(locked()) + self.assertFalse(locked()) + with self.assertRaises(ZeroDivisionError): + with lock: + self.assertTrue(locked()) + 1 / 0 + self.assertFalse(locked()) + + def testWithLock(self): + lock = threading.Lock() + self.boilerPlate(lock, lock.locked) + + def testWithRLock(self): + lock = threading.RLock() + self.boilerPlate(lock, lock._is_owned) + + def testWithCondition(self): + lock = threading.Condition() + def locked(): + return lock._is_owned() + self.boilerPlate(lock, locked) + + def testWithSemaphore(self): + lock = threading.Semaphore() + def locked(): + if lock.acquire(False): + lock.release() + return False + else: + return True + self.boilerPlate(lock, locked) + + def testWithBoundedSemaphore(self): + lock = threading.BoundedSemaphore() + def locked(): + if lock.acquire(False): + lock.release() + return False + else: + return True + self.boilerPlate(lock, locked) + + +class mycontext(ContextDecorator): + """Example decoration-compatible context manager for testing""" + started = False + exc = None + catch = False + + def __enter__(self): + self.started = True + return self + + def __exit__(self, *exc): + self.exc = exc + return self.catch + + +class TestContextDecorator(unittest.TestCase): + + @support.requires_docstrings + def test_instance_docs(self): + # Issue 19330: ensure context manager instances have good docstrings + cm_docstring = mycontext.__doc__ + obj = mycontext() + self.assertEqual(obj.__doc__, cm_docstring) + + def test_contextdecorator(self): + context = mycontext() + with context as result: + self.assertIs(result, context) + self.assertTrue(context.started) + + self.assertEqual(context.exc, (None, None, None)) + + + def test_contextdecorator_with_exception(self): + context = mycontext() + + with self.assertRaisesRegex(NameError, 'foo'): + with context: + raise NameError('foo') + self.assertIsNotNone(context.exc) + self.assertIs(context.exc[0], NameError) + + context = mycontext() + context.catch = True + with context: + raise NameError('foo') + self.assertIsNotNone(context.exc) + self.assertIs(context.exc[0], NameError) + + + def test_decorator(self): + context = mycontext() + + @context + def test(): + self.assertIsNone(context.exc) + self.assertTrue(context.started) + test() + self.assertEqual(context.exc, (None, None, None)) + + + def test_decorator_with_exception(self): + context = mycontext() + + @context + def test(): + self.assertIsNone(context.exc) + self.assertTrue(context.started) + raise NameError('foo') + + with self.assertRaisesRegex(NameError, 'foo'): + test() + self.assertIsNotNone(context.exc) + self.assertIs(context.exc[0], NameError) + + + def test_decorating_method(self): + context = mycontext() + + class Test(object): + + @context + def method(self, a, b, c=None): + self.a = a + self.b = b + self.c = c + + # these tests are for argument passing when used as a decorator + test = Test() + test.method(1, 2) + self.assertEqual(test.a, 1) + self.assertEqual(test.b, 2) + self.assertEqual(test.c, None) + + test = Test() + test.method('a', 'b', 'c') + self.assertEqual(test.a, 'a') + self.assertEqual(test.b, 'b') + self.assertEqual(test.c, 'c') + + test = Test() + test.method(a=1, b=2) + self.assertEqual(test.a, 1) + self.assertEqual(test.b, 2) + + + def test_typo_enter(self): + class mycontext(ContextDecorator): + def __unter__(self): + pass + def __exit__(self, *exc): + pass + + with self.assertRaises(AttributeError): + with mycontext(): + pass + + + def test_typo_exit(self): + class mycontext(ContextDecorator): + def __enter__(self): + pass + def __uxit__(self, *exc): + pass + + with self.assertRaises(AttributeError): + with mycontext(): + pass + + + def test_contextdecorator_as_mixin(self): + class somecontext(object): + started = False + exc = None + + def __enter__(self): + self.started = True + return self + + def __exit__(self, *exc): + self.exc = exc + + class mycontext(somecontext, ContextDecorator): + pass + + context = mycontext() + @context + def test(): + self.assertIsNone(context.exc) + self.assertTrue(context.started) + test() + self.assertEqual(context.exc, (None, None, None)) + + + def test_contextmanager_as_decorator(self): + @contextmanager + def woohoo(y): + state.append(y) + yield + state.append(999) + + state = [] + @woohoo(1) + def test(x): + self.assertEqual(state, [1]) + state.append(x) + test('something') + self.assertEqual(state, [1, 'something', 999]) + + # Issue #11647: Ensure the decorated function is 'reusable' + state = [] + test('something else') + self.assertEqual(state, [1, 'something else', 999]) + + +class TestBaseExitStack: + exit_stack = None + + @support.requires_docstrings + def test_instance_docs(self): + # Issue 19330: ensure context manager instances have good docstrings + cm_docstring = self.exit_stack.__doc__ + obj = self.exit_stack() + self.assertEqual(obj.__doc__, cm_docstring) + + def test_no_resources(self): + with self.exit_stack(): + pass + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_callback(self): + expected = [ + ((), {}), + ((1,), {}), + ((1,2), {}), + ((), dict(example=1)), + ((1,), dict(example=1)), + ((1,2), dict(example=1)), + ((1,2), dict(self=3, callback=4)), + ] + result = [] + def _exit(*args, **kwds): + """Test metadata propagation""" + result.append((args, kwds)) + with self.exit_stack() as stack: + for args, kwds in reversed(expected): + if args and kwds: + f = stack.callback(_exit, *args, **kwds) + elif args: + f = stack.callback(_exit, *args) + elif kwds: + f = stack.callback(_exit, **kwds) + else: + f = stack.callback(_exit) + self.assertIs(f, _exit) + for wrapper in stack._exit_callbacks: + self.assertIs(wrapper[1].__wrapped__, _exit) + self.assertNotEqual(wrapper[1].__name__, _exit.__name__) + self.assertIsNone(wrapper[1].__doc__, _exit.__doc__) + self.assertEqual(result, expected) + + result = [] + with self.exit_stack() as stack: + with self.assertRaises(TypeError): + stack.callback(arg=1) + with self.assertRaises(TypeError): + self.exit_stack.callback(arg=2) + with self.assertWarns(DeprecationWarning): + stack.callback(callback=_exit, arg=3) + self.assertEqual(result, [((), {'arg': 3})]) + + def test_push(self): + exc_raised = ZeroDivisionError + def _expect_exc(exc_type, exc, exc_tb): + self.assertIs(exc_type, exc_raised) + def _suppress_exc(*exc_details): + return True + def _expect_ok(exc_type, exc, exc_tb): + self.assertIsNone(exc_type) + self.assertIsNone(exc) + self.assertIsNone(exc_tb) + class ExitCM(object): + def __init__(self, check_exc): + self.check_exc = check_exc + def __enter__(self): + self.fail("Should not be called!") + def __exit__(self, *exc_details): + self.check_exc(*exc_details) + with self.exit_stack() as stack: + stack.push(_expect_ok) + self.assertIs(stack._exit_callbacks[-1][1], _expect_ok) + cm = ExitCM(_expect_ok) + stack.push(cm) + self.assertIs(stack._exit_callbacks[-1][1].__self__, cm) + stack.push(_suppress_exc) + self.assertIs(stack._exit_callbacks[-1][1], _suppress_exc) + cm = ExitCM(_expect_exc) + stack.push(cm) + self.assertIs(stack._exit_callbacks[-1][1].__self__, cm) + stack.push(_expect_exc) + self.assertIs(stack._exit_callbacks[-1][1], _expect_exc) + stack.push(_expect_exc) + self.assertIs(stack._exit_callbacks[-1][1], _expect_exc) + 1/0 + + def test_enter_context(self): + class TestCM(object): + def __enter__(self): + result.append(1) + def __exit__(self, *exc_details): + result.append(3) + + result = [] + cm = TestCM() + with self.exit_stack() as stack: + @stack.callback # Registered first => cleaned up last + def _exit(): + result.append(4) + self.assertIsNotNone(_exit) + stack.enter_context(cm) + self.assertIs(stack._exit_callbacks[-1][1].__self__, cm) + result.append(2) + self.assertEqual(result, [1, 2, 3, 4]) + + def test_close(self): + result = [] + with self.exit_stack() as stack: + @stack.callback + def _exit(): + result.append(1) + self.assertIsNotNone(_exit) + stack.close() + result.append(2) + self.assertEqual(result, [1, 2]) + + def test_pop_all(self): + result = [] + with self.exit_stack() as stack: + @stack.callback + def _exit(): + result.append(3) + self.assertIsNotNone(_exit) + new_stack = stack.pop_all() + result.append(1) + result.append(2) + new_stack.close() + self.assertEqual(result, [1, 2, 3]) + + def test_exit_raise(self): + with self.assertRaises(ZeroDivisionError): + with self.exit_stack() as stack: + stack.push(lambda *exc: False) + 1/0 + + def test_exit_suppress(self): + with self.exit_stack() as stack: + stack.push(lambda *exc: True) + 1/0 + + def test_exit_exception_chaining_reference(self): + # Sanity check to make sure that ExitStack chaining matches + # actual nested with statements + class RaiseExc: + def __init__(self, exc): + self.exc = exc + def __enter__(self): + return self + def __exit__(self, *exc_details): + raise self.exc + + class RaiseExcWithContext: + def __init__(self, outer, inner): + self.outer = outer + self.inner = inner + def __enter__(self): + return self + def __exit__(self, *exc_details): + try: + raise self.inner + except: + raise self.outer + + class SuppressExc: + def __enter__(self): + return self + def __exit__(self, *exc_details): + type(self).saved_details = exc_details + return True + + try: + with RaiseExc(IndexError): + with RaiseExcWithContext(KeyError, AttributeError): + with SuppressExc(): + with RaiseExc(ValueError): + 1 / 0 + except IndexError as exc: + self.assertIsInstance(exc.__context__, KeyError) + self.assertIsInstance(exc.__context__.__context__, AttributeError) + # Inner exceptions were suppressed + self.assertIsNone(exc.__context__.__context__.__context__) + else: + self.fail("Expected IndexError, but no exception was raised") + # Check the inner exceptions + inner_exc = SuppressExc.saved_details[1] + self.assertIsInstance(inner_exc, ValueError) + self.assertIsInstance(inner_exc.__context__, ZeroDivisionError) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_exit_exception_chaining(self): + # Ensure exception chaining matches the reference behaviour + def raise_exc(exc): + raise exc + + saved_details = None + def suppress_exc(*exc_details): + nonlocal saved_details + saved_details = exc_details + return True + + try: + with self.exit_stack() as stack: + stack.callback(raise_exc, IndexError) + stack.callback(raise_exc, KeyError) + stack.callback(raise_exc, AttributeError) + stack.push(suppress_exc) + stack.callback(raise_exc, ValueError) + 1 / 0 + except IndexError as exc: + self.assertIsInstance(exc.__context__, KeyError) + self.assertIsInstance(exc.__context__.__context__, AttributeError) + # Inner exceptions were suppressed + self.assertIsNone(exc.__context__.__context__.__context__) + else: + self.fail("Expected IndexError, but no exception was raised") + # Check the inner exceptions + inner_exc = saved_details[1] + self.assertIsInstance(inner_exc, ValueError) + self.assertIsInstance(inner_exc.__context__, ZeroDivisionError) + + def test_exit_exception_non_suppressing(self): + # http://bugs.python.org/issue19092 + def raise_exc(exc): + raise exc + + def suppress_exc(*exc_details): + return True + + try: + with self.exit_stack() as stack: + stack.callback(lambda: None) + stack.callback(raise_exc, IndexError) + except Exception as exc: + self.assertIsInstance(exc, IndexError) + else: + self.fail("Expected IndexError, but no exception was raised") + + try: + with self.exit_stack() as stack: + stack.callback(raise_exc, KeyError) + stack.push(suppress_exc) + stack.callback(raise_exc, IndexError) + except Exception as exc: + self.assertIsInstance(exc, KeyError) + else: + self.fail("Expected KeyError, but no exception was raised") + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_exit_exception_with_correct_context(self): + # http://bugs.python.org/issue20317 + @contextmanager + def gets_the_context_right(exc): + try: + yield + finally: + raise exc + + exc1 = Exception(1) + exc2 = Exception(2) + exc3 = Exception(3) + exc4 = Exception(4) + + # The contextmanager already fixes the context, so prior to the + # fix, ExitStack would try to fix it *again* and get into an + # infinite self-referential loop + try: + with self.exit_stack() as stack: + stack.enter_context(gets_the_context_right(exc4)) + stack.enter_context(gets_the_context_right(exc3)) + stack.enter_context(gets_the_context_right(exc2)) + raise exc1 + except Exception as exc: + self.assertIs(exc, exc4) + self.assertIs(exc.__context__, exc3) + self.assertIs(exc.__context__.__context__, exc2) + self.assertIs(exc.__context__.__context__.__context__, exc1) + self.assertIsNone( + exc.__context__.__context__.__context__.__context__) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_exit_exception_with_existing_context(self): + # Addresses a lack of test coverage discovered after checking in a + # fix for issue 20317 that still contained debugging code. + def raise_nested(inner_exc, outer_exc): + try: + raise inner_exc + finally: + raise outer_exc + exc1 = Exception(1) + exc2 = Exception(2) + exc3 = Exception(3) + exc4 = Exception(4) + exc5 = Exception(5) + try: + with self.exit_stack() as stack: + stack.callback(raise_nested, exc4, exc5) + stack.callback(raise_nested, exc2, exc3) + raise exc1 + except Exception as exc: + self.assertIs(exc, exc5) + self.assertIs(exc.__context__, exc4) + self.assertIs(exc.__context__.__context__, exc3) + self.assertIs(exc.__context__.__context__.__context__, exc2) + self.assertIs( + exc.__context__.__context__.__context__.__context__, exc1) + self.assertIsNone( + exc.__context__.__context__.__context__.__context__.__context__) + + def test_body_exception_suppress(self): + def suppress_exc(*exc_details): + return True + try: + with self.exit_stack() as stack: + stack.push(suppress_exc) + 1/0 + except IndexError as exc: + self.fail("Expected no exception, got IndexError") + + def test_exit_exception_chaining_suppress(self): + with self.exit_stack() as stack: + stack.push(lambda *exc: True) + stack.push(lambda *exc: 1/0) + stack.push(lambda *exc: {}[1]) + + def test_excessive_nesting(self): + # The original implementation would die with RecursionError here + with self.exit_stack() as stack: + for i in range(10000): + stack.callback(int) + + def test_instance_bypass(self): + class Example(object): pass + cm = Example() + cm.__exit__ = object() + stack = self.exit_stack() + self.assertRaises(AttributeError, stack.enter_context, cm) + stack.push(cm) + self.assertIs(stack._exit_callbacks[-1][1], cm) + + def test_dont_reraise_RuntimeError(self): + # https://bugs.python.org/issue27122 + class UniqueException(Exception): pass + class UniqueRuntimeError(RuntimeError): pass + + @contextmanager + def second(): + try: + yield 1 + except Exception as exc: + raise UniqueException("new exception") from exc + + @contextmanager + def first(): + try: + yield 1 + except Exception as exc: + raise exc + + # The UniqueRuntimeError should be caught by second()'s exception + # handler which chain raised a new UniqueException. + with self.assertRaises(UniqueException) as err_ctx: + with self.exit_stack() as es_ctx: + es_ctx.enter_context(second()) + es_ctx.enter_context(first()) + raise UniqueRuntimeError("please no infinite loop.") + + exc = err_ctx.exception + self.assertIsInstance(exc, UniqueException) + self.assertIsInstance(exc.__context__, UniqueRuntimeError) + self.assertIsNone(exc.__context__.__context__) + self.assertIsNone(exc.__context__.__cause__) + self.assertIs(exc.__cause__, exc.__context__) + + +class TestExitStack(TestBaseExitStack, unittest.TestCase): + exit_stack = ExitStack + + +class TestRedirectStream: + + redirect_stream = None + orig_stream = None + + @support.requires_docstrings + def test_instance_docs(self): + # Issue 19330: ensure context manager instances have good docstrings + cm_docstring = self.redirect_stream.__doc__ + obj = self.redirect_stream(None) + self.assertEqual(obj.__doc__, cm_docstring) + + def test_no_redirect_in_init(self): + orig_stdout = getattr(sys, self.orig_stream) + self.redirect_stream(None) + self.assertIs(getattr(sys, self.orig_stream), orig_stdout) + + def test_redirect_to_string_io(self): + f = io.StringIO() + msg = "Consider an API like help(), which prints directly to stdout" + orig_stdout = getattr(sys, self.orig_stream) + with self.redirect_stream(f): + print(msg, file=getattr(sys, self.orig_stream)) + self.assertIs(getattr(sys, self.orig_stream), orig_stdout) + s = f.getvalue().strip() + self.assertEqual(s, msg) + + def test_enter_result_is_target(self): + f = io.StringIO() + with self.redirect_stream(f) as enter_result: + self.assertIs(enter_result, f) + + def test_cm_is_reusable(self): + f = io.StringIO() + write_to_f = self.redirect_stream(f) + orig_stdout = getattr(sys, self.orig_stream) + with write_to_f: + print("Hello", end=" ", file=getattr(sys, self.orig_stream)) + with write_to_f: + print("World!", file=getattr(sys, self.orig_stream)) + self.assertIs(getattr(sys, self.orig_stream), orig_stdout) + s = f.getvalue() + self.assertEqual(s, "Hello World!\n") + + def test_cm_is_reentrant(self): + f = io.StringIO() + write_to_f = self.redirect_stream(f) + orig_stdout = getattr(sys, self.orig_stream) + with write_to_f: + print("Hello", end=" ", file=getattr(sys, self.orig_stream)) + with write_to_f: + print("World!", file=getattr(sys, self.orig_stream)) + self.assertIs(getattr(sys, self.orig_stream), orig_stdout) + s = f.getvalue() + self.assertEqual(s, "Hello World!\n") + + +class TestRedirectStdout(TestRedirectStream, unittest.TestCase): + + redirect_stream = redirect_stdout + orig_stream = "stdout" + + +class TestRedirectStderr(TestRedirectStream, unittest.TestCase): + + redirect_stream = redirect_stderr + orig_stream = "stderr" + + +class TestSuppress(unittest.TestCase): + + @support.requires_docstrings + def test_instance_docs(self): + # Issue 19330: ensure context manager instances have good docstrings + cm_docstring = suppress.__doc__ + obj = suppress() + self.assertEqual(obj.__doc__, cm_docstring) + + def test_no_result_from_enter(self): + with suppress(ValueError) as enter_result: + self.assertIsNone(enter_result) + + def test_no_exception(self): + with suppress(ValueError): + self.assertEqual(pow(2, 5), 32) + + def test_exact_exception(self): + with suppress(TypeError): + len(5) + + def test_exception_hierarchy(self): + with suppress(LookupError): + 'Hello'[50] + + def test_other_exception(self): + with self.assertRaises(ZeroDivisionError): + with suppress(TypeError): + 1/0 + + def test_no_args(self): + with self.assertRaises(ZeroDivisionError): + with suppress(): + 1/0 + + def test_multiple_exception_args(self): + with suppress(ZeroDivisionError, TypeError): + 1/0 + with suppress(ZeroDivisionError, TypeError): + len(5) + + def test_cm_is_reentrant(self): + ignore_exceptions = suppress(Exception) + with ignore_exceptions: + pass + with ignore_exceptions: + len(5) + with ignore_exceptions: + with ignore_exceptions: # Check nested usage + len(5) + outer_continued = True + 1/0 + self.assertTrue(outer_continued) + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_dummy_thread.py b/Lib/test/test_dummy_thread.py index d751db097d..e654a555c3 100644 --- a/Lib/test/test_dummy_thread.py +++ b/Lib/test/test_dummy_thread.py @@ -1,278 +1,278 @@ -import _dummy_thread as _thread -import time -import queue -import random -import unittest -from test import support -from unittest import mock - -DELAY = 0 - - -class LockTests(unittest.TestCase): - """Test lock objects.""" - - def setUp(self): - # Create a lock - self.lock = _thread.allocate_lock() - - def test_initlock(self): - #Make sure locks start locked - self.assertFalse(self.lock.locked(), - "Lock object is not initialized unlocked.") - - def test_release(self): - # Test self.lock.release() - self.lock.acquire() - self.lock.release() - self.assertFalse(self.lock.locked(), - "Lock object did not release properly.") - - def test_LockType_context_manager(self): - with _thread.LockType(): - pass - self.assertFalse(self.lock.locked(), - "Acquired Lock was not released") - - def test_improper_release(self): - #Make sure release of an unlocked thread raises RuntimeError - self.assertRaises(RuntimeError, self.lock.release) - - def test_cond_acquire_success(self): - #Make sure the conditional acquiring of the lock works. - self.assertTrue(self.lock.acquire(0), - "Conditional acquiring of the lock failed.") - - def test_cond_acquire_fail(self): - #Test acquiring locked lock returns False - self.lock.acquire(0) - self.assertFalse(self.lock.acquire(0), - "Conditional acquiring of a locked lock incorrectly " - "succeeded.") - - def test_uncond_acquire_success(self): - #Make sure unconditional acquiring of a lock works. - self.lock.acquire() - self.assertTrue(self.lock.locked(), - "Uncondional locking failed.") - - def test_uncond_acquire_return_val(self): - #Make sure that an unconditional locking returns True. - self.assertIs(self.lock.acquire(1), True, - "Unconditional locking did not return True.") - self.assertIs(self.lock.acquire(), True) - - def test_uncond_acquire_blocking(self): - #Make sure that unconditional acquiring of a locked lock blocks. - def delay_unlock(to_unlock, delay): - """Hold on to lock for a set amount of time before unlocking.""" - time.sleep(delay) - to_unlock.release() - - self.lock.acquire() - start_time = int(time.monotonic()) - _thread.start_new_thread(delay_unlock,(self.lock, DELAY)) - if support.verbose: - print() - print("*** Waiting for thread to release the lock "\ - "(approx. %s sec.) ***" % DELAY) - self.lock.acquire() - end_time = int(time.monotonic()) - if support.verbose: - print("done") - self.assertGreaterEqual(end_time - start_time, DELAY, - "Blocking by unconditional acquiring failed.") - - @mock.patch('time.sleep') - def test_acquire_timeout(self, mock_sleep): - """Test invoking acquire() with a positive timeout when the lock is - already acquired. Ensure that time.sleep() is invoked with the given - timeout and that False is returned.""" - - self.lock.acquire() - retval = self.lock.acquire(waitflag=0, timeout=1) - self.assertTrue(mock_sleep.called) - mock_sleep.assert_called_once_with(1) - self.assertEqual(retval, False) - - def test_lock_representation(self): - self.lock.acquire() - self.assertIn("locked", repr(self.lock)) - self.lock.release() - self.assertIn("unlocked", repr(self.lock)) - - -class RLockTests(unittest.TestCase): - """Test dummy RLock objects.""" - - def setUp(self): - self.rlock = _thread.RLock() - - def test_multiple_acquire(self): - self.assertIn("unlocked", repr(self.rlock)) - self.rlock.acquire() - self.rlock.acquire() - self.assertIn("locked", repr(self.rlock)) - self.rlock.release() - self.assertIn("locked", repr(self.rlock)) - self.rlock.release() - self.assertIn("unlocked", repr(self.rlock)) - self.assertRaises(RuntimeError, self.rlock.release) - - -class MiscTests(unittest.TestCase): - """Miscellaneous tests.""" - - def test_exit(self): - self.assertRaises(SystemExit, _thread.exit) - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_ident(self): - self.assertIsInstance(_thread.get_ident(), int, - "_thread.get_ident() returned a non-integer") - self.assertGreater(_thread.get_ident(), 0) - - def test_LockType(self): - self.assertIsInstance(_thread.allocate_lock(), _thread.LockType, - "_thread.LockType is not an instance of what " - "is returned by _thread.allocate_lock()") - - def test_set_sentinel(self): - self.assertIsInstance(_thread._set_sentinel(), _thread.LockType, - "_thread._set_sentinel() did not return a " - "LockType instance.") - - def test_interrupt_main(self): - #Calling start_new_thread with a function that executes interrupt_main - # should raise KeyboardInterrupt upon completion. - def call_interrupt(): - _thread.interrupt_main() - - self.assertRaises(KeyboardInterrupt, - _thread.start_new_thread, - call_interrupt, - tuple()) - - def test_interrupt_in_main(self): - self.assertRaises(KeyboardInterrupt, _thread.interrupt_main) - - def test_stack_size_None(self): - retval = _thread.stack_size(None) - self.assertEqual(retval, 0) - - def test_stack_size_not_None(self): - with self.assertRaises(_thread.error) as cm: - _thread.stack_size("") - self.assertEqual(cm.exception.args[0], - "setting thread stack size not supported") - - -class ThreadTests(unittest.TestCase): - """Test thread creation.""" - - def test_arg_passing(self): - #Make sure that parameter passing works. - def arg_tester(queue, arg1=False, arg2=False): - """Use to test _thread.start_new_thread() passes args properly.""" - queue.put((arg1, arg2)) - - testing_queue = queue.Queue(1) - _thread.start_new_thread(arg_tester, (testing_queue, True, True)) - result = testing_queue.get() - self.assertTrue(result[0] and result[1], - "Argument passing for thread creation " - "using tuple failed") - - _thread.start_new_thread( - arg_tester, - tuple(), - {'queue':testing_queue, 'arg1':True, 'arg2':True}) - - result = testing_queue.get() - self.assertTrue(result[0] and result[1], - "Argument passing for thread creation " - "using kwargs failed") - - _thread.start_new_thread( - arg_tester, - (testing_queue, True), - {'arg2':True}) - - result = testing_queue.get() - self.assertTrue(result[0] and result[1], - "Argument passing for thread creation using both tuple" - " and kwargs failed") - - def test_multi_thread_creation(self): - def queue_mark(queue, delay): - time.sleep(delay) - queue.put(_thread.get_ident()) - - thread_count = 5 - testing_queue = queue.Queue(thread_count) - - if support.verbose: - print() - print("*** Testing multiple thread creation " - "(will take approx. %s to %s sec.) ***" % ( - DELAY, thread_count)) - - for count in range(thread_count): - if DELAY: - local_delay = round(random.random(), 1) - else: - local_delay = 0 - _thread.start_new_thread(queue_mark, - (testing_queue, local_delay)) - time.sleep(DELAY) - if support.verbose: - print('done') - self.assertEqual(testing_queue.qsize(), thread_count, - "Not all %s threads executed properly " - "after %s sec." % (thread_count, DELAY)) - - def test_args_not_tuple(self): - """ - Test invoking start_new_thread() with a non-tuple value for "args". - Expect TypeError with a meaningful error message to be raised. - """ - with self.assertRaises(TypeError) as cm: - _thread.start_new_thread(mock.Mock(), []) - self.assertEqual(cm.exception.args[0], "2nd arg must be a tuple") - - def test_kwargs_not_dict(self): - """ - Test invoking start_new_thread() with a non-dict value for "kwargs". - Expect TypeError with a meaningful error message to be raised. - """ - with self.assertRaises(TypeError) as cm: - _thread.start_new_thread(mock.Mock(), tuple(), kwargs=[]) - self.assertEqual(cm.exception.args[0], "3rd arg must be a dict") - - def test_SystemExit(self): - """ - Test invoking start_new_thread() with a function that raises - SystemExit. - The exception should be discarded. - """ - func = mock.Mock(side_effect=SystemExit()) - try: - _thread.start_new_thread(func, tuple()) - except SystemExit: - self.fail("start_new_thread raised SystemExit.") - - @mock.patch('traceback.print_exc') - def test_RaiseException(self, mock_print_exc): - """ - Test invoking start_new_thread() with a function that raises exception. - - The exception should be discarded and the traceback should be printed - via traceback.print_exc() - """ - func = mock.Mock(side_effect=Exception) - _thread.start_new_thread(func, tuple()) - self.assertTrue(mock_print_exc.called) - -if __name__ == '__main__': - unittest.main() +import _dummy_thread as _thread +import time +import queue +import random +import unittest +from test import support +from unittest import mock + +DELAY = 0 + + +class LockTests(unittest.TestCase): + """Test lock objects.""" + + def setUp(self): + # Create a lock + self.lock = _thread.allocate_lock() + + def test_initlock(self): + #Make sure locks start locked + self.assertFalse(self.lock.locked(), + "Lock object is not initialized unlocked.") + + def test_release(self): + # Test self.lock.release() + self.lock.acquire() + self.lock.release() + self.assertFalse(self.lock.locked(), + "Lock object did not release properly.") + + def test_LockType_context_manager(self): + with _thread.LockType(): + pass + self.assertFalse(self.lock.locked(), + "Acquired Lock was not released") + + def test_improper_release(self): + #Make sure release of an unlocked thread raises RuntimeError + self.assertRaises(RuntimeError, self.lock.release) + + def test_cond_acquire_success(self): + #Make sure the conditional acquiring of the lock works. + self.assertTrue(self.lock.acquire(0), + "Conditional acquiring of the lock failed.") + + def test_cond_acquire_fail(self): + #Test acquiring locked lock returns False + self.lock.acquire(0) + self.assertFalse(self.lock.acquire(0), + "Conditional acquiring of a locked lock incorrectly " + "succeeded.") + + def test_uncond_acquire_success(self): + #Make sure unconditional acquiring of a lock works. + self.lock.acquire() + self.assertTrue(self.lock.locked(), + "Uncondional locking failed.") + + def test_uncond_acquire_return_val(self): + #Make sure that an unconditional locking returns True. + self.assertIs(self.lock.acquire(1), True, + "Unconditional locking did not return True.") + self.assertIs(self.lock.acquire(), True) + + def test_uncond_acquire_blocking(self): + #Make sure that unconditional acquiring of a locked lock blocks. + def delay_unlock(to_unlock, delay): + """Hold on to lock for a set amount of time before unlocking.""" + time.sleep(delay) + to_unlock.release() + + self.lock.acquire() + start_time = int(time.monotonic()) + _thread.start_new_thread(delay_unlock,(self.lock, DELAY)) + if support.verbose: + print() + print("*** Waiting for thread to release the lock "\ + "(approx. %s sec.) ***" % DELAY) + self.lock.acquire() + end_time = int(time.monotonic()) + if support.verbose: + print("done") + self.assertGreaterEqual(end_time - start_time, DELAY, + "Blocking by unconditional acquiring failed.") + + @mock.patch('time.sleep') + def test_acquire_timeout(self, mock_sleep): + """Test invoking acquire() with a positive timeout when the lock is + already acquired. Ensure that time.sleep() is invoked with the given + timeout and that False is returned.""" + + self.lock.acquire() + retval = self.lock.acquire(waitflag=0, timeout=1) + self.assertTrue(mock_sleep.called) + mock_sleep.assert_called_once_with(1) + self.assertEqual(retval, False) + + def test_lock_representation(self): + self.lock.acquire() + self.assertIn("locked", repr(self.lock)) + self.lock.release() + self.assertIn("unlocked", repr(self.lock)) + + +class RLockTests(unittest.TestCase): + """Test dummy RLock objects.""" + + def setUp(self): + self.rlock = _thread.RLock() + + def test_multiple_acquire(self): + self.assertIn("unlocked", repr(self.rlock)) + self.rlock.acquire() + self.rlock.acquire() + self.assertIn("locked", repr(self.rlock)) + self.rlock.release() + self.assertIn("locked", repr(self.rlock)) + self.rlock.release() + self.assertIn("unlocked", repr(self.rlock)) + self.assertRaises(RuntimeError, self.rlock.release) + + +class MiscTests(unittest.TestCase): + """Miscellaneous tests.""" + + def test_exit(self): + self.assertRaises(SystemExit, _thread.exit) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_ident(self): + self.assertIsInstance(_thread.get_ident(), int, + "_thread.get_ident() returned a non-integer") + self.assertGreater(_thread.get_ident(), 0) + + def test_LockType(self): + self.assertIsInstance(_thread.allocate_lock(), _thread.LockType, + "_thread.LockType is not an instance of what " + "is returned by _thread.allocate_lock()") + + def test_set_sentinel(self): + self.assertIsInstance(_thread._set_sentinel(), _thread.LockType, + "_thread._set_sentinel() did not return a " + "LockType instance.") + + def test_interrupt_main(self): + #Calling start_new_thread with a function that executes interrupt_main + # should raise KeyboardInterrupt upon completion. + def call_interrupt(): + _thread.interrupt_main() + + self.assertRaises(KeyboardInterrupt, + _thread.start_new_thread, + call_interrupt, + tuple()) + + def test_interrupt_in_main(self): + self.assertRaises(KeyboardInterrupt, _thread.interrupt_main) + + def test_stack_size_None(self): + retval = _thread.stack_size(None) + self.assertEqual(retval, 0) + + def test_stack_size_not_None(self): + with self.assertRaises(_thread.error) as cm: + _thread.stack_size("") + self.assertEqual(cm.exception.args[0], + "setting thread stack size not supported") + + +class ThreadTests(unittest.TestCase): + """Test thread creation.""" + + def test_arg_passing(self): + #Make sure that parameter passing works. + def arg_tester(queue, arg1=False, arg2=False): + """Use to test _thread.start_new_thread() passes args properly.""" + queue.put((arg1, arg2)) + + testing_queue = queue.Queue(1) + _thread.start_new_thread(arg_tester, (testing_queue, True, True)) + result = testing_queue.get() + self.assertTrue(result[0] and result[1], + "Argument passing for thread creation " + "using tuple failed") + + _thread.start_new_thread( + arg_tester, + tuple(), + {'queue':testing_queue, 'arg1':True, 'arg2':True}) + + result = testing_queue.get() + self.assertTrue(result[0] and result[1], + "Argument passing for thread creation " + "using kwargs failed") + + _thread.start_new_thread( + arg_tester, + (testing_queue, True), + {'arg2':True}) + + result = testing_queue.get() + self.assertTrue(result[0] and result[1], + "Argument passing for thread creation using both tuple" + " and kwargs failed") + + def test_multi_thread_creation(self): + def queue_mark(queue, delay): + time.sleep(delay) + queue.put(_thread.get_ident()) + + thread_count = 5 + testing_queue = queue.Queue(thread_count) + + if support.verbose: + print() + print("*** Testing multiple thread creation " + "(will take approx. %s to %s sec.) ***" % ( + DELAY, thread_count)) + + for count in range(thread_count): + if DELAY: + local_delay = round(random.random(), 1) + else: + local_delay = 0 + _thread.start_new_thread(queue_mark, + (testing_queue, local_delay)) + time.sleep(DELAY) + if support.verbose: + print('done') + self.assertEqual(testing_queue.qsize(), thread_count, + "Not all %s threads executed properly " + "after %s sec." % (thread_count, DELAY)) + + def test_args_not_tuple(self): + """ + Test invoking start_new_thread() with a non-tuple value for "args". + Expect TypeError with a meaningful error message to be raised. + """ + with self.assertRaises(TypeError) as cm: + _thread.start_new_thread(mock.Mock(), []) + self.assertEqual(cm.exception.args[0], "2nd arg must be a tuple") + + def test_kwargs_not_dict(self): + """ + Test invoking start_new_thread() with a non-dict value for "kwargs". + Expect TypeError with a meaningful error message to be raised. + """ + with self.assertRaises(TypeError) as cm: + _thread.start_new_thread(mock.Mock(), tuple(), kwargs=[]) + self.assertEqual(cm.exception.args[0], "3rd arg must be a dict") + + def test_SystemExit(self): + """ + Test invoking start_new_thread() with a function that raises + SystemExit. + The exception should be discarded. + """ + func = mock.Mock(side_effect=SystemExit()) + try: + _thread.start_new_thread(func, tuple()) + except SystemExit: + self.fail("start_new_thread raised SystemExit.") + + @mock.patch('traceback.print_exc') + def test_RaiseException(self, mock_print_exc): + """ + Test invoking start_new_thread() with a function that raises exception. + + The exception should be discarded and the traceback should be printed + via traceback.print_exc() + """ + func = mock.Mock(side_effect=Exception) + _thread.start_new_thread(func, tuple()) + self.assertTrue(mock_print_exc.called) + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_dummy_threading.py b/Lib/test/test_dummy_threading.py index e3ad436f0f..a0c2972a60 100644 --- a/Lib/test/test_dummy_threading.py +++ b/Lib/test/test_dummy_threading.py @@ -1,60 +1,60 @@ -from test import support -import unittest -import dummy_threading as _threading -import time - -class DummyThreadingTestCase(unittest.TestCase): - - class TestThread(_threading.Thread): - - def run(self): - global running - global sema - global mutex - # Uncomment if testing another module, such as the real 'threading' - # module. - #delay = random.random() * 2 - delay = 0 - if support.verbose: - print('task', self.name, 'will run for', delay, 'sec') - sema.acquire() - mutex.acquire() - running += 1 - if support.verbose: - print(running, 'tasks are running') - mutex.release() - time.sleep(delay) - if support.verbose: - print('task', self.name, 'done') - mutex.acquire() - running -= 1 - if support.verbose: - print(self.name, 'is finished.', running, 'tasks are running') - mutex.release() - sema.release() - - def setUp(self): - self.numtasks = 10 - global sema - sema = _threading.BoundedSemaphore(value=3) - global mutex - mutex = _threading.RLock() - global running - running = 0 - self.threads = [] - - def test_tasks(self): - for i in range(self.numtasks): - t = self.TestThread(name=""%i) - self.threads.append(t) - t.start() - - if support.verbose: - print('waiting for all tasks to complete') - for t in self.threads: - t.join() - if support.verbose: - print('all tasks done') - -if __name__ == '__main__': - unittest.main() +from test import support +import unittest +import dummy_threading as _threading +import time + +class DummyThreadingTestCase(unittest.TestCase): + + class TestThread(_threading.Thread): + + def run(self): + global running + global sema + global mutex + # Uncomment if testing another module, such as the real 'threading' + # module. + #delay = random.random() * 2 + delay = 0 + if support.verbose: + print('task', self.name, 'will run for', delay, 'sec') + sema.acquire() + mutex.acquire() + running += 1 + if support.verbose: + print(running, 'tasks are running') + mutex.release() + time.sleep(delay) + if support.verbose: + print('task', self.name, 'done') + mutex.acquire() + running -= 1 + if support.verbose: + print(self.name, 'is finished.', running, 'tasks are running') + mutex.release() + sema.release() + + def setUp(self): + self.numtasks = 10 + global sema + sema = _threading.BoundedSemaphore(value=3) + global mutex + mutex = _threading.RLock() + global running + running = 0 + self.threads = [] + + def test_tasks(self): + for i in range(self.numtasks): + t = self.TestThread(name=""%i) + self.threads.append(t) + t.start() + + if support.verbose: + print('waiting for all tasks to complete') + for t in self.threads: + t.join() + if support.verbose: + print('all tasks done') + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/tracedmodules/__init__.py b/Lib/test/tracedmodules/__init__.py index 7f16357bea..13fa4f2be9 100644 --- a/Lib/test/tracedmodules/__init__.py +++ b/Lib/test/tracedmodules/__init__.py @@ -1,4 +1,4 @@ -"""This package contains modules that help testing the trace.py module. Note -that the exact location of functions in these modules is important, as trace.py -takes the real line numbers into account. -""" +"""This package contains modules that help testing the trace.py module. Note +that the exact location of functions in these modules is important, as trace.py +takes the real line numbers into account. +""" diff --git a/Lib/test/tracedmodules/testmod.py b/Lib/test/tracedmodules/testmod.py index 06c93f649e..642776ea88 100644 --- a/Lib/test/tracedmodules/testmod.py +++ b/Lib/test/tracedmodules/testmod.py @@ -1,9 +1,9 @@ -def func(x): - b = x + 1 - return b + 2 - -def func2(): - """Test function for issue 9936 """ - return (1, - 2, - 3) +def func(x): + b = x + 1 + return b + 2 + +def func2(): + """Test function for issue 9936 """ + return (1, + 2, + 3)