diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4955fd95a..c901ada03 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -124,8 +124,8 @@ jobs: - name: Set up the Windows environment shell: bash run: | - choco install llvm openssl - echo "OPENSSL_DIR=C:\Program Files\OpenSSL-Win64" >>$GITHUB_ENV + choco install llvm openssl --no-progress + echo "OPENSSL_DIR=C:\Program Files\OpenSSL" >>$GITHUB_ENV if: runner.os == 'Windows' - name: Set up the Mac environment run: brew install autoconf automake libtool @@ -263,8 +263,8 @@ jobs: - name: Set up the Windows environment shell: bash run: | - choco install llvm openssl - echo "OPENSSL_DIR=C:\Program Files\OpenSSL-Win64" >>$GITHUB_ENV + choco install llvm openssl --no-progress + echo "OPENSSL_DIR=C:\Program Files\OpenSSL" >>$GITHUB_ENV if: runner.os == 'Windows' - name: Set up the Mac environment run: brew install autoconf automake libtool openssl@3 @@ -308,6 +308,11 @@ jobs: run: | target/release/rustpython -m ensurepip target/release/rustpython -c "import pip" + - if: runner.os != 'Windows' + name: Check if pip inside venv is functional + run: | + target/release/rustpython -m venv testvenv + testvenv/bin/rustpython -m pip install wheel - name: Check whats_left is not broken run: python -I whats_left.py diff --git a/.vscode/launch.json b/.vscode/launch.json index f0f4518df..6a632e0f1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,6 +17,9 @@ "preLaunchTask": "Build RustPython Debug", "program": "target/debug/rustpython", "args": [], + "env": { + "RUST_BACKTRACE": "1" + }, "cwd": "${workspaceFolder}" }, { diff --git a/Cargo.lock b/Cargo.lock index 83cf55763..23dcc4b2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -950,6 +950,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -2137,6 +2146,7 @@ dependencies = [ "foreign-types-shared", "gethostname", "hex", + "hmac", "itertools", "libc", "libsqlite3-sys", diff --git a/Lib/concurrent/futures/thread.py b/Lib/concurrent/futures/thread.py index 51c942f51..493861d31 100644 --- a/Lib/concurrent/futures/thread.py +++ b/Lib/concurrent/futures/thread.py @@ -37,7 +37,8 @@ def _python_exit(): threading._register_atexit(_python_exit) # At fork, reinitialize the `_global_shutdown_lock` lock in the child process -if hasattr(os, 'register_at_fork'): +# TODO RUSTPYTHON - _at_fork_reinit is not implemented yet +if hasattr(os, 'register_at_fork') and hasattr(_global_shutdown_lock, '_at_fork_reinit'): os.register_at_fork(before=_global_shutdown_lock.acquire, after_in_child=_global_shutdown_lock._at_fork_reinit, after_in_parent=_global_shutdown_lock.release) diff --git a/Lib/hashlib.py b/Lib/hashlib.py new file mode 100644 index 000000000..52a4f8a77 --- /dev/null +++ b/Lib/hashlib.py @@ -0,0 +1,316 @@ +#. Copyright (C) 2005-2010 Gregory P. Smith (greg@krypto.org) +# Licensed to PSF under a Contributor Agreement. +# + +__doc__ = """hashlib module - A common interface to many hash functions. + +new(name, data=b'', **kwargs) - returns a new hash object implementing the + given hash function; initializing the hash + using the given binary data. + +Named constructor functions are also available, these are faster +than using new(name): + +md5(), sha1(), sha224(), sha256(), sha384(), sha512(), blake2b(), blake2s(), +sha3_224, sha3_256, sha3_384, sha3_512, shake_128, and shake_256. + +More algorithms may be available on your platform but the above are guaranteed +to exist. See the algorithms_guaranteed and algorithms_available attributes +to find out what algorithm names can be passed to new(). + +NOTE: If you want the adler32 or crc32 hash functions they are available in +the zlib module. + +Choose your hash function wisely. Some have known collision weaknesses. +sha384 and sha512 will be slow on 32 bit platforms. + +Hash objects have these methods: + - update(data): Update the hash object with the bytes in data. Repeated calls + are equivalent to a single call with the concatenation of all + the arguments. + - digest(): Return the digest of the bytes passed to the update() method + so far as a bytes object. + - hexdigest(): Like digest() except the digest is returned as a string + of double length, containing only hexadecimal digits. + - copy(): Return a copy (clone) of the hash object. This can be used to + efficiently compute the digests of datas that share a common + initial substring. + +For example, to obtain the digest of the byte string 'Nobody inspects the +spammish repetition': + + >>> import hashlib + >>> m = hashlib.md5() + >>> m.update(b"Nobody inspects") + >>> m.update(b" the spammish repetition") + >>> m.digest() + b'\\xbbd\\x9c\\x83\\xdd\\x1e\\xa5\\xc9\\xd9\\xde\\xc9\\xa1\\x8d\\xf0\\xff\\xe9' + +More condensed: + + >>> hashlib.sha224(b"Nobody inspects the spammish repetition").hexdigest() + 'a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2' + +""" + +# This tuple and __get_builtin_constructor() must be modified if a new +# always available algorithm is added. +__always_supported = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', + 'blake2b', 'blake2s', + 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512', + 'shake_128', 'shake_256') + + +algorithms_guaranteed = set(__always_supported) +algorithms_available = set(__always_supported) + +__all__ = __always_supported + ('new', 'algorithms_guaranteed', + 'algorithms_available', 'pbkdf2_hmac', 'file_digest') + + +__builtin_constructor_cache = {} + +# Prefer our blake2 implementation +# OpenSSL 1.1.0 comes with a limited implementation of blake2b/s. The OpenSSL +# implementations neither support keyed blake2 (blake2 MAC) nor advanced +# features like salt, personalization, or tree hashing. OpenSSL hash-only +# variants are available as 'blake2b512' and 'blake2s256', though. +__block_openssl_constructor = { + 'blake2b', 'blake2s', +} + +def __get_builtin_constructor(name): + cache = __builtin_constructor_cache + constructor = cache.get(name) + if constructor is not None: + return constructor + try: + if name in {'SHA1', 'sha1'}: + import _sha1 + cache['SHA1'] = cache['sha1'] = _sha1.sha1 + elif name in {'MD5', 'md5'}: + import _md5 + cache['MD5'] = cache['md5'] = _md5.md5 + elif name in {'SHA256', 'sha256', 'SHA224', 'sha224'}: + import _sha256 + cache['SHA224'] = cache['sha224'] = _sha256.sha224 + cache['SHA256'] = cache['sha256'] = _sha256.sha256 + elif name in {'SHA512', 'sha512', 'SHA384', 'sha384'}: + import _sha512 + cache['SHA384'] = cache['sha384'] = _sha512.sha384 + cache['SHA512'] = cache['sha512'] = _sha512.sha512 + elif name in {'blake2b', 'blake2s'}: + import _blake2 + cache['blake2b'] = _blake2.blake2b + cache['blake2s'] = _blake2.blake2s + elif name in {'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512'}: + import _sha3 + cache['sha3_224'] = _sha3.sha3_224 + cache['sha3_256'] = _sha3.sha3_256 + cache['sha3_384'] = _sha3.sha3_384 + cache['sha3_512'] = _sha3.sha3_512 + elif name in {'shake_128', 'shake_256'}: + import _sha3 + cache['shake_128'] = _sha3.shake_128 + cache['shake_256'] = _sha3.shake_256 + except ImportError: + pass # no extension module, this hash is unsupported.''' + + constructor = cache.get(name) + if constructor is not None: + return constructor + + raise ValueError('unsupported hash type ' + name) + + +def __get_openssl_constructor(name): + if name in __block_openssl_constructor: + # Prefer our builtin blake2 implementation. + return __get_builtin_constructor(name) + try: + # MD5, SHA1, and SHA2 are in all supported OpenSSL versions + # SHA3/shake are available in OpenSSL 1.1.1+ + f = getattr(_hashlib, 'openssl_' + name) + # Allow the C module to raise ValueError. The function will be + # defined but the hash not actually available. Don't fall back to + # builtin if the current security policy blocks a digest, bpo#40695. + f(usedforsecurity=False) + # Use the C function directly (very fast) + return f + except (AttributeError, ValueError): + return __get_builtin_constructor(name) + + +def __py_new(name, data=b'', **kwargs): + """new(name, data=b'', **kwargs) - Return a new hashing object using the + named algorithm; optionally initialized with data (which must be + a bytes-like object). + """ + return __get_builtin_constructor(name)(data, **kwargs) + + +def __hash_new(name, data=b'', **kwargs): + """new(name, data=b'') - Return a new hashing object using the named algorithm; + optionally initialized with data (which must be a bytes-like object). + """ + if name in __block_openssl_constructor: + # Prefer our builtin blake2 implementation. + return __get_builtin_constructor(name)(data, **kwargs) + try: + return _hashlib.new(name, data, **kwargs) + except ValueError: + # If the _hashlib module (OpenSSL) doesn't support the named + # hash, try using our builtin implementations. + # This allows for SHA224/256 and SHA384/512 support even though + # the OpenSSL library prior to 0.9.8 doesn't provide them. + return __get_builtin_constructor(name)(data) + + +try: + import _hashlib + new = __hash_new + __get_hash = __get_openssl_constructor + # TODO: RUSTPYTHON set in _hashlib instance PyFrozenSet algorithms_available + '''algorithms_available = algorithms_available.union( + _hashlib.openssl_md_meth_names)''' +except ImportError: + _hashlib = None + new = __py_new + __get_hash = __get_builtin_constructor + +try: + # OpenSSL's PKCS5_PBKDF2_HMAC requires OpenSSL 1.0+ with HMAC and SHA + from _hashlib import pbkdf2_hmac +except ImportError: + from warnings import warn as _warn + _trans_5C = bytes((x ^ 0x5C) for x in range(256)) + _trans_36 = bytes((x ^ 0x36) for x in range(256)) + + def pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None): + """Password based key derivation function 2 (PKCS #5 v2.0) + + This Python implementations based on the hmac module about as fast + as OpenSSL's PKCS5_PBKDF2_HMAC for short passwords and much faster + for long passwords. + """ + _warn( + "Python implementation of pbkdf2_hmac() is deprecated.", + category=DeprecationWarning, + stacklevel=2 + ) + if not isinstance(hash_name, str): + raise TypeError(hash_name) + + if not isinstance(password, (bytes, bytearray)): + password = bytes(memoryview(password)) + if not isinstance(salt, (bytes, bytearray)): + salt = bytes(memoryview(salt)) + + # Fast inline HMAC implementation + inner = new(hash_name) + outer = new(hash_name) + blocksize = getattr(inner, 'block_size', 64) + if len(password) > blocksize: + password = new(hash_name, password).digest() + password = password + b'\x00' * (blocksize - len(password)) + inner.update(password.translate(_trans_36)) + outer.update(password.translate(_trans_5C)) + + def prf(msg, inner=inner, outer=outer): + # PBKDF2_HMAC uses the password as key. We can re-use the same + # digest objects and just update copies to skip initialization. + icpy = inner.copy() + ocpy = outer.copy() + icpy.update(msg) + ocpy.update(icpy.digest()) + return ocpy.digest() + + if iterations < 1: + raise ValueError(iterations) + if dklen is None: + dklen = outer.digest_size + if dklen < 1: + raise ValueError(dklen) + + dkey = b'' + loop = 1 + from_bytes = int.from_bytes + while len(dkey) < dklen: + prev = prf(salt + loop.to_bytes(4)) + # endianness doesn't matter here as long to / from use the same + rkey = from_bytes(prev) + for i in range(iterations - 1): + prev = prf(prev) + # rkey = rkey ^ prev + rkey ^= from_bytes(prev) + loop += 1 + dkey += rkey.to_bytes(inner.digest_size) + + return dkey[:dklen] + +try: + # OpenSSL's scrypt requires OpenSSL 1.1+ + from _hashlib import scrypt +except ImportError: + pass + + +def file_digest(fileobj, digest, /, *, _bufsize=2**18): + """Hash the contents of a file-like object. Returns a digest object. + + *fileobj* must be a file-like object opened for reading in binary mode. + It accepts file objects from open(), io.BytesIO(), and SocketIO objects. + The function may bypass Python's I/O and use the file descriptor *fileno* + directly. + + *digest* must either be a hash algorithm name as a *str*, a hash + constructor, or a callable that returns a hash object. + """ + # On Linux we could use AF_ALG sockets and sendfile() to archive zero-copy + # hashing with hardware acceleration. + if isinstance(digest, str): + digestobj = new(digest) + else: + digestobj = digest() + + if hasattr(fileobj, "getbuffer"): + # io.BytesIO object, use zero-copy buffer + digestobj.update(fileobj.getbuffer()) + return digestobj + + # Only binary files implement readinto(). + if not ( + hasattr(fileobj, "readinto") + and hasattr(fileobj, "readable") + and fileobj.readable() + ): + raise ValueError( + f"'{fileobj!r}' is not a file-like object in binary reading mode." + ) + + # binary file, socket.SocketIO object + # Note: socket I/O uses different syscalls than file I/O. + buf = bytearray(_bufsize) # Reusable buffer to reduce allocations. + view = memoryview(buf) + while True: + size = fileobj.readinto(buf) + if size == 0: + break # EOF + digestobj.update(view[:size]) + + return digestobj + + +for __func_name in __always_supported: + # try them all, some may not work due to the OpenSSL + # version not supporting that algorithm. + try: + globals()[__func_name] = __get_hash(__func_name) + except ValueError: + import logging + logging.exception('code for hash %s was not found.', __func_name) + + +# Cleanup locals() +del __always_supported, __func_name, __get_hash +del __py_new, __hash_new, __get_openssl_constructor diff --git a/Lib/site.py b/Lib/site.py index 06c103333..6d5dca641 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -275,7 +275,8 @@ def _getuserbase(): if os.name == "nt": base = os.environ.get("APPDATA") or "~" - return joinuser(base, "Python") + # XXX: RUSTPYTHON; please keep this change for site-packages + return joinuser(base, "RustPython") if sys.platform == "darwin" and sys._framework: return joinuser("~", "Library", sys._framework, @@ -368,7 +369,8 @@ def getsitepackages(prefixes=None): for libdir in libdirs: path = os.path.join(prefix, libdir, - "python%d.%d" % sys.version_info[:2], + # XXX: RUSTPYTHON; please keep this change for site-packages + "rustpython%d.%d" % sys.version_info[:2], "site-packages") sitepackages.append(path) else: diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py index f14b138bb..9999d6bbd 100644 --- a/Lib/sysconfig.py +++ b/Lib/sysconfig.py @@ -1,3 +1,5 @@ +# XXX: RUSTPYTHON; Trick to make sysconfig work as RustPython +exec(r''' """Access to Python's configuration information.""" import os @@ -851,10 +853,6 @@ def _main(): print() _print_dict('Variables', get_config_vars()) -# XXX RUSTPYTHON: replace python with rustpython in all these paths -for group in _INSTALL_SCHEMES.values(): - for key in group.keys(): - group[key] = group[key].replace("Python", "RustPython").replace("python", "rustpython") - if __name__ == '__main__': _main() +'''.replace("Python", "RustPython").replace("/python", "/rustpython")) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 3d89f85a5..bfa2802fb 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -37,6 +37,8 @@ try: except ImportError: pty = signal = None +import threading # XXX: RUSTPYTHON; to skip _at_fork_reinit + class Squares: @@ -2269,6 +2271,7 @@ class PtyTests(unittest.TestCase): # Check stdin/stdout error handler is used when invoking PyOS_Readline() self.check_input_tty("prompté", b"quux\xe9", "ascii") + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_input_no_stdout_fileno(self): # Issue #24402: If stdin is the original terminal but stdout.fileno() # fails, do not use the original stdout file descriptor diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 053c7bff2..1cccd27de 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -2889,9 +2889,7 @@ class TestIntFlag(unittest.TestCase): self.assertEqual(Color.ALL.value, 7) self.assertEqual(str(Color.BLUE), 'blue') - # TODO: RUSTPYTHON - @unittest.expectedFailure - @unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON, inconsistent test result on Windows due to threading") + @unittest.skip("TODO: RUSTPYTHON, inconsistent test result due to threading") @threading_helper.reap_threads def test_unique_composite(self): # override __eq__ to be identity only diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 06a84118e..84bdf83e9 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -4,6 +4,7 @@ import platform import os import struct import sys +import threading # XXX: RUSTPYTHON import unittest from multiprocessing import Process from test.support import verbose, cpython_only @@ -155,8 +156,9 @@ class TestFcntl(unittest.TestCase): self.assertRaises(ValueError, fcntl.flock, -1, fcntl.LOCK_SH) self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH) - # TODO: RUSTPYTHON, AttributeError: module 'os' has no attribute 'fork' + # TODO: RUSTPYTHON @unittest.expectedFailure + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") def test_lockf_exclusive(self): self.f = open(TESTFN, 'wb+') @@ -167,9 +169,9 @@ class TestFcntl(unittest.TestCase): p.join() fcntl.lockf(self.f, fcntl.LOCK_UN) self.assertEqual(p.exitcode, 0) - - # TODO: RUSTPYTHON, AttributeError: module 'os' has no attribute 'fork' + # TODO: RUSTPYTHON @unittest.expectedFailure + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") def test_lockf_share(self): self.f = open(TESTFN, 'wb+') diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 5c2663e8e..6f12aaa93 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -10,6 +10,7 @@ import array from binascii import unhexlify import hashlib import importlib +import io import itertools import os import sys @@ -20,6 +21,7 @@ import warnings from test import support from test.support import _4G, bigmemtest from test.support.import_helper import import_fresh_module +from test.support import os_helper from test.support import threading_helper from test.support import warnings_helper from http.client import HTTPException @@ -102,8 +104,7 @@ class HashLibTestCase(unittest.TestCase): 'sha384', 'SHA384', 'sha512', 'SHA512', 'blake2b', 'blake2s', 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512', - 'shake_128', 'shake_256' - ) + 'shake_128', 'shake_256') shakes = {'shake_128', 'shake_256'} @@ -214,15 +215,11 @@ class HashLibTestCase(unittest.TestCase): else: c.hexdigest() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_algorithms_guaranteed(self): self.assertEqual(hashlib.algorithms_guaranteed, set(_algo for _algo in self.supported_hash_names if _algo.islower())) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_algorithms_available(self): self.assertTrue(set(hashlib.algorithms_guaranteed). issubset(hashlib.algorithms_available)) @@ -262,8 +259,6 @@ class HashLibTestCase(unittest.TestCase): def test_new_upper_to_lower(self): self.assertEqual(hashlib.new("SHA256").name, "sha256") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_get_builtin_constructor(self): get_builtin_constructor = getattr(hashlib, '__get_builtin_constructor') @@ -384,6 +379,39 @@ class HashLibTestCase(unittest.TestCase): if not shake: self.assertEqual(len(digest), m.digest_size) + if not shake and kwargs.get("key") is None: + # skip shake and blake2 extended parameter tests + self.check_file_digest(name, data, hexdigest) + + def check_file_digest(self, name, data, hexdigest): + hexdigest = hexdigest.lower() + try: + hashlib.new(name) + except ValueError: + # skip, algorithm is blocked by security policy. + return + digests = [name] + digests.extend(self.constructors_to_test[name]) + + with open(os_helper.TESTFN, "wb") as f: + f.write(data) + + try: + for digest in digests: + buf = io.BytesIO(data) + buf.seek(0) + '''self.assertEqual( + dir(hashlib), None + )''' + self.assertEqual( + hashlib.file_digest(buf, digest).hexdigest(), hexdigest + ) + with open(os_helper.TESTFN, "rb") as f: + digestobj = hashlib.file_digest(f, digest) + self.assertEqual(digestobj.hexdigest(), hexdigest) + finally: + os.unlink(os_helper.TESTFN) + def check_no_unicode(self, algorithm_name): # Unicode objects are not allowed as input. constructors = self.constructors_to_test[algorithm_name] @@ -475,6 +503,8 @@ class HashLibTestCase(unittest.TestCase): self.check_sha3('shake_128', 256, 1344, b'\x1f') self.check_sha3('shake_256', 512, 1088, b'\x1f') + # TODO: RUSTPYTHON implement all blake2 params + @unittest.expectedFailure @requires_blake2 def test_blocksize_name_blake2(self): self.check_blocksize_name('blake2b', 128, 64) @@ -718,6 +748,8 @@ class HashLibTestCase(unittest.TestCase): outer.update(keyed.digest()) return outer.hexdigest() + # TODO: RUSTPYTHON add to constructor const value + @unittest.expectedFailure @requires_blake2 def test_blake2b(self): self.check_blake2(hashlib.blake2b, 16, 16, 64, 64, (1<<64)-1) @@ -739,6 +771,8 @@ class HashLibTestCase(unittest.TestCase): "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1"+ "7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923") + # TODO: RUSTPYTHON implement all blake2 fields + @unittest.expectedFailure @requires_blake2 def test_case_blake2b_all_parameters(self): # This checks that all the parameters work in general, and also that @@ -763,6 +797,8 @@ class HashLibTestCase(unittest.TestCase): key = bytes.fromhex(key) self.check('blake2b', msg, md, key=key) + # TODO: RUSTPYTHON add to constructor const value + @unittest.expectedFailure @requires_blake2 def test_blake2s(self): self.check_blake2(hashlib.blake2s, 8, 8, 32, 32, (1<<48)-1) @@ -782,6 +818,8 @@ class HashLibTestCase(unittest.TestCase): self.check('blake2s', b"abc", "508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982") + # TODO: RUSTPYTHON implement all blake2 fields + @unittest.expectedFailure @requires_blake2 def test_case_blake2s_all_parameters(self): # This checks that all the parameters work in general, and also that @@ -898,6 +936,7 @@ class HashLibTestCase(unittest.TestCase): ) @threading_helper.reap_threads + @threading_helper.requires_working_threading() def test_threaded_hashing(self): # Updating the same hash object from several threads at once # using data chunk sizes containing the same byte sequences. @@ -1136,12 +1175,37 @@ class KDFTests(unittest.TestCase): hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1, dklen=dklen) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_normalized_name(self): self.assertNotIn("blake2b512", hashlib.algorithms_available) self.assertNotIn("sha3-512", hashlib.algorithms_available) + def test_file_digest(self): + data = b'a' * 65536 + d1 = hashlib.sha256() + self.addCleanup(os.unlink, os_helper.TESTFN) + with open(os_helper.TESTFN, "wb") as f: + for _ in range(10): + d1.update(data) + f.write(data) + + with open(os_helper.TESTFN, "rb") as f: + d2 = hashlib.file_digest(f, hashlib.sha256) + + self.assertEqual(d1.hexdigest(), d2.hexdigest()) + self.assertEqual(d1.name, d2.name) + self.assertIs(type(d1), type(d2)) + + with self.assertRaises(ValueError): + hashlib.file_digest(None, "sha256") + + with self.assertRaises(ValueError): + with open(os_helper.TESTFN, "r") as f: + hashlib.file_digest(f, "sha256") + + with self.assertRaises(ValueError): + with open(os_helper.TESTFN, "wb") as f: + hashlib.file_digest(f, "sha256") + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index 7cf99735c..bc2e02528 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -39,6 +39,8 @@ def ignore_warning(func): class TestVectorsTestCase(unittest.TestCase): + # TODO: RUSTPYTHON + @unittest.expectedFailure def assert_hmac_internals( self, h, digest, hashname, digest_size, block_size ): @@ -48,6 +50,8 @@ class TestVectorsTestCase(unittest.TestCase): self.assertEqual(h.digest_size, digest_size) self.assertEqual(h.block_size, block_size) + # TODO: RUSTPYTHON + @unittest.expectedFailure def assert_hmac( self, key, data, digest, hashfunc, hashname, digest_size, block_size ): @@ -122,6 +126,8 @@ class TestVectorsTestCase(unittest.TestCase): h, digest, hashname, digest_size, block_size ) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('md5', openssl=True) def test_md5_vectors(self): # Test the HMAC module against test vectors from the RFC. @@ -164,6 +170,8 @@ class TestVectorsTestCase(unittest.TestCase): b"and Larger Than One Block-Size Data"), "6f630fad67cda0ee1fb1f562db3aa53e") + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha1', openssl=True) def test_sha_vectors(self): def shatest(key, data, digest): @@ -323,18 +331,26 @@ class TestVectorsTestCase(unittest.TestCase): '134676fb6de0446065c97440fa8c6a58', }) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha224', openssl=True) def test_sha224_rfc4231(self): self._rfc4231_test_cases(hashlib.sha224, 'sha224', 28, 64) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256', openssl=True) def test_sha256_rfc4231(self): self._rfc4231_test_cases(hashlib.sha256, 'sha256', 32, 64) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha384', openssl=True) def test_sha384_rfc4231(self): self._rfc4231_test_cases(hashlib.sha384, 'sha384', 48, 128) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha512', openssl=True) def test_sha512_rfc4231(self): self._rfc4231_test_cases(hashlib.sha512, 'sha512', 64, 128) @@ -380,6 +396,8 @@ class ConstructorTestCase(unittest.TestCase): "6c845b47f52b3b47f6590c502db7825aad757bf4fadc8fa972f7cd2e76a5bdeb" ) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_normal(self): # Standard constructor call. @@ -402,6 +420,8 @@ class ConstructorTestCase(unittest.TestCase): with self.assertRaises(TypeError): h = hmac.new("key", digestmod='sha256') + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_withtext(self): # Constructor call with text. @@ -411,6 +431,8 @@ class ConstructorTestCase(unittest.TestCase): self.fail("Constructor call with text argument raised exception.") self.assertEqual(h.hexdigest(), self.expected) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_with_bytearray(self): try: @@ -420,6 +442,8 @@ class ConstructorTestCase(unittest.TestCase): self.fail("Constructor call with bytearray arguments raised exception.") self.assertEqual(h.hexdigest(), self.expected) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_with_memoryview_msg(self): try: @@ -428,6 +452,8 @@ class ConstructorTestCase(unittest.TestCase): self.fail("Constructor call with memoryview msg raised exception.") self.assertEqual(h.hexdigest(), self.expected) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_withmodule(self): # Constructor call with text and digest module. @@ -436,6 +462,8 @@ class ConstructorTestCase(unittest.TestCase): except Exception: self.fail("Constructor call with hashlib.sha256 raised exception.") + # TODO: RUSTPYTHON + @unittest.expectedFailure @unittest.skipUnless(C_HMAC is not None, 'need _hashlib') def test_internal_types(self): # internal types like _hashlib.C_HMAC are not constructable @@ -443,6 +471,8 @@ class ConstructorTestCase(unittest.TestCase): with self.assertRaisesRegex(TypeError, "immutable type"): C_HMAC.value = None + # TODO: RUSTPYTHON + @unittest.expectedFailure @unittest.skipUnless(sha256_module is not None, 'need _sha256') def test_with_sha256_module(self): h = hmac.HMAC(b"key", b"hash this!", digestmod=sha256_module.sha256) @@ -455,6 +485,8 @@ class ConstructorTestCase(unittest.TestCase): class SanityTestCase(unittest.TestCase): + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_exercise_all_methods(self): # Exercising all methods once. @@ -496,6 +528,8 @@ class CopyTestCase(unittest.TestCase): "No real copy of the attribute 'outer'.") self.assertIs(h1._hmac, None) + # TODO: RUSTPYTHON + @unittest.expectedFailure @unittest.skipIf(_hashopenssl is None, "test requires _hashopenssl") @hashlib_helper.requires_hashdigest('sha256') def test_realcopy_hmac(self): @@ -504,6 +538,8 @@ class CopyTestCase(unittest.TestCase): h2 = h1.copy() self.assertTrue(id(h1._hmac) != id(h2._hmac)) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_equality(self): # Testing if the copy has the same digests. @@ -515,6 +551,8 @@ class CopyTestCase(unittest.TestCase): self.assertEqual(h1.hexdigest(), h2.hexdigest(), "Hexdigest of copy doesn't match original hexdigest.") + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_equality_new(self): # Testing if the copy has the same digests with hmac.new(). @@ -532,6 +570,8 @@ class CopyTestCase(unittest.TestCase): class CompareDigestTestCase(unittest.TestCase): + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_hmac_compare_digest(self): self._test_compare_digest(hmac.compare_digest) if openssl_compare_digest is not None: @@ -542,6 +582,8 @@ class CompareDigestTestCase(unittest.TestCase): def test_operator_compare_digest(self): self._test_compare_digest(operator_compare_digest) + # TODO: RUSTPYTHON + @unittest.expectedFailure @unittest.skipIf(openssl_compare_digest is None, "test requires _hashlib") def test_openssl_compare_digest(self): self._test_compare_digest(openssl_compare_digest) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index d31582c0d..7480a27ad 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -607,7 +607,8 @@ for k, v in os.environ.items(): print("") """ - +@unittest.skipIf(not hasattr(os, '_exit'), + "TODO: RUSTPYTHON, run_cgi in http/server.py gets stuck as os._exit(127) doesn't currently kill forked processes") @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, "This test can't be run reliably as root (issue #13308).") class CGIHTTPServerTestCase(BaseTestCase): @@ -765,6 +766,7 @@ class CGIHTTPServerTestCase(BaseTestCase): msg='path = %r\nGot: %r\nWanted: %r' % (path, actual, expected)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_headers_and_content(self): res = self.request('/cgi-bin/file1.py') self.assertEqual( @@ -775,6 +777,7 @@ class CGIHTTPServerTestCase(BaseTestCase): res = self.request('///////////nocgi.py/../cgi-bin/nothere.sh') self.assertEqual(res.status, HTTPStatus.NOT_FOUND) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_post(self): params = urllib.parse.urlencode( {'spam' : 1, 'eggs' : 'python', 'bacon' : 123456}) @@ -788,6 +791,7 @@ class CGIHTTPServerTestCase(BaseTestCase): res.read() self.assertEqual(res.status, HTTPStatus.NOT_FOUND) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_authorization(self): headers = {b'Authorization' : b'Basic ' + base64.b64encode(b'username:pass')} @@ -796,6 +800,7 @@ class CGIHTTPServerTestCase(BaseTestCase): (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_no_leading_slash(self): # http://bugs.python.org/issue2254 res = self.request('cgi-bin/file1.py') @@ -803,6 +808,7 @@ class CGIHTTPServerTestCase(BaseTestCase): (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_os_environ_is_not_altered(self): signature = "Test CGI Server" os.environ['SERVER_SOFTWARE'] = signature @@ -812,24 +818,28 @@ class CGIHTTPServerTestCase(BaseTestCase): (res.read(), res.getheader('Content-type'), res.status)) self.assertEqual(os.environ['SERVER_SOFTWARE'], signature) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_urlquote_decoding_in_cgi_check(self): res = self.request('/cgi-bin%2ffile1.py') self.assertEqual( (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_nested_cgi_path_issue21323(self): res = self.request('/cgi-bin/child-dir/file3.py') self.assertEqual( (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_query_with_multiple_question_mark(self): res = self.request('/cgi-bin/file4.py?a=b?c=d') self.assertEqual( (b'a=b?c=d' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_query_with_continuous_slashes(self): res = self.request('/cgi-bin/file4.py?k=aa%2F%2Fbb&//q//p//=//a//b//') self.assertEqual( @@ -837,6 +847,7 @@ class CGIHTTPServerTestCase(BaseTestCase): 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_cgi_path_in_sub_directories(self): try: CGIHTTPRequestHandler.cgi_directories.append('/sub/dir/cgi-bin') @@ -847,6 +858,7 @@ class CGIHTTPServerTestCase(BaseTestCase): finally: CGIHTTPRequestHandler.cgi_directories.remove('/sub/dir/cgi-bin') + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_accept(self): browser_accept = \ 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' diff --git a/Lib/test/test_importlib/test_threaded_import.py b/Lib/test/test_importlib/test_threaded_import.py index c13195218..49c02484b 100644 --- a/Lib/test/test_importlib/test_threaded_import.py +++ b/Lib/test/test_importlib/test_threaded_import.py @@ -6,6 +6,7 @@ # randrange, and then Python hangs. import _imp as imp +import _multiprocessing # TODO: RUSTPYTHON import os import importlib import sys @@ -260,6 +261,7 @@ class ThreadedImportTests(unittest.TestCase): 'partial', 'cfimport.py') script_helper.assert_python_ok(fn) + @unittest.skipUnless(hasattr(_multiprocessing, "SemLock"), "TODO: RUSTPYTHON, pool_in_threads.py needs _multiprocessing.SemLock") @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_multiprocessing_pool_circular_import(self): # Regression test for bpo-41567 diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 34afabefe..d7776a025 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -69,6 +69,10 @@ try: except ImportError: INT_MAX = PY_SSIZE_T_MAX = sys.maxsize +try: + import _testcapi +except ImportError: + _testcapi = None from test.support.script_helper import assert_python_ok from test.support import unix_shell @@ -3067,11 +3071,13 @@ class PidTests(unittest.TestCase): self.assertEqual(pid2, pid) # TODO: RUSTPYTHON (AttributeError: module 'os' has no attribute 'spawnv') + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.expectedFailure def test_waitpid(self): self.check_waitpid(code='pass', exitcode=0) # TODO: RUSTPYTHON (AttributeError: module 'os' has no attribute 'spawnv') + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.expectedFailure def test_waitstatus_to_exitcode(self): exitcode = 23 @@ -3103,7 +3109,8 @@ class PidTests(unittest.TestCase): os.waitstatus_to_exitcode((max_exitcode + 1) << 8) with self.assertRaises(OverflowError): os.waitstatus_to_exitcode(-1) - + + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') # TODO: RUSTPYTHON (AttributeError: module 'os' has no attribute 'spawnv') @unittest.expectedFailure # Skip the test on Windows @@ -3146,31 +3153,36 @@ class SpawnTests(unittest.TestCase): for k, v in self.env.items()} return args - + + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnl') def test_spawnl(self): args = self.create_args() exitcode = os.spawnl(os.P_WAIT, args[0], *args) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnle') def test_spawnle(self): args = self.create_args(with_env=True) exitcode = os.spawnle(os.P_WAIT, args[0], *args, self.env) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnlp') def test_spawnlp(self): args = self.create_args() exitcode = os.spawnlp(os.P_WAIT, args[0], *args) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnlpe') def test_spawnlpe(self): args = self.create_args(with_env=True) exitcode = os.spawnlpe(os.P_WAIT, args[0], *args, self.env) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnv') def test_spawnv(self): args = self.create_args() @@ -3181,30 +3193,35 @@ class SpawnTests(unittest.TestCase): exitcode = os.spawnv(os.P_WAIT, FakePath(args[0]), args) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnve') def test_spawnve(self): args = self.create_args(with_env=True) exitcode = os.spawnve(os.P_WAIT, args[0], args, self.env) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnvp') def test_spawnvp(self): args = self.create_args() exitcode = os.spawnvp(os.P_WAIT, args[0], args) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnvpe') def test_spawnvpe(self): args = self.create_args(with_env=True) exitcode = os.spawnvpe(os.P_WAIT, args[0], args, self.env) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnv') def test_nowait(self): args = self.create_args() pid = os.spawnv(os.P_NOWAIT, args[0], args) support.wait_process(pid, exitcode=self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnve') def test_spawnve_bytes(self): # Test bytes handling in parse_arglist and parse_envlist (#28114) @@ -3286,10 +3303,12 @@ class SpawnTests(unittest.TestCase): exitcode = spawn(os.P_WAIT, args[0], args, newenv) self.assertEqual(exitcode, 0) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnve') def test_spawnve_invalid_env(self): self._test_invalid_env(os.spawnve) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnvpe') def test_spawnvpe_invalid_env(self): self._test_invalid_env(os.spawnvpe) @@ -4660,6 +4679,35 @@ class ForkTests(unittest.TestCase): assert_python_ok("-c", code) assert_python_ok("-c", code, PYTHONMALLOC="malloc_debug") + @unittest.skipIf(_testcapi is None, 'TODO: RUSTPYTHON; needs _testcapi') + @unittest.skipUnless(sys.platform in ("linux", "darwin"), + "Only Linux and macOS detect this today.") + def test_fork_warns_when_non_python_thread_exists(self): + code = """if 1: + import os, threading, warnings + from _testcapi import _spawn_pthread_waiter, _end_spawned_pthread + _spawn_pthread_waiter() + try: + with warnings.catch_warnings(record=True) as ws: + warnings.filterwarnings( + "always", category=DeprecationWarning) + if os.fork() == 0: + assert not ws, f"unexpected warnings in child: {ws}" + os._exit(0) # child + else: + assert ws[0].category == DeprecationWarning, ws[0] + assert 'fork' in str(ws[0].message), ws[0] + # Waiting allows an error in the child to hit stderr. + exitcode = os.wait()[1] + assert exitcode == 0, f"child exited {exitcode}" + assert threading.active_count() == 1, threading.enumerate() + finally: + _end_spawned_pthread() + """ + _, out, err = assert_python_ok("-c", code, PYTHONOPTIMIZE='0') + self.assertEqual(err.decode("utf-8"), "") + self.assertEqual(out.decode("utf-8"), "") + # Only test if the C version is provided, otherwise TestPEP519 already tested # the pure Python implementation. diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 0d2a04c79..c6a1cc341 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -346,8 +346,6 @@ class PlatformTest(unittest.TestCase): else: self.assertEqual(res[2], 'PowerPC') - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipUnless(sys.platform == 'darwin', "OSX only test") def test_mac_ver_with_fork(self): # Issue7895: platform.mac_ver() crashes when using fork without exec diff --git a/Lib/test/test_poll.py b/Lib/test/test_poll.py new file mode 100644 index 000000000..88eed5303 --- /dev/null +++ b/Lib/test/test_poll.py @@ -0,0 +1,240 @@ +# Test case for the os.poll() function + +import os +import subprocess +import random +import select +import threading +import time +import unittest +from test.support import ( + cpython_only, requires_subprocess, requires_working_socket +) +from test.support import threading_helper +from test.support.os_helper import TESTFN + + +try: + select.poll +except AttributeError: + raise unittest.SkipTest("select.poll not defined") + +requires_working_socket(module=True) + +def find_ready_matching(ready, flag): + match = [] + for fd, mode in ready: + if mode & flag: + match.append(fd) + return match + +class PollTests(unittest.TestCase): + + def test_poll1(self): + # Basic functional test of poll object + # Create a bunch of pipe and test that poll works with them. + + p = select.poll() + + NUM_PIPES = 12 + MSG = b" This is a test." + MSG_LEN = len(MSG) + readers = [] + writers = [] + r2w = {} + w2r = {} + + for i in range(NUM_PIPES): + rd, wr = os.pipe() + p.register(rd) + p.modify(rd, select.POLLIN) + p.register(wr, select.POLLOUT) + readers.append(rd) + writers.append(wr) + r2w[rd] = wr + w2r[wr] = rd + + bufs = [] + + while writers: + ready = p.poll() + ready_writers = find_ready_matching(ready, select.POLLOUT) + if not ready_writers: + raise RuntimeError("no pipes ready for writing") + wr = random.choice(ready_writers) + os.write(wr, MSG) + + ready = p.poll() + ready_readers = find_ready_matching(ready, select.POLLIN) + if not ready_readers: + raise RuntimeError("no pipes ready for reading") + rd = random.choice(ready_readers) + buf = os.read(rd, MSG_LEN) + self.assertEqual(len(buf), MSG_LEN) + bufs.append(buf) + os.close(r2w[rd]) ; os.close( rd ) + p.unregister( r2w[rd] ) + p.unregister( rd ) + writers.remove(r2w[rd]) + + self.assertEqual(bufs, [MSG] * NUM_PIPES) + + def test_poll_unit_tests(self): + # returns NVAL for invalid file descriptor + FD, w = os.pipe() + os.close(FD) + os.close(w) + p = select.poll() + p.register(FD) + r = p.poll() + self.assertEqual(r[0], (FD, select.POLLNVAL)) + + with open(TESTFN, 'w') as f: + fd = f.fileno() + p = select.poll() + p.register(f) + r = p.poll() + self.assertEqual(r[0][0], fd) + r = p.poll() + self.assertEqual(r[0], (fd, select.POLLNVAL)) + os.unlink(TESTFN) + + # type error for invalid arguments + p = select.poll() + self.assertRaises(TypeError, p.register, p) + self.assertRaises(TypeError, p.unregister, p) + + # can't unregister non-existent object + p = select.poll() + self.assertRaises(KeyError, p.unregister, 3) + + # Test error cases + pollster = select.poll() + class Nope: + pass + + class Almost: + def fileno(self): + return 'fileno' + + self.assertRaises(TypeError, pollster.register, Nope(), 0) + self.assertRaises(TypeError, pollster.register, Almost(), 0) + + # Another test case for poll(). This is copied from the test case for + # select(), modified to use poll() instead. + + @requires_subprocess() + def test_poll2(self): + cmd = 'for i in 0 1 2 3 4 5 6 7 8 9; do echo testing...; sleep 1; done' + proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + bufsize=0) + self.enterContext(proc) + p = proc.stdout + pollster = select.poll() + pollster.register( p, select.POLLIN ) + for tout in (0, 1000, 2000, 4000, 8000, 16000) + (-1,)*10: + fdlist = pollster.poll(tout) + if (fdlist == []): + continue + fd, flags = fdlist[0] + if flags & select.POLLHUP: + line = p.readline() + if line != b"": + self.fail('error: pipe seems to be closed, but still returns data') + continue + + elif flags & select.POLLIN: + line = p.readline() + if not line: + break + self.assertEqual(line, b'testing...\n') + continue + else: + self.fail('Unexpected return value from select.poll: %s' % fdlist) + + # TODO: RUSTPYTHON int overflow + @unittest.expectedFailure + def test_poll3(self): + # test int overflow + pollster = select.poll() + pollster.register(1) + + self.assertRaises(OverflowError, pollster.poll, 1 << 64) + + x = 2 + 3 + if x != 5: + self.fail('Overflow must have occurred') + + # Issues #15989, #17919 + self.assertRaises(ValueError, pollster.register, 0, -1) + self.assertRaises(OverflowError, pollster.register, 0, 1 << 64) + self.assertRaises(ValueError, pollster.modify, 1, -1) + self.assertRaises(OverflowError, pollster.modify, 1, 1 << 64) + + @cpython_only + def test_poll_c_limits(self): + from _testcapi import USHRT_MAX, INT_MAX, UINT_MAX + pollster = select.poll() + pollster.register(1) + + # Issues #15989, #17919 + self.assertRaises(OverflowError, pollster.register, 0, USHRT_MAX + 1) + self.assertRaises(OverflowError, pollster.modify, 1, USHRT_MAX + 1) + self.assertRaises(OverflowError, pollster.poll, INT_MAX + 1) + self.assertRaises(OverflowError, pollster.poll, UINT_MAX + 1) + + @unittest.skip("TODO: RUSTPYTHON fd reallocation") + @threading_helper.reap_threads + def test_threaded_poll(self): + r, w = os.pipe() + self.addCleanup(os.close, r) + self.addCleanup(os.close, w) + rfds = [] + for i in range(10): + fd = os.dup(r) + self.addCleanup(os.close, fd) + rfds.append(fd) + pollster = select.poll() + for fd in rfds: + pollster.register(fd, select.POLLIN) + t = threading.Thread(target=pollster.poll) + t.start() + try: + time.sleep(0.5) + # trigger ufds array reallocation + for fd in rfds: + pollster.unregister(fd) + pollster.register(w, select.POLLOUT) + self.assertRaises(RuntimeError, pollster.poll) + finally: + # and make the call to poll() from the thread return + os.write(w, b'spam') + t.join() + + # TODO: RUSTPYTHON add support for negative timeout + @unittest.expectedFailure + @unittest.skipUnless(threading, 'Threading required for this test.') + @threading_helper.reap_threads + def test_poll_blocks_with_negative_ms(self): + for timeout_ms in [None, -1000, -1, -1.0, -0.1, -1e-100]: + # Create two file descriptors. This will be used to unlock + # the blocking call to poll.poll inside the thread + r, w = os.pipe() + pollster = select.poll() + pollster.register(r, select.POLLIN) + + poll_thread = threading.Thread(target=pollster.poll, args=(timeout_ms,)) + poll_thread.start() + poll_thread.join(timeout=0.1) + self.assertTrue(poll_thread.is_alive()) + + # Write to the pipe so pollster.poll unblocks and the thread ends. + os.write(w, b'spam') + poll_thread.join() + self.assertFalse(poll_thread.is_alive()) + os.close(r) + os.close(w) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 09123e08a..5312925d9 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -226,6 +226,23 @@ class PropertyTests(unittest.TestCase): ): p.__set_name__(*([0] * i)) + def test_property_setname_on_property_subclass(self): + # https://github.com/python/cpython/issues/100942 + # Copy was setting the name field without first + # verifying that the copy was an actual property + # instance. As a result, the code below was + # causing a segfault. + + class pro(property): + def __new__(typ, *args, **kwargs): + return "abcdef" + + class A: + pass + + p = property.__new__(pro) + p.__set_name__(A, 1) + np = p.getter(lambda self: 1) # Issue 5890: subclasses of property do not preserve method __doc__ strings class PropertySub(property): @@ -341,43 +358,35 @@ class _PropertyUnreachableAttribute: def setUpClass(cls): cls.obj = cls.cls() + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_get_property(self): - with self.assertRaisesRegex(AttributeError, self._format_exc_msg("unreadable attribute")): + with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no getter")): self.obj.foo + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_set_property(self): - with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't set attribute")): + with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no setter")): self.obj.foo = None + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_del_property(self): - with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't delete attribute")): + with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no deleter")): del self.obj.foo class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = "^{} 'foo'$" + msg_format = r"^property 'foo' of 'PropertyUnreachableAttributeWithName\.cls' object {}$" class cls: foo = property() - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_get_property(self): # TODO: RUSTPYTHON; remove this function when the test is fixed - super().test_get_property() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_set_property(self): # TODO: RUSTPYTHON; remove this function when the test is fixed - super().test_get_property() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_del_property(self): # TODO: RUSTPYTHON; remove this function when the test is fixed - super().test_get_property() class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = "^{}$" + msg_format = r"^property of 'PropertyUnreachableAttributeNoName\.cls' object {}$" class cls: pass diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index 7c38b64f7..7bd1d0877 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -1,3 +1,4 @@ +import threading # XXX: RUSTPYTHON from test.support import verbose, reap_children from test.support.import_helper import import_module @@ -211,6 +212,7 @@ class PtyTest(unittest.TestCase): self.assertEqual(b'For my pet fish, Eric.\n', normalize_output(s2)) # TODO: RUSTPYTHON + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.expectedFailure def test_fork(self): debug("calling pty.fork()") @@ -314,6 +316,7 @@ class PtyTest(unittest.TestCase): self.assertEqual(data, b"") # TODO: RUSTPYTHON; no os.fork + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.expectedFailure def test_spawn_doesnt_hang(self): pty.spawn([sys.executable, '-c', 'print("hi there")']) diff --git a/Lib/test/test_pulldom.py b/Lib/test/test_pulldom.py new file mode 100644 index 000000000..1308c73be --- /dev/null +++ b/Lib/test/test_pulldom.py @@ -0,0 +1,356 @@ +import io +import unittest +import xml.sax + +from xml.sax.xmlreader import AttributesImpl +from xml.sax.handler import feature_external_ges +from xml.dom import pulldom + +from test.support import findfile + + +tstfile = findfile("test.xml", subdir="xmltestdata") + +# A handy XML snippet, containing attributes, a namespace prefix, and a +# self-closing tag: +SMALL_SAMPLE = """ + + +